mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-20 04:44:46 +08:00
feat(frontend): unify citation logic and prevent half-finished citations
- Add SafeCitationContent as single component for citation-aware body: useParsedCitations + shouldShowCitationLoading; show loading until citations complete, then render body with createCitationMarkdownComponents. Supports optional remarkPlugins, rehypePlugins, isHuman, img. - Refactor MessageListItem: assistant message body now uses SafeCitationContent only; remove duplicate useParsedCitations, shouldShowCitationLoading, createCitationMarkdownComponents and CitationsLoadingIndicator logic. Human messages keep plain AIElementMessageResponse (no citation parsing). - Use SafeCitationContent for clarification, present-files (message-list), thinking steps and write_file loading (message-group), subtask result (subtask-card). Artifact markdown preview keeps same guard (shouldShowCitationLoading) with ArtifactFilePreview. - Unify loading condition: shouldShowCitationLoading(rawContent, cleanContent, isLoading) is the single source of truth. Show loading when (isLoading && hasCitationsBlock(rawContent)) or when (hasCitationsBlock(rawContent) && hasUnreplacedCitationRefs(cleanContent)) so Pro/Ultra modes also show "loading citations" and half-finished [cite-N] never appear. - message-group write_file: replace hasCitationsBlock + threadIsLoading with shouldShowCitationLoading(fileContent, cleanContent, threadIsLoading && isLast) for consistency. - citations/utils: parse incomplete <citations> during streaming; remove isCitationsBlockIncomplete; keep hasUnreplacedCitationRefs internal; document display rule in file header. Co-authored-by: Cursor <cursoragent@cursor.com> --- feat(前端): 统一引用逻辑并杜绝半成品引用 - 新增 SafeCitationContent 作为引用正文的唯一出口:内部使用 useParsedCitations + shouldShowCitationLoading,在引用未就绪时只显示 「正在整理引用」,就绪后用 createCitationMarkdownComponents 渲染正文; 支持可选 remarkPlugins、rehypePlugins、isHuman、img。 - 重构 MessageListItem:助手消息正文仅通过 SafeCitationContent 渲染, 删除重复的 useParsedCitations、shouldShowCitationLoading、 createCitationMarkdownComponents、CitationsLoadingIndicator 等逻辑; 用户消息仍用 AIElementMessageResponse,不做引用解析。 - 澄清、present-files(message-list)、思考步骤与 write_file 加载 (message-group)、子任务结果(subtask-card)均使用 SafeCitationContent;Artifact 的 markdown 预览仍用同一 guard shouldShowCitationLoading,正文由 ArtifactFilePreview 渲染。 - 统一加载条件:shouldShowCitationLoading(rawContent, cleanContent, isLoading) 为唯一判断。在「流式中且已有引用块」或「有引用块且 cleanContent 中仍有未替换的 [cite-N]」时仅显示加载,从而在 Pro/Ultra 下也能看到「正在整理引用」,且永不出现半成品 [cite-N]。 - message-group 的 write_file:用 shouldShowCitationLoading( fileContent, cleanContent, threadIsLoading && isLast) 替代 hasCitationsBlock + threadIsLoading,与其他场景一致。 - citations/utils:流式时解析未闭合的 <citations>;移除 isCitationsBlockIncomplete;hasUnreplacedCitationRefs 保持内部使用; 在文件头注释中说明展示规则。
This commit is contained in:
@@ -21,7 +21,7 @@ import {
|
||||
ArtifactHeader,
|
||||
ArtifactTitle,
|
||||
} from "@/components/ai-elements/artifact";
|
||||
import { CitationAwareLink } from "@/components/ai-elements/inline-citation";
|
||||
import { createCitationMarkdownComponents } from "@/components/ai-elements/inline-citation";
|
||||
import { Select, SelectItem } from "@/components/ui/select";
|
||||
import {
|
||||
SelectContent,
|
||||
@@ -32,11 +32,13 @@ import {
|
||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
||||
import { CodeEditor } from "@/components/workspace/code-editor";
|
||||
import { useArtifactContent } from "@/core/artifacts/hooks";
|
||||
import { CitationsLoadingIndicator } from "@/components/ai-elements/inline-citation";
|
||||
import { urlOfArtifact } from "@/core/artifacts/utils";
|
||||
import type { Citation } from "@/core/citations";
|
||||
import {
|
||||
contentWithoutCitationsFromParsed,
|
||||
removeAllCitations,
|
||||
shouldShowCitationLoading,
|
||||
useParsedCitations,
|
||||
} from "@/core/citations";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
@@ -48,6 +50,8 @@ import { cn } from "@/lib/utils";
|
||||
|
||||
import { Tooltip } from "../tooltip";
|
||||
|
||||
import { useThread } from "../messages/context";
|
||||
|
||||
import { useArtifacts } from "./context";
|
||||
|
||||
export function ArtifactFileDetail({
|
||||
@@ -89,6 +93,7 @@ export function ArtifactFileDetail({
|
||||
const previewable = useMemo(() => {
|
||||
return (language === "html" && !isWriteFile) || language === "markdown";
|
||||
}, [isWriteFile, language]);
|
||||
const { thread } = useThread();
|
||||
const { content } = useArtifactContent({
|
||||
threadId,
|
||||
filepath: filepathFromProps,
|
||||
@@ -248,14 +253,29 @@ export function ArtifactFileDetail({
|
||||
</ArtifactHeader>
|
||||
<ArtifactContent className="p-0">
|
||||
{previewable && viewMode === "preview" && (
|
||||
<ArtifactFilePreview
|
||||
filepath={filepath}
|
||||
threadId={threadId}
|
||||
content={content}
|
||||
language={language ?? "text"}
|
||||
cleanContent={parsed.cleanContent}
|
||||
citationMap={parsed.citationMap}
|
||||
/>
|
||||
language === "markdown" &&
|
||||
content &&
|
||||
shouldShowCitationLoading(
|
||||
content,
|
||||
parsed.cleanContent,
|
||||
thread.isLoading,
|
||||
) ? (
|
||||
<div className="flex size-full items-center justify-center p-4">
|
||||
<CitationsLoadingIndicator
|
||||
citations={parsed.citations}
|
||||
className="my-0"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<ArtifactFilePreview
|
||||
filepath={filepath}
|
||||
threadId={threadId}
|
||||
content={content}
|
||||
language={language ?? "text"}
|
||||
cleanContent={parsed.cleanContent}
|
||||
citationMap={parsed.citationMap}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
{isCodeFile && viewMode === "code" && (
|
||||
<CodeEditor
|
||||
@@ -291,20 +311,16 @@ export function ArtifactFilePreview({
|
||||
citationMap: Map<string, Citation>;
|
||||
}) {
|
||||
if (language === "markdown") {
|
||||
const components = createCitationMarkdownComponents({
|
||||
citationMap,
|
||||
syntheticExternal: true,
|
||||
});
|
||||
return (
|
||||
<div className="size-full px-4">
|
||||
<Streamdown
|
||||
className="size-full"
|
||||
{...streamdownPlugins}
|
||||
components={{
|
||||
a: (props: React.AnchorHTMLAttributes<HTMLAnchorElement>) => (
|
||||
<CitationAwareLink
|
||||
{...props}
|
||||
citationMap={citationMap}
|
||||
syntheticExternal
|
||||
/>
|
||||
),
|
||||
}}
|
||||
components={components}
|
||||
>
|
||||
{cleanContent ?? ""}
|
||||
</Streamdown>
|
||||
|
||||
Reference in New Issue
Block a user