Files
deer-flow/frontend/src/components/workspace/artifacts/artifact-file-detail.tsx

336 lines
10 KiB
TypeScript
Raw Normal View History

2026-01-17 15:19:53 +08:00
import {
2026-01-24 23:51:11 +08:00
Code2Icon,
2026-01-17 15:19:53 +08:00
CopyIcon,
DownloadIcon,
2026-01-24 23:51:11 +08:00
EyeIcon,
LoaderIcon,
2026-01-31 11:08:27 +08:00
PackageIcon,
2026-01-17 15:19:53 +08:00
SquareArrowOutUpRightIcon,
XIcon,
} from "lucide-react";
import * as React from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
2026-01-17 15:09:44 +08:00
import { toast } from "sonner";
2026-01-19 19:41:46 +08:00
import { Streamdown } from "streamdown";
2026-01-17 00:05:19 +08:00
2026-01-17 15:09:44 +08:00
import {
Artifact,
ArtifactAction,
ArtifactActions,
ArtifactContent,
ArtifactHeader,
ArtifactTitle,
} from "@/components/ai-elements/artifact";
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 保持内部使用; 在文件头注释中说明展示规则。
2026-02-09 15:01:51 +08:00
import { createCitationMarkdownComponents } from "@/components/ai-elements/inline-citation";
import { Select, SelectItem } from "@/components/ui/select";
import {
SelectContent,
SelectGroup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
2026-01-19 19:41:46 +08:00
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
2026-01-21 10:46:43 +08:00
import { CodeEditor } from "@/components/workspace/code-editor";
2026-01-17 15:09:44 +08:00
import { useArtifactContent } from "@/core/artifacts/hooks";
import { urlOfArtifact } from "@/core/artifacts/utils";
import type { Citation } from "@/core/citations";
2026-01-30 16:41:18 +08:00
import {
Merge upstream/experimental and resolve conflicts; citations + path_utils + mode-hover ## 冲突解决 (Resolve conflicts) - input-box.tsx: 保留 ModeHoverGuide 包裹的模式选择器(PR #26 的 mode-hover-guide) - message-group.tsx: 保留 getCleanContent / hasCitationsBlock / useParsedCitations - message-list-item.tsx: 保留 useParsedCitations,移除重复的 MessageLink(使用 CitationAwareLink) - artifact-file-detail.tsx: 保留 CitationAwareLink、useParsedCitations、contentWithoutCitationsFromParsed - artifacts.py: 保留 path_utils 与 _extract_citation_urls + remove_citations_block 精简实现 - citations/index.ts: 保留并补充 contentWithoutCitationsFromParsed 导出 - en-US.ts: 保留 Ultra 模式描述 "Reasoning, planning and execution with subagents..." - zh-CN.ts: 保留「超级」标签,描述保留「思考、计划并执行,可调用子代理分工协作...」 ## PR #26 代码改动汇总 ### 1. Citations(引用) - lead_agent prompt: 增加 Web search 与子代理合成时的 citation 提示 - general_purpose: 子代理 system prompt 增加 <citations_format> 说明 - frontend utils: 新增 contentWithoutCitationsFromParsed,removeAllCitations 基于单次解析 - frontend artifact: 使用 contentWithoutCitationsFromParsed(parsed) 避免对同一内容解析两次 - backend artifacts: _extract_citation_urls + remove_citations_block,json 提到顶部 ### 2. path_utils(路径解析) - 新增 backend/src/gateway/path_utils.py:resolve_thread_virtual_path,防 path traversal - artifacts.py / skills.py:删除内联路径解析,统一使用 path_utils ### 3. Mode hover guide - input-box: 模式选择器外包 ModeHoverGuide,悬停展示模式说明 ### 4. i18n - en: ultraModeDescription 与 zh: ultraMode / ultraModeDescription 与上游对齐并保留 PR 文案 Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 13:01:01 +08:00
contentWithoutCitationsFromParsed,
removeAllCitations,
Merge upstream/experimental and resolve conflicts; citations + path_utils + mode-hover ## 冲突解决 (Resolve conflicts) - input-box.tsx: 保留 ModeHoverGuide 包裹的模式选择器(PR #26 的 mode-hover-guide) - message-group.tsx: 保留 getCleanContent / hasCitationsBlock / useParsedCitations - message-list-item.tsx: 保留 useParsedCitations,移除重复的 MessageLink(使用 CitationAwareLink) - artifact-file-detail.tsx: 保留 CitationAwareLink、useParsedCitations、contentWithoutCitationsFromParsed - artifacts.py: 保留 path_utils 与 _extract_citation_urls + remove_citations_block 精简实现 - citations/index.ts: 保留并补充 contentWithoutCitationsFromParsed 导出 - en-US.ts: 保留 Ultra 模式描述 "Reasoning, planning and execution with subagents..." - zh-CN.ts: 保留「超级」标签,描述保留「思考、计划并执行,可调用子代理分工协作...」 ## PR #26 代码改动汇总 ### 1. Citations(引用) - lead_agent prompt: 增加 Web search 与子代理合成时的 citation 提示 - general_purpose: 子代理 system prompt 增加 <citations_format> 说明 - frontend utils: 新增 contentWithoutCitationsFromParsed,removeAllCitations 基于单次解析 - frontend artifact: 使用 contentWithoutCitationsFromParsed(parsed) 避免对同一内容解析两次 - backend artifacts: _extract_citation_urls + remove_citations_block,json 提到顶部 ### 2. path_utils(路径解析) - 新增 backend/src/gateway/path_utils.py:resolve_thread_virtual_path,防 path traversal - artifacts.py / skills.py:删除内联路径解析,统一使用 path_utils ### 3. Mode hover guide - input-box: 模式选择器外包 ModeHoverGuide,悬停展示模式说明 ### 4. i18n - en: ultraModeDescription 与 zh: ultraMode / ultraModeDescription 与上游对齐并保留 PR 文案 Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 13:01:01 +08:00
useParsedCitations,
2026-01-30 16:41:18 +08:00
} from "@/core/citations";
2026-01-20 14:06:47 +08:00
import { useI18n } from "@/core/i18n/hooks";
import { installSkill } from "@/core/skills/api";
2026-01-30 16:41:18 +08:00
import { streamdownPlugins } from "@/core/streamdown";
import { checkCodeFile, getFileName } from "@/core/utils/files";
2026-02-01 10:55:08 +08:00
import { env } from "@/env";
import { cn } from "@/lib/utils";
2026-01-17 00:05:19 +08:00
2026-02-01 10:55:08 +08:00
import { Tooltip } from "../tooltip";
refactor(frontend): consolidate citation logic, slim exports and impl - SafeCitationContent: add loadingOnly and renderBody props. - loadingOnly: show only loading indicator or null (e.g. write_file step). - renderBody(parsed): custom body renderer (e.g. artifact preview). - message-group write_file: use SafeCitationContent(content, isLoading, rehypePlugins, loadingOnly) instead of local useParsedCitations + shouldShowCitationLoading + CitationsLoadingIndicator. Pass rehypePlugins into ToolCall. - artifact-file-detail markdown preview: use SafeCitationContent with renderBody((p) => <ArtifactFilePreview ... cleanContent={p.cleanContent} citationMap={p.citationMap} />). Remove local shouldShowCitationLoading and CitationsLoadingIndicator branch. - core/citations: inline buildCitationMap into use-parsed-citations, remove from utils; stop exporting hasCitationsBlock (internal to shouldShowCitationLoading). - inline-citation: make InlineCitationCard, InlineCitationCardBody, InlineCitationSource file-private (no longer exported). Co-authored-by: Cursor <cursoragent@cursor.com> --- refactor(前端): 收拢引用逻辑、精简导出与实现 - SafeCitationContent 新增 loadingOnly、renderBody。 - loadingOnly:仅显示加载或 null(如 write_file 步骤)。 - renderBody(parsed):自定义正文渲染(如 artifact 预览)。 - message-group write_file:改用 SafeCitationContent(loadingOnly),去掉 本地 useParsedCitations + shouldShowCitationLoading + CitationsLoadingIndicator, 并向 ToolCall 传入 rehypePlugins。 - artifact-file-detail 的 markdown 预览:改用 SafeCitationContent + renderBody 渲染 ArtifactFilePreview,去掉本地加载判断与 CitationsLoadingIndicator 分支。 - core/citations:buildCitationMap 内联到 use-parsed-citations 并从 utils 删除;hasCitationsBlock 不再导出(仅 shouldShowCitationLoading 内部使用)。 - inline-citation:InlineCitationCard/Body/Source 改为文件内私有,不再导出。
2026-02-09 15:58:59 +08:00
import { SafeCitationContent } from "../messages/safe-citation-content";
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 保持内部使用; 在文件头注释中说明展示规则。
2026-02-09 15:01:51 +08:00
import { useThread } from "../messages/context";
2026-01-17 15:09:44 +08:00
import { useArtifacts } from "./context";
2026-01-17 11:02:33 +08:00
export function ArtifactFileDetail({
className,
2026-01-18 17:13:15 +08:00
filepath: filepathFromProps,
2026-01-17 15:09:44 +08:00
threadId,
2026-01-17 11:02:33 +08:00
}: {
className?: string;
filepath: string;
2026-01-17 15:09:44 +08:00
threadId: string;
2026-01-17 11:02:33 +08:00
}) {
2026-01-20 14:06:47 +08:00
const { t } = useI18n();
const { artifacts, setOpen, select } = useArtifacts();
2026-01-18 17:13:15 +08:00
const isWriteFile = useMemo(() => {
return filepathFromProps.startsWith("write-file:");
}, [filepathFromProps]);
const filepath = useMemo(() => {
if (isWriteFile) {
const url = new URL(filepathFromProps);
2026-01-18 20:26:01 +08:00
return decodeURIComponent(url.pathname);
2026-01-18 17:13:15 +08:00
}
return filepathFromProps;
}, [filepathFromProps, isWriteFile]);
const isSkillFile = useMemo(() => {
return filepath.endsWith(".skill");
}, [filepath]);
2026-01-19 19:41:46 +08:00
const { isCodeFile, language } = useMemo(() => {
2026-01-18 17:13:15 +08:00
if (isWriteFile) {
let language = checkCodeFile(filepath).language;
2026-01-19 19:41:46 +08:00
language ??= "text";
2026-01-18 17:13:15 +08:00
return { isCodeFile: true, language };
}
// Treat .skill files as markdown (they contain SKILL.md)
if (isSkillFile) {
return { isCodeFile: true, language: "markdown" };
}
2026-01-18 17:13:15 +08:00
return checkCodeFile(filepath);
}, [filepath, isWriteFile, isSkillFile]);
2026-01-19 19:41:46 +08:00
const previewable = useMemo(() => {
return (language === "html" && !isWriteFile) || language === "markdown";
}, [isWriteFile, language]);
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 保持内部使用; 在文件头注释中说明展示规则。
2026-02-09 15:01:51 +08:00
const { thread } = useThread();
2026-01-17 15:09:44 +08:00
const { content } = useArtifactContent({
threadId,
2026-01-18 17:13:15 +08:00
filepath: filepathFromProps,
enabled: isCodeFile && !isWriteFile,
2026-01-17 15:09:44 +08:00
});
2026-01-30 16:41:18 +08:00
const parsed = useParsedCitations(
language === "markdown" ? (content ?? "") : "",
);
const cleanContent =
language === "markdown" && content ? parsed.cleanContent : (content ?? "");
Merge upstream/experimental and resolve conflicts; citations + path_utils + mode-hover ## 冲突解决 (Resolve conflicts) - input-box.tsx: 保留 ModeHoverGuide 包裹的模式选择器(PR #26 的 mode-hover-guide) - message-group.tsx: 保留 getCleanContent / hasCitationsBlock / useParsedCitations - message-list-item.tsx: 保留 useParsedCitations,移除重复的 MessageLink(使用 CitationAwareLink) - artifact-file-detail.tsx: 保留 CitationAwareLink、useParsedCitations、contentWithoutCitationsFromParsed - artifacts.py: 保留 path_utils 与 _extract_citation_urls + remove_citations_block 精简实现 - citations/index.ts: 保留并补充 contentWithoutCitationsFromParsed 导出 - en-US.ts: 保留 Ultra 模式描述 "Reasoning, planning and execution with subagents..." - zh-CN.ts: 保留「超级」标签,描述保留「思考、计划并执行,可调用子代理分工协作...」 ## PR #26 代码改动汇总 ### 1. Citations(引用) - lead_agent prompt: 增加 Web search 与子代理合成时的 citation 提示 - general_purpose: 子代理 system prompt 增加 <citations_format> 说明 - frontend utils: 新增 contentWithoutCitationsFromParsed,removeAllCitations 基于单次解析 - frontend artifact: 使用 contentWithoutCitationsFromParsed(parsed) 避免对同一内容解析两次 - backend artifacts: _extract_citation_urls + remove_citations_block,json 提到顶部 ### 2. path_utils(路径解析) - 新增 backend/src/gateway/path_utils.py:resolve_thread_virtual_path,防 path traversal - artifacts.py / skills.py:删除内联路径解析,统一使用 path_utils ### 3. Mode hover guide - input-box: 模式选择器外包 ModeHoverGuide,悬停展示模式说明 ### 4. i18n - en: ultraModeDescription 与 zh: ultraMode / ultraModeDescription 与上游对齐并保留 PR 文案 Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 13:01:01 +08:00
const contentWithoutCitations =
language === "markdown" && content
? contentWithoutCitationsFromParsed(parsed)
: (content ?? "");
2026-01-19 19:41:46 +08:00
const [viewMode, setViewMode] = useState<"code" | "preview">("code");
const [isInstalling, setIsInstalling] = useState(false);
2026-01-19 19:41:46 +08:00
useEffect(() => {
if (previewable) {
setViewMode("preview");
} else {
setViewMode("code");
}
}, [previewable]);
const handleInstallSkill = useCallback(async () => {
if (isInstalling) return;
setIsInstalling(true);
try {
const result = await installSkill({
thread_id: threadId,
path: filepath,
});
if (result.success) {
toast.success(result.message);
} else {
2026-02-01 10:55:08 +08:00
toast.error(result.message ?? "Failed to install skill");
}
} catch (error) {
console.error("Failed to install skill:", error);
toast.error("Failed to install skill");
} finally {
setIsInstalling(false);
}
}, [threadId, filepath, isInstalling]);
2026-01-17 00:02:03 +08:00
return (
2026-01-21 08:50:15 +08:00
<Artifact className={cn(className)}>
<ArtifactHeader className="px-2">
2026-01-19 19:41:46 +08:00
<div className="flex items-center gap-2">
<ArtifactTitle>
2026-01-18 17:13:15 +08:00
{isWriteFile ? (
<div className="px-2">{getFileName(filepath)}</div>
) : (
<Select value={filepath} onValueChange={select}>
<SelectTrigger className="border-none bg-transparent! shadow-none select-none focus:outline-0 active:outline-0">
<SelectValue placeholder="Select a file" />
</SelectTrigger>
<SelectContent className="select-none">
<SelectGroup>
{(artifacts ?? []).map((filepath) => (
<SelectItem key={filepath} value={filepath}>
{getFileName(filepath)}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
)}
</ArtifactTitle>
2026-01-17 15:09:44 +08:00
</div>
2026-01-19 19:41:46 +08:00
<div className="flex min-w-0 grow items-center justify-center">
{previewable && (
<ToggleGroup
className="mx-auto"
type="single"
variant="outline"
size="sm"
value={viewMode}
onValueChange={(value) =>
setViewMode(value as "code" | "preview")
}
>
2026-01-25 00:06:49 +08:00
<ToggleGroupItem value="code">
<Code2Icon />
</ToggleGroupItem>
<ToggleGroupItem value="preview">
<EyeIcon />
</ToggleGroupItem>
2026-01-19 19:41:46 +08:00
</ToggleGroup>
)}
</div>
2026-01-17 15:09:44 +08:00
<div className="flex items-center gap-2">
<ArtifactActions>
2026-01-31 11:08:27 +08:00
{!isWriteFile && filepath.endsWith(".skill") && (
2026-02-01 10:55:08 +08:00
<Tooltip content={t.toolCalls.skillInstallTooltip}>
<ArtifactAction
icon={isInstalling ? LoaderIcon : PackageIcon}
label={t.common.install}
tooltip={t.common.install}
disabled={
isInstalling ||
env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true"
}
onClick={handleInstallSkill}
/>
</Tooltip>
2026-01-31 11:08:27 +08:00
)}
2026-01-18 17:13:15 +08:00
{!isWriteFile && (
<a href={urlOfArtifact({ filepath, threadId })} target="_blank">
<ArtifactAction
icon={SquareArrowOutUpRightIcon}
2026-01-20 14:06:47 +08:00
label={t.common.openInNewWindow}
tooltip={t.common.openInNewWindow}
2026-01-18 17:13:15 +08:00
/>
</a>
)}
2026-01-17 15:09:44 +08:00
{isCodeFile && (
<ArtifactAction
icon={CopyIcon}
2026-01-20 14:06:47 +08:00
label={t.clipboard.copyToClipboard}
2026-01-17 15:09:44 +08:00
disabled={!content}
onClick={async () => {
try {
await navigator.clipboard.writeText(contentWithoutCitations ?? "");
2026-01-20 14:06:47 +08:00
toast.success(t.clipboard.copiedToClipboard);
2026-01-17 15:09:44 +08:00
} catch (error) {
toast.error("Failed to copy to clipboard");
console.error(error);
}
}}
2026-01-20 14:06:47 +08:00
tooltip={t.clipboard.copyToClipboard}
2026-01-17 15:09:44 +08:00
/>
)}
2026-01-18 17:13:15 +08:00
{!isWriteFile && (
<a
href={urlOfArtifact({ filepath, threadId, download: true })}
target="_blank"
>
<ArtifactAction
icon={DownloadIcon}
2026-01-20 14:06:47 +08:00
label={t.common.download}
tooltip={t.common.download}
2026-01-18 17:13:15 +08:00
/>
</a>
)}
2026-01-17 15:09:44 +08:00
<ArtifactAction
icon={XIcon}
2026-01-20 14:06:47 +08:00
label={t.common.close}
2026-01-17 15:09:44 +08:00
onClick={() => setOpen(false)}
2026-01-20 14:06:47 +08:00
tooltip={t.common.close}
2026-01-17 15:09:44 +08:00
/>
</ArtifactActions>
2026-01-17 00:02:03 +08:00
</div>
2026-01-17 15:09:44 +08:00
</ArtifactHeader>
<ArtifactContent className="p-0">
refactor(frontend): consolidate citation logic, slim exports and impl - SafeCitationContent: add loadingOnly and renderBody props. - loadingOnly: show only loading indicator or null (e.g. write_file step). - renderBody(parsed): custom body renderer (e.g. artifact preview). - message-group write_file: use SafeCitationContent(content, isLoading, rehypePlugins, loadingOnly) instead of local useParsedCitations + shouldShowCitationLoading + CitationsLoadingIndicator. Pass rehypePlugins into ToolCall. - artifact-file-detail markdown preview: use SafeCitationContent with renderBody((p) => <ArtifactFilePreview ... cleanContent={p.cleanContent} citationMap={p.citationMap} />). Remove local shouldShowCitationLoading and CitationsLoadingIndicator branch. - core/citations: inline buildCitationMap into use-parsed-citations, remove from utils; stop exporting hasCitationsBlock (internal to shouldShowCitationLoading). - inline-citation: make InlineCitationCard, InlineCitationCardBody, InlineCitationSource file-private (no longer exported). Co-authored-by: Cursor <cursoragent@cursor.com> --- refactor(前端): 收拢引用逻辑、精简导出与实现 - SafeCitationContent 新增 loadingOnly、renderBody。 - loadingOnly:仅显示加载或 null(如 write_file 步骤)。 - renderBody(parsed):自定义正文渲染(如 artifact 预览)。 - message-group write_file:改用 SafeCitationContent(loadingOnly),去掉 本地 useParsedCitations + shouldShowCitationLoading + CitationsLoadingIndicator, 并向 ToolCall 传入 rehypePlugins。 - artifact-file-detail 的 markdown 预览:改用 SafeCitationContent + renderBody 渲染 ArtifactFilePreview,去掉本地加载判断与 CitationsLoadingIndicator 分支。 - core/citations:buildCitationMap 内联到 use-parsed-citations 并从 utils 删除;hasCitationsBlock 不再导出(仅 shouldShowCitationLoading 内部使用)。 - inline-citation:InlineCitationCard/Body/Source 改为文件内私有,不再导出。
2026-02-09 15:58:59 +08:00
{previewable &&
viewMode === "preview" &&
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 保持内部使用; 在文件头注释中说明展示规则。
2026-02-09 15:01:51 +08:00
language === "markdown" &&
refactor(frontend): consolidate citation logic, slim exports and impl - SafeCitationContent: add loadingOnly and renderBody props. - loadingOnly: show only loading indicator or null (e.g. write_file step). - renderBody(parsed): custom body renderer (e.g. artifact preview). - message-group write_file: use SafeCitationContent(content, isLoading, rehypePlugins, loadingOnly) instead of local useParsedCitations + shouldShowCitationLoading + CitationsLoadingIndicator. Pass rehypePlugins into ToolCall. - artifact-file-detail markdown preview: use SafeCitationContent with renderBody((p) => <ArtifactFilePreview ... cleanContent={p.cleanContent} citationMap={p.citationMap} />). Remove local shouldShowCitationLoading and CitationsLoadingIndicator branch. - core/citations: inline buildCitationMap into use-parsed-citations, remove from utils; stop exporting hasCitationsBlock (internal to shouldShowCitationLoading). - inline-citation: make InlineCitationCard, InlineCitationCardBody, InlineCitationSource file-private (no longer exported). Co-authored-by: Cursor <cursoragent@cursor.com> --- refactor(前端): 收拢引用逻辑、精简导出与实现 - SafeCitationContent 新增 loadingOnly、renderBody。 - loadingOnly:仅显示加载或 null(如 write_file 步骤)。 - renderBody(parsed):自定义正文渲染(如 artifact 预览)。 - message-group write_file:改用 SafeCitationContent(loadingOnly),去掉 本地 useParsedCitations + shouldShowCitationLoading + CitationsLoadingIndicator, 并向 ToolCall 传入 rehypePlugins。 - artifact-file-detail 的 markdown 预览:改用 SafeCitationContent + renderBody 渲染 ArtifactFilePreview,去掉本地加载判断与 CitationsLoadingIndicator 分支。 - core/citations:buildCitationMap 内联到 use-parsed-citations 并从 utils 删除;hasCitationsBlock 不再导出(仅 shouldShowCitationLoading 内部使用)。 - inline-citation:InlineCitationCard/Body/Source 改为文件内私有,不再导出。
2026-02-09 15:58:59 +08:00
content && (
<SafeCitationContent
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 保持内部使用; 在文件头注释中说明展示规则。
2026-02-09 15:01:51 +08:00
content={content}
refactor(frontend): consolidate citation logic, slim exports and impl - SafeCitationContent: add loadingOnly and renderBody props. - loadingOnly: show only loading indicator or null (e.g. write_file step). - renderBody(parsed): custom body renderer (e.g. artifact preview). - message-group write_file: use SafeCitationContent(content, isLoading, rehypePlugins, loadingOnly) instead of local useParsedCitations + shouldShowCitationLoading + CitationsLoadingIndicator. Pass rehypePlugins into ToolCall. - artifact-file-detail markdown preview: use SafeCitationContent with renderBody((p) => <ArtifactFilePreview ... cleanContent={p.cleanContent} citationMap={p.citationMap} />). Remove local shouldShowCitationLoading and CitationsLoadingIndicator branch. - core/citations: inline buildCitationMap into use-parsed-citations, remove from utils; stop exporting hasCitationsBlock (internal to shouldShowCitationLoading). - inline-citation: make InlineCitationCard, InlineCitationCardBody, InlineCitationSource file-private (no longer exported). Co-authored-by: Cursor <cursoragent@cursor.com> --- refactor(前端): 收拢引用逻辑、精简导出与实现 - SafeCitationContent 新增 loadingOnly、renderBody。 - loadingOnly:仅显示加载或 null(如 write_file 步骤)。 - renderBody(parsed):自定义正文渲染(如 artifact 预览)。 - message-group write_file:改用 SafeCitationContent(loadingOnly),去掉 本地 useParsedCitations + shouldShowCitationLoading + CitationsLoadingIndicator, 并向 ToolCall 传入 rehypePlugins。 - artifact-file-detail 的 markdown 预览:改用 SafeCitationContent + renderBody 渲染 ArtifactFilePreview,去掉本地加载判断与 CitationsLoadingIndicator 分支。 - core/citations:buildCitationMap 内联到 use-parsed-citations 并从 utils 删除;hasCitationsBlock 不再导出(仅 shouldShowCitationLoading 内部使用)。 - inline-citation:InlineCitationCard/Body/Source 改为文件内私有,不再导出。
2026-02-09 15:58:59 +08:00
isLoading={thread.isLoading}
rehypePlugins={streamdownPlugins.rehypePlugins}
className="flex size-full items-center justify-center p-4 my-0"
renderBody={(p) => (
<ArtifactFilePreview
filepath={filepath}
threadId={threadId}
content={content}
language={language ?? "text"}
cleanContent={p.cleanContent}
citationMap={p.citationMap}
/>
)}
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 保持内部使用; 在文件头注释中说明展示规则。
2026-02-09 15:01:51 +08:00
/>
refactor(frontend): consolidate citation logic, slim exports and impl - SafeCitationContent: add loadingOnly and renderBody props. - loadingOnly: show only loading indicator or null (e.g. write_file step). - renderBody(parsed): custom body renderer (e.g. artifact preview). - message-group write_file: use SafeCitationContent(content, isLoading, rehypePlugins, loadingOnly) instead of local useParsedCitations + shouldShowCitationLoading + CitationsLoadingIndicator. Pass rehypePlugins into ToolCall. - artifact-file-detail markdown preview: use SafeCitationContent with renderBody((p) => <ArtifactFilePreview ... cleanContent={p.cleanContent} citationMap={p.citationMap} />). Remove local shouldShowCitationLoading and CitationsLoadingIndicator branch. - core/citations: inline buildCitationMap into use-parsed-citations, remove from utils; stop exporting hasCitationsBlock (internal to shouldShowCitationLoading). - inline-citation: make InlineCitationCard, InlineCitationCardBody, InlineCitationSource file-private (no longer exported). Co-authored-by: Cursor <cursoragent@cursor.com> --- refactor(前端): 收拢引用逻辑、精简导出与实现 - SafeCitationContent 新增 loadingOnly、renderBody。 - loadingOnly:仅显示加载或 null(如 write_file 步骤)。 - renderBody(parsed):自定义正文渲染(如 artifact 预览)。 - message-group write_file:改用 SafeCitationContent(loadingOnly),去掉 本地 useParsedCitations + shouldShowCitationLoading + CitationsLoadingIndicator, 并向 ToolCall 传入 rehypePlugins。 - artifact-file-detail 的 markdown 预览:改用 SafeCitationContent + renderBody 渲染 ArtifactFilePreview,去掉本地加载判断与 CitationsLoadingIndicator 分支。 - core/citations:buildCitationMap 内联到 use-parsed-citations 并从 utils 删除;hasCitationsBlock 不再导出(仅 shouldShowCitationLoading 内部使用)。 - inline-citation:InlineCitationCard/Body/Source 改为文件内私有,不再导出。
2026-02-09 15:58:59 +08:00
)}
2026-01-19 19:41:46 +08:00
{isCodeFile && viewMode === "code" && (
2026-01-21 09:33:33 +08:00
<CodeEditor
2026-01-19 19:41:46 +08:00
className="size-full resize-none rounded-none border-none"
value={cleanContent ?? ""}
2026-01-21 09:33:33 +08:00
readonly
2026-01-19 19:41:46 +08:00
/>
)}
{!isCodeFile && (
<iframe
className="size-full"
src={urlOfArtifact({ filepath, threadId })}
/>
)}
2026-01-17 15:09:44 +08:00
</ArtifactContent>
</Artifact>
2026-01-17 00:02:03 +08:00
);
}
2026-01-19 19:41:46 +08:00
export function ArtifactFilePreview({
filepath,
threadId,
content,
language,
cleanContent,
citationMap,
2026-01-19 19:41:46 +08:00
}: {
filepath: string;
threadId: string;
content: string;
language: string;
cleanContent: string;
citationMap: Map<string, Citation>;
2026-01-19 19:41:46 +08:00
}) {
if (language === "markdown") {
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 保持内部使用; 在文件头注释中说明展示规则。
2026-02-09 15:01:51 +08:00
const components = createCitationMarkdownComponents({
citationMap,
syntheticExternal: true,
});
2026-01-19 19:41:46 +08:00
return (
<div className="size-full px-4">
<Streamdown
className="size-full"
2026-01-30 16:41:18 +08:00
{...streamdownPlugins}
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 保持内部使用; 在文件头注释中说明展示规则。
2026-02-09 15:01:51 +08:00
components={components}
>
{cleanContent ?? ""}
</Streamdown>
2026-01-19 19:41:46 +08:00
</div>
);
}
if (language === "html") {
return (
<iframe
className="size-full"
src={urlOfArtifact({ filepath, threadId })}
/>
);
}
return null;
}