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