Merge branch 'master-nj' of http://192.168.0.200/knowledge-base-bj/deerflow-code into master-zl
This commit is contained in:
commit
8e77e2da1e
3 files changed
+196
-111
No files matched your search
@@ -15,7 +15,7 @@ import {
|
|||||||
SquareTerminalIcon,
|
SquareTerminalIcon,
|
||||||
WrenchIcon,
|
WrenchIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState, type AnchorHTMLAttributes } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ChainOfThought,
|
ChainOfThought,
|
||||||
@@ -58,6 +58,7 @@ import { useRehypeSplitWordsIntoSpans } from "@/core/rehype";
|
|||||||
import { useLocalSettings } from "@/core/settings";
|
import { useLocalSettings } from "@/core/settings";
|
||||||
import { useSkills } from "@/core/skills/hooks";
|
import { useSkills } from "@/core/skills/hooks";
|
||||||
import {
|
import {
|
||||||
|
applyInlineCitations,
|
||||||
buildSkillDisplayMap,
|
buildSkillDisplayMap,
|
||||||
findDisplayConfigForTool,
|
findDisplayConfigForTool,
|
||||||
isDisplaySamplePayload,
|
isDisplaySamplePayload,
|
||||||
@@ -71,6 +72,7 @@ import { env } from "@/env";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
import { useArtifactsOptional } from "../artifacts";
|
import { useArtifactsOptional } from "../artifacts";
|
||||||
|
import { CitationLink } from "../citations/citation-link";
|
||||||
import { FlipDisplay } from "../flip-display";
|
import { FlipDisplay } from "../flip-display";
|
||||||
import { Tooltip } from "../tooltip";
|
import { Tooltip } from "../tooltip";
|
||||||
import {
|
import {
|
||||||
@@ -79,15 +81,18 @@ import {
|
|||||||
} from "../skill-display-sources";
|
} from "../skill-display-sources";
|
||||||
|
|
||||||
import { MarkdownContent } from "./markdown-content";
|
import { MarkdownContent } from "./markdown-content";
|
||||||
|
import { SingleRoundRagCitationDialog } from "./message-list-item";
|
||||||
|
|
||||||
export function MessageGroup({
|
export function MessageGroup({
|
||||||
className,
|
className,
|
||||||
messages,
|
messages,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
|
displaySources = [],
|
||||||
}: {
|
}: {
|
||||||
className?: string;
|
className?: string;
|
||||||
messages: Message[];
|
messages: Message[];
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
|
displaySources?: RuntimeDisplaySource[];
|
||||||
}) {
|
}) {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const [showAbove, setShowAbove] = useState(
|
const [showAbove, setShowAbove] = useState(
|
||||||
@@ -124,11 +129,29 @@ export function MessageGroup({
|
|||||||
}
|
}
|
||||||
}, [lastToolCallStep, steps]);
|
}, [lastToolCallStep, steps]);
|
||||||
const rehypePlugins = useRehypeSplitWordsIntoSpans(isLoading);
|
const rehypePlugins = useRehypeSplitWordsIntoSpans(isLoading);
|
||||||
|
const [selectedCitation, setSelectedCitation] =
|
||||||
|
useState<RuntimeDisplaySource | null>(null);
|
||||||
|
const handleCitationClick = useCallback(
|
||||||
|
(referenceId: string) => {
|
||||||
|
const referenceIndex = Number(referenceId);
|
||||||
|
if (!Number.isInteger(referenceIndex) || referenceIndex < 1) return;
|
||||||
|
const source =
|
||||||
|
displaySources.find((item) => item.index === referenceIndex) ??
|
||||||
|
displaySources[referenceIndex - 1];
|
||||||
|
if (source) setSelectedCitation(source);
|
||||||
|
},
|
||||||
|
[displaySources],
|
||||||
|
);
|
||||||
|
const reasoningId =
|
||||||
|
lastReasoningStep?.id ??
|
||||||
|
messages.find((message) => message.type === "ai")?.id;
|
||||||
return (
|
return (
|
||||||
<ChainOfThought
|
<>
|
||||||
className={cn("w-full gap-2 rounded-lg border p-0.5", className)}
|
<ChainOfThought
|
||||||
open={true}
|
className={cn("w-full gap-2 rounded-lg border p-0.5", className)}
|
||||||
>
|
data-reference-batch-id={reasoningId}
|
||||||
|
open={true}
|
||||||
|
>
|
||||||
{aboveLastToolCallSteps.length > 0 && (
|
{aboveLastToolCallSteps.length > 0 && (
|
||||||
<Button
|
<Button
|
||||||
key="above"
|
key="above"
|
||||||
@@ -163,10 +186,12 @@ export function MessageGroup({
|
|||||||
<ChainOfThoughtStep
|
<ChainOfThoughtStep
|
||||||
key={step.id}
|
key={step.id}
|
||||||
label={
|
label={
|
||||||
<MarkdownContent
|
<StepReasoningMarkdown
|
||||||
content={step.reasoning ?? ""}
|
content={step.reasoning ?? ""}
|
||||||
|
displaySources={displaySources}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
rehypePlugins={rehypePlugins}
|
rehypePlugins={rehypePlugins}
|
||||||
|
onCitationClick={handleCitationClick}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
></ChainOfThoughtStep>
|
></ChainOfThoughtStep>
|
||||||
@@ -223,10 +248,12 @@ export function MessageGroup({
|
|||||||
<ChainOfThoughtStep
|
<ChainOfThoughtStep
|
||||||
key={lastReasoningStep.id}
|
key={lastReasoningStep.id}
|
||||||
label={
|
label={
|
||||||
<MarkdownContent
|
<StepReasoningMarkdown
|
||||||
content={lastReasoningStep.reasoning ?? ""}
|
content={lastReasoningStep.reasoning ?? ""}
|
||||||
|
displaySources={displaySources}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
rehypePlugins={rehypePlugins}
|
rehypePlugins={rehypePlugins}
|
||||||
|
onCitationClick={handleCitationClick}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
></ChainOfThoughtStep>
|
></ChainOfThoughtStep>
|
||||||
@@ -234,7 +261,60 @@ export function MessageGroup({
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</ChainOfThought>
|
</ChainOfThought>
|
||||||
|
<SingleRoundRagCitationDialog
|
||||||
|
source={selectedCitation}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open) setSelectedCitation(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function StepReasoningMarkdown({
|
||||||
|
content,
|
||||||
|
displaySources,
|
||||||
|
isLoading,
|
||||||
|
rehypePlugins,
|
||||||
|
onCitationClick,
|
||||||
|
}: {
|
||||||
|
content: string;
|
||||||
|
displaySources: RuntimeDisplaySource[];
|
||||||
|
isLoading: boolean;
|
||||||
|
rehypePlugins: ReturnType<typeof useRehypeSplitWordsIntoSpans>;
|
||||||
|
onCitationClick: (referenceId: string) => void;
|
||||||
|
}) {
|
||||||
|
const markdown = useMemo(
|
||||||
|
() =>
|
||||||
|
applyInlineCitations(content, displaySources, { appendSummary: false }),
|
||||||
|
[content, displaySources],
|
||||||
|
);
|
||||||
|
const components = useMemo(() => {
|
||||||
|
if (displaySources.length === 0) return undefined;
|
||||||
|
return {
|
||||||
|
a: ({ href, ...props }: AnchorHTMLAttributes<HTMLAnchorElement>) => {
|
||||||
|
if (href?.startsWith("#reference-")) {
|
||||||
|
return (
|
||||||
|
<CitationLink
|
||||||
|
href={href}
|
||||||
|
{...props}
|
||||||
|
onReferenceClick={onCitationClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <a href={href} {...props} />;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}, [displaySources.length, onCitationClick]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MarkdownContent
|
||||||
|
content={markdown}
|
||||||
|
isLoading={isLoading}
|
||||||
|
rehypePlugins={rehypePlugins}
|
||||||
|
components={components}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -695,13 +695,6 @@ function MessageContent_({
|
|||||||
const withCitations = applyInlineCitations(rawContent ?? '', displaySources);
|
const withCitations = applyInlineCitations(rawContent ?? '', displaySources);
|
||||||
return stripIncompleteCitationMarker(withCitations);
|
return stripIncompleteCitationMarker(withCitations);
|
||||||
}, [rawContent, isHuman, displaySources]);
|
}, [rawContent, isHuman, displaySources]);
|
||||||
const reasoningToDisplay = useMemo(() => {
|
|
||||||
if (!reasoningContent) return '';
|
|
||||||
const withCitations = applyInlineCitations(reasoningContent, displaySources, {
|
|
||||||
appendSummary: false,
|
|
||||||
});
|
|
||||||
return stripIncompleteCitationMarker(withCitations);
|
|
||||||
}, [reasoningContent, displaySources]);
|
|
||||||
const markdownRehypePlugins = useMemo(
|
const markdownRehypePlugins = useMemo(
|
||||||
() => [...rehypePlugins, [rehypeKatex, { output: 'html' as const }]],
|
() => [...rehypePlugins, [rehypeKatex, { output: 'html' as const }]],
|
||||||
[rehypePlugins],
|
[rehypePlugins],
|
||||||
@@ -735,6 +728,23 @@ function MessageContent_({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reasoning-only AI message (no main response content yet)
|
||||||
|
if (!isHuman && reasoningContent && !rawContent && !multiRoundTrace) {
|
||||||
|
return (
|
||||||
|
<AIElementMessageContent className={className}>
|
||||||
|
<Reasoning isStreaming={isLoading}>
|
||||||
|
<ReasoningTrigger />
|
||||||
|
<ReasoningContent>{reasoningContent}</ReasoningContent>
|
||||||
|
</Reasoning>
|
||||||
|
<MessageTokenUsage
|
||||||
|
enabled={tokenUsageEnabled}
|
||||||
|
isLoading={isLoading}
|
||||||
|
message={message}
|
||||||
|
/>
|
||||||
|
</AIElementMessageContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (isHuman) {
|
if (isHuman) {
|
||||||
// Only the user's own question carries a delete affordance, and only once
|
// Only the user's own question carries a delete affordance, and only once
|
||||||
// it exists server-side (real, non-optimistic id). Deleting it cascades to
|
// it exists server-side (real, non-optimistic id). Deleting it cascades to
|
||||||
@@ -770,24 +780,6 @@ function MessageContent_({
|
|||||||
<AIElementMessageContent className={className}>
|
<AIElementMessageContent className={className}>
|
||||||
{filesList}
|
{filesList}
|
||||||
<div data-reference-batch-id={message.id} className="contents">
|
<div data-reference-batch-id={message.id} className="contents">
|
||||||
{reasoningToDisplay && !multiRoundTrace && (
|
|
||||||
<Reasoning isStreaming={isLoading && !contentToDisplay}>
|
|
||||||
<ReasoningTrigger />
|
|
||||||
<ReasoningContent>
|
|
||||||
{isSingleRoundRag || displaySources.length > 0 ? (
|
|
||||||
<MarkdownContent
|
|
||||||
content={reasoningToDisplay}
|
|
||||||
isLoading={isLoading}
|
|
||||||
rehypePlugins={markdownRehypePlugins}
|
|
||||||
components={components}
|
|
||||||
className="text-muted-foreground text-sm"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
reasoningToDisplay
|
|
||||||
)}
|
|
||||||
</ReasoningContent>
|
|
||||||
</Reasoning>
|
|
||||||
)}
|
|
||||||
{multiRoundTrace ? (
|
{multiRoundTrace ? (
|
||||||
<MultiRoundRagContent
|
<MultiRoundRagContent
|
||||||
trace={multiRoundTrace}
|
trace={multiRoundTrace}
|
||||||
@@ -797,7 +789,7 @@ function MessageContent_({
|
|||||||
rehypePlugins={markdownRehypePlugins}
|
rehypePlugins={markdownRehypePlugins}
|
||||||
components={components}
|
components={components}
|
||||||
/>
|
/>
|
||||||
) : contentToDisplay ? (
|
) : (
|
||||||
<AssistantDisplayContent
|
<AssistantDisplayContent
|
||||||
content={contentToDisplay}
|
content={contentToDisplay}
|
||||||
displaySources={displaySources}
|
displaySources={displaySources}
|
||||||
@@ -806,7 +798,7 @@ function MessageContent_({
|
|||||||
rehypePlugins={markdownRehypePlugins}
|
rehypePlugins={markdownRehypePlugins}
|
||||||
components={components}
|
components={components}
|
||||||
/>
|
/>
|
||||||
) : null}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{(isSingleRoundRag || isMultiRoundRag) && displaySources.length > 0 && (
|
{(isSingleRoundRag || isMultiRoundRag) && displaySources.length > 0 && (
|
||||||
<FrontendRagSourcesPanel
|
<FrontendRagSourcesPanel
|
||||||
@@ -905,7 +897,7 @@ function FrontendRagSourcesPanel({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SingleRoundRagCitationDialog({
|
export function SingleRoundRagCitationDialog({
|
||||||
source,
|
source,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
}: {
|
}: {
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import {
|
|||||||
} from "@/components/ai-elements/conversation";
|
} from "@/components/ai-elements/conversation";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { rememberAskTimes, useChatAskTimes } from "@/core/chat-timestamps";
|
import { rememberAskTimes, useChatAskTimes } from "@/core/chat-timestamps";
|
||||||
import { useDocumentRewritePresentation } from "@/core/document-rewrite/context";
|
import { useDocumentRewritePresentation } from "@/core/document-rewrite/context";
|
||||||
import { useI18n } from "@/core/i18n/hooks";
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
import {
|
import {
|
||||||
extractContentFromMessage,
|
extractContentFromMessage,
|
||||||
@@ -46,8 +46,8 @@ import { getQuestionAnchorId } from "../question-navigator";
|
|||||||
import { StreamingIndicator } from "../streaming-indicator";
|
import { StreamingIndicator } from "../streaming-indicator";
|
||||||
|
|
||||||
import { ReferenceBatchesContext, ThreadContext } from "./context";
|
import { ReferenceBatchesContext, ThreadContext } from "./context";
|
||||||
import { MarkdownContent } from "./markdown-content";
|
import { MarkdownContent } from "./markdown-content";
|
||||||
import { DocumentRewriteTimeline } from "./document-rewrite-timeline";
|
import { DocumentRewriteTimeline } from "./document-rewrite-timeline";
|
||||||
import { MessageGroup } from "./message-group";
|
import { MessageGroup } from "./message-group";
|
||||||
import { MessageListItem } from "./message-list-item";
|
import { MessageListItem } from "./message-list-item";
|
||||||
import {
|
import {
|
||||||
@@ -112,10 +112,10 @@ export function MessageList({
|
|||||||
onSetupWritingApprove,
|
onSetupWritingApprove,
|
||||||
onSetupWritingReject,
|
onSetupWritingReject,
|
||||||
onSetupWritingStart,
|
onSetupWritingStart,
|
||||||
researchApprovalSlot,
|
researchApprovalSlot,
|
||||||
onOpenArtifact,
|
onOpenArtifact,
|
||||||
onDownloadArtifact,
|
onDownloadArtifact,
|
||||||
}: {
|
}: {
|
||||||
className?: string;
|
className?: string;
|
||||||
contentClassName?: string;
|
contentClassName?: string;
|
||||||
fitMessageRowsToContent?: boolean;
|
fitMessageRowsToContent?: boolean;
|
||||||
@@ -138,23 +138,23 @@ export function MessageList({
|
|||||||
/** 审批卡「直接开始写作」回调:应用配置(含已确认大纲)并立即启动写作流程(仅写作页提供,模板编辑等场景缺省隐藏该按钮)。 */
|
/** 审批卡「直接开始写作」回调:应用配置(含已确认大纲)并立即启动写作流程(仅写作页提供,模板编辑等场景缺省隐藏该按钮)。 */
|
||||||
onSetupWritingStart?: (payload: SetupWritingApprovalPayload) => void | Promise<void>;
|
onSetupWritingStart?: (payload: SetupWritingApprovalPayload) => void | Promise<void>;
|
||||||
/**
|
/**
|
||||||
* 深度研究的交互卡插槽(报告配置、计划确认、失败重试):workbench 向
|
* 深度研究的交互卡插槽(报告配置、计划确认、失败重试):workbench 向
|
||||||
* displayThread 注入带 `additional_kwargs.setup_research_approval` 的虚拟 ai
|
* displayThread 注入带 `additional_kwargs.setup_research_approval` 的虚拟 ai
|
||||||
* 消息。撰写步骤和完成摘要不走此插槽,而是分别复用原生 tool-call 步骤条和
|
* 消息。撰写步骤和完成摘要不走此插槽,而是分别复用原生 tool-call 步骤条和
|
||||||
* assistant 消息行;不传该 slot 时普通对话/AI 写作零行为变化。
|
* assistant 消息行;不传该 slot 时普通对话/AI 写作零行为变化。
|
||||||
*/
|
*/
|
||||||
researchApprovalSlot?: (message: Message) => React.ReactNode;
|
researchApprovalSlot?: (message: Message) => React.ReactNode;
|
||||||
onOpenArtifact?: (filepath: string) => void;
|
onOpenArtifact?: (filepath: string) => void;
|
||||||
/** Host override for downloading virtual/session-owned files in a present-files card. */
|
/** Host override for downloading virtual/session-owned files in a present-files card. */
|
||||||
onDownloadArtifact?: (filepath: string) => void | Promise<void>;
|
onDownloadArtifact?: (filepath: string) => void | Promise<void>;
|
||||||
}) {
|
}) {
|
||||||
const baseRehypePlugins = useRehypeSplitWordsIntoSpans(thread.isLoading);
|
const baseRehypePlugins = useRehypeSplitWordsIntoSpans(thread.isLoading);
|
||||||
const rehypePlugins = useMemo(
|
const rehypePlugins = useMemo(
|
||||||
() => (extraRehypePlugins?.length ? [...baseRehypePlugins, ...extraRehypePlugins] : baseRehypePlugins),
|
() => (extraRehypePlugins?.length ? [...baseRehypePlugins, ...extraRehypePlugins] : baseRehypePlugins),
|
||||||
[baseRehypePlugins, extraRehypePlugins],
|
[baseRehypePlugins, extraRehypePlugins],
|
||||||
);
|
);
|
||||||
const updateSubtask = useUpdateSubtask();
|
const updateSubtask = useUpdateSubtask();
|
||||||
const documentRewritePresentation = useDocumentRewritePresentation();
|
const documentRewritePresentation = useDocumentRewritePresentation();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const threadContext = useContext(ThreadContext);
|
const threadContext = useContext(ThreadContext);
|
||||||
// Read-only contexts (public share, mock previews) must not offer deletion.
|
// Read-only contexts (public share, mock previews) must not offer deletion.
|
||||||
@@ -194,7 +194,7 @@ export function MessageList({
|
|||||||
: ({ ...message, content: override } as Message);
|
: ({ ...message, content: override } as Message);
|
||||||
});
|
});
|
||||||
}, [messageContentOverrides, thread.messages]);
|
}, [messageContentOverrides, thread.messages]);
|
||||||
const messages = useMemo(
|
const messages = useMemo(
|
||||||
() => {
|
() => {
|
||||||
const externalHidden = externallyHiddenMessageIds ?? new Set<string>();
|
const externalHidden = externallyHiddenMessageIds ?? new Set<string>();
|
||||||
return hiddenMessageIds.size === 0 && externalHidden.size === 0
|
return hiddenMessageIds.size === 0 && externalHidden.size === 0
|
||||||
@@ -205,9 +205,9 @@ export function MessageList({
|
|||||||
(!hiddenMessageIds.has(m.id) && !externalHidden.has(m.id)),
|
(!hiddenMessageIds.has(m.id) && !externalHidden.has(m.id)),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[allMessages, externallyHiddenMessageIds, hiddenMessageIds],
|
[allMessages, externallyHiddenMessageIds, hiddenMessageIds],
|
||||||
);
|
);
|
||||||
const documentRewriteItems = documentRewritePresentation?.forThread(threadId) ?? [];
|
const documentRewriteItems = documentRewritePresentation?.forThread(threadId) ?? [];
|
||||||
|
|
||||||
const askTimes = useChatAskTimes();
|
const askTimes = useChatAskTimes();
|
||||||
|
|
||||||
@@ -255,15 +255,15 @@ export function MessageList({
|
|||||||
const [settings] = useLocalSettings();
|
const [settings] = useLocalSettings();
|
||||||
const ragModeEnabled =
|
const ragModeEnabled =
|
||||||
settings.display.ragModeEnabled || isThreadRagModeLocked(threadId);
|
settings.display.ragModeEnabled || isThreadRagModeLocked(threadId);
|
||||||
const hasSingleRoundRag = useMemo(
|
const hasSingleRoundRag = useMemo(
|
||||||
() =>
|
() =>
|
||||||
messages.some(
|
messages.some(
|
||||||
(message) =>
|
(message) =>
|
||||||
isSingleRoundRagMessage(message) || isMultiRoundRagMessage(message),
|
isSingleRoundRagMessage(message) || isMultiRoundRagMessage(message),
|
||||||
),
|
),
|
||||||
[messages],
|
[messages],
|
||||||
);
|
);
|
||||||
const referencesEnabled = ragModeEnabled || hasSingleRoundRag;
|
const referencesEnabled = ragModeEnabled || hasSingleRoundRag;
|
||||||
const { skills } = useSkills();
|
const { skills } = useSkills();
|
||||||
const skillDisplayMap = useMemo(
|
const skillDisplayMap = useMemo(
|
||||||
() => (referencesEnabled ? buildSkillDisplayMap(skills) : new Map()),
|
() => (referencesEnabled ? buildSkillDisplayMap(skills) : new Map()),
|
||||||
@@ -322,6 +322,32 @@ export function MessageList({
|
|||||||
return map;
|
return map;
|
||||||
}, [thread.messages]);
|
}, [thread.messages]);
|
||||||
|
|
||||||
|
const resolveDisplaySources = useCallback(
|
||||||
|
(msg: Message | undefined): RuntimeDisplaySource[] => {
|
||||||
|
if (!referencesEnabled || !msg || msg.type !== "ai") return [];
|
||||||
|
const batchSources =
|
||||||
|
referenceBatches.find((batch) => batch.assistant_message_id === msg.id)
|
||||||
|
?.sources ?? [];
|
||||||
|
const frontendRagSources = msg.id
|
||||||
|
? frontendRagSourcesByAiId.get(msg.id) ?? []
|
||||||
|
: [];
|
||||||
|
if (batchSources.length > 0) return batchSources;
|
||||||
|
if (frontendRagSources.length > 0) return frontendRagSources;
|
||||||
|
return collectRuntimeDisplaySources(messages, skillDisplayMap, {
|
||||||
|
afterMessageId: findPreviousHumanMessageId(messages, msg.id),
|
||||||
|
beforeMessageId: msg.id,
|
||||||
|
includeBeforeMessage: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[
|
||||||
|
frontendRagSourcesByAiId,
|
||||||
|
messages,
|
||||||
|
referenceBatches,
|
||||||
|
referencesEnabled,
|
||||||
|
skillDisplayMap,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
// Track which clarification messages are still awaiting an answer.
|
// Track which clarification messages are still awaiting an answer.
|
||||||
// Pending = clarification tool messages that appear after the last human message
|
// Pending = clarification tool messages that appear after the last human message
|
||||||
// (no user reply has been sent for them yet).
|
// (no user reply has been sent for them yet).
|
||||||
@@ -436,47 +462,29 @@ export function MessageList({
|
|||||||
className={cn("flex size-full flex-col justify-center", className)}
|
className={cn("flex size-full flex-col justify-center", className)}
|
||||||
>
|
>
|
||||||
<ScrollToBottomOnUserMessage threadId={threadId} messages={messages} />
|
<ScrollToBottomOnUserMessage threadId={threadId} messages={messages} />
|
||||||
<ConversationContent
|
<ConversationContent
|
||||||
className={cn(
|
className={cn(
|
||||||
"mx-auto w-full max-w-(--container-width-lg)",
|
"mx-auto w-full max-w-(--container-width-lg)",
|
||||||
hasSingleRoundRag ? "gap-4 pt-4" : "gap-8 pt-12",
|
hasSingleRoundRag ? "gap-4 pt-4" : "gap-8 pt-12",
|
||||||
contentClassName,
|
contentClassName,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<MessageMetricsProvider messages={messages} isLoading={thread.isLoading}>
|
<MessageMetricsProvider messages={messages} isLoading={thread.isLoading}>
|
||||||
{groupMessages(messages, (group) => {
|
{groupMessages(messages, (group) => {
|
||||||
if (group.type === "human" || group.type === "assistant") {
|
if (group.type === "human" || group.type === "assistant") {
|
||||||
return group.messages.map((msg) => {
|
return group.messages.map((msg) => {
|
||||||
// 深度研究的报告摘要由独立 SSE 作业驱动;把它接入既有 assistant 流式状态,
|
// 深度研究的报告摘要由独立 SSE 作业驱动;把它接入既有 assistant 流式状态,
|
||||||
// 便于 Streamdown 在每次 summary_delta 到达时立即更新该普通消息行。
|
// 便于 Streamdown 在每次 summary_delta 到达时立即更新该普通消息行。
|
||||||
const messageIsLoading =
|
const messageIsLoading =
|
||||||
thread.isLoading ||
|
thread.isLoading ||
|
||||||
msg.additional_kwargs?.deep_research_summary_streaming === true ||
|
msg.additional_kwargs?.deep_research_summary_streaming === true ||
|
||||||
msg.additional_kwargs?.deep_research_follow_up_streaming === true;
|
msg.additional_kwargs?.deep_research_follow_up_streaming === true;
|
||||||
const isAssistantMessage =
|
const isAssistantMessage =
|
||||||
referencesEnabled &&
|
referencesEnabled &&
|
||||||
group.type === "assistant" &&
|
group.type === "assistant" &&
|
||||||
msg.type === "ai";
|
msg.type === "ai";
|
||||||
const batchSources = isAssistantMessage
|
|
||||||
? referenceBatches.find(
|
|
||||||
(batch) => batch.assistant_message_id === msg.id,
|
|
||||||
)?.sources ?? []
|
|
||||||
: [];
|
|
||||||
// 单轮/多轮 RAG 的参考资料来自隐藏的 tool 消息,优先于通用扫描。
|
|
||||||
const frontendRagSources =
|
|
||||||
isAssistantMessage && msg.id
|
|
||||||
? frontendRagSourcesByAiId.get(msg.id) ?? []
|
|
||||||
: [];
|
|
||||||
const displaySources = isAssistantMessage
|
const displaySources = isAssistantMessage
|
||||||
? batchSources.length > 0
|
? resolveDisplaySources(msg)
|
||||||
? batchSources
|
|
||||||
: frontendRagSources.length > 0
|
|
||||||
? frontendRagSources
|
|
||||||
: collectRuntimeDisplaySources(messages, skillDisplayMap, {
|
|
||||||
afterMessageId: findPreviousHumanMessageId(messages, msg.id),
|
|
||||||
beforeMessageId: msg.id,
|
|
||||||
includeBeforeMessage: true,
|
|
||||||
})
|
|
||||||
: [];
|
: [];
|
||||||
return (
|
return (
|
||||||
<MessageListItem
|
<MessageListItem
|
||||||
@@ -486,9 +494,9 @@ export function MessageList({
|
|||||||
msg.type === "human"
|
msg.type === "human"
|
||||||
? getQuestionAnchorId(msg, messages.indexOf(msg))
|
? getQuestionAnchorId(msg, messages.indexOf(msg))
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
message={msg}
|
message={msg}
|
||||||
isLoading={messageIsLoading}
|
isLoading={messageIsLoading}
|
||||||
threadId={threadId}
|
threadId={threadId}
|
||||||
tokenUsageEnabled={tokenUsageEnabled}
|
tokenUsageEnabled={tokenUsageEnabled}
|
||||||
displaySources={displaySources}
|
displaySources={displaySources}
|
||||||
@@ -584,8 +592,8 @@ export function MessageList({
|
|||||||
</div>
|
</div>
|
||||||
) : null;
|
) : null;
|
||||||
} else if (group.type === "assistant:research-approval") {
|
} else if (group.type === "assistant:research-approval") {
|
||||||
// 深度研究交互卡(配置/计划确认/失败重试):宿主不传渲染函数时该
|
// 深度研究交互卡(配置/计划确认/失败重试):宿主不传渲染函数时该
|
||||||
// 分组不渲染。撰写步骤和完成摘要会经过普通的 processing/assistant 分组。
|
// 分组不渲染。撰写步骤和完成摘要会经过普通的 processing/assistant 分组。
|
||||||
const message = group.messages[0];
|
const message = group.messages[0];
|
||||||
return researchApprovalSlot && message ? (
|
return researchApprovalSlot && message ? (
|
||||||
<div key={group.id} className="w-full pb-2">
|
<div key={group.id} className="w-full pb-2">
|
||||||
@@ -611,11 +619,11 @@ export function MessageList({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ArtifactFileList
|
<ArtifactFileList
|
||||||
files={files}
|
files={files}
|
||||||
threadId={threadId}
|
threadId={threadId}
|
||||||
onOpenArtifact={onOpenArtifact}
|
onOpenArtifact={onOpenArtifact}
|
||||||
onDownloadArtifact={onDownloadArtifact}
|
onDownloadArtifact={onDownloadArtifact}
|
||||||
/>
|
/>
|
||||||
<MessageTokenUsageList
|
<MessageTokenUsageList
|
||||||
enabled={tokenUsageEnabled}
|
enabled={tokenUsageEnabled}
|
||||||
isLoading={thread.isLoading}
|
isLoading={thread.isLoading}
|
||||||
@@ -683,6 +691,7 @@ export function MessageList({
|
|||||||
key={"thinking-group-" + message.id}
|
key={"thinking-group-" + message.id}
|
||||||
messages={[message]}
|
messages={[message]}
|
||||||
isLoading={thread.isLoading}
|
isLoading={thread.isLoading}
|
||||||
|
displaySources={resolveDisplaySources(message)}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -726,11 +735,15 @@ export function MessageList({
|
|||||||
message.type === "ai" &&
|
message.type === "ai" &&
|
||||||
(hasToolCalls(message) ? true : !hasContent(message)),
|
(hasToolCalls(message) ? true : !hasContent(message)),
|
||||||
);
|
);
|
||||||
|
const processingAi = [...group.messages]
|
||||||
|
.reverse()
|
||||||
|
.find((message) => message.type === "ai");
|
||||||
return (
|
return (
|
||||||
<div key={"group-" + group.id} className="w-full">
|
<div key={"group-" + group.id} className="w-full">
|
||||||
<MessageGroup
|
<MessageGroup
|
||||||
messages={group.messages}
|
messages={group.messages}
|
||||||
isLoading={thread.isLoading}
|
isLoading={thread.isLoading}
|
||||||
|
displaySources={resolveDisplaySources(processingAi)}
|
||||||
/>
|
/>
|
||||||
<MessageTokenUsageList
|
<MessageTokenUsageList
|
||||||
enabled={tokenUsageEnabled}
|
enabled={tokenUsageEnabled}
|
||||||
@@ -739,11 +752,11 @@ export function MessageList({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{documentRewriteItems.map((item) => (
|
{documentRewriteItems.map((item) => (
|
||||||
<DocumentRewriteTimeline key={item.id} item={item} />
|
<DocumentRewriteTimeline key={item.id} item={item} />
|
||||||
))}
|
))}
|
||||||
{thread.isLoading && <StreamingIndicator className="my-4" />}
|
{thread.isLoading && <StreamingIndicator className="my-4" />}
|
||||||
<div style={{ height: `${paddingBottom}px` }} />
|
<div style={{ height: `${paddingBottom}px` }} />
|
||||||
</MessageMetricsProvider>
|
</MessageMetricsProvider>
|
||||||
</ConversationContent>
|
</ConversationContent>
|
||||||
|
|||||||
Reference in new issue
Block a user