cef8d389fd
- 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 改为文件内私有,不再导出。
86 lines
2.4 KiB
TypeScript
86 lines
2.4 KiB
TypeScript
"use client";
|
|
|
|
import type { ImgHTMLAttributes } from "react";
|
|
import type { ReactNode } from "react";
|
|
import { useMemo } from "react";
|
|
|
|
import {
|
|
CitationsLoadingIndicator,
|
|
createCitationMarkdownComponents,
|
|
} from "@/components/ai-elements/inline-citation";
|
|
import {
|
|
MessageResponse,
|
|
type MessageResponseProps,
|
|
} from "@/components/ai-elements/message";
|
|
import {
|
|
shouldShowCitationLoading,
|
|
useParsedCitations,
|
|
type UseParsedCitationsResult,
|
|
} from "@/core/citations";
|
|
import { streamdownPlugins } from "@/core/streamdown";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
export type SafeCitationContentProps = {
|
|
content: string;
|
|
isLoading: boolean;
|
|
rehypePlugins: MessageResponseProps["rehypePlugins"];
|
|
className?: string;
|
|
remarkPlugins?: MessageResponseProps["remarkPlugins"];
|
|
isHuman?: boolean;
|
|
img?: (props: ImgHTMLAttributes<HTMLImageElement> & { threadId?: string; maxWidth?: string }) => ReactNode;
|
|
/** When true, only show loading indicator or null (e.g. write_file step). */
|
|
loadingOnly?: boolean;
|
|
/** When set, use instead of default MessageResponse (e.g. artifact preview). */
|
|
renderBody?: (parsed: UseParsedCitationsResult) => ReactNode;
|
|
};
|
|
|
|
/** Single place for citation-aware body: shows loading until citations complete (no half-finished refs), else body. */
|
|
export function SafeCitationContent({
|
|
content,
|
|
isLoading,
|
|
rehypePlugins,
|
|
className,
|
|
remarkPlugins = streamdownPlugins.remarkPlugins,
|
|
isHuman = false,
|
|
img,
|
|
loadingOnly = false,
|
|
renderBody,
|
|
}: SafeCitationContentProps) {
|
|
const parsed = useParsedCitations(content);
|
|
const { citations, cleanContent, citationMap } = parsed;
|
|
const showLoading = shouldShowCitationLoading(content, cleanContent, isLoading);
|
|
const components = useMemo(
|
|
() =>
|
|
createCitationMarkdownComponents({
|
|
citationMap,
|
|
isHuman,
|
|
isLoadingCitations: false,
|
|
img,
|
|
}),
|
|
[citationMap, isHuman, img],
|
|
);
|
|
|
|
if (showLoading) {
|
|
return (
|
|
<CitationsLoadingIndicator
|
|
citations={citations}
|
|
className={cn("my-2", className)}
|
|
/>
|
|
);
|
|
}
|
|
if (loadingOnly) return null;
|
|
if (renderBody) return renderBody(parsed);
|
|
if (!cleanContent) return null;
|
|
|
|
return (
|
|
<MessageResponse
|
|
className={className}
|
|
remarkPlugins={remarkPlugins}
|
|
rehypePlugins={rehypePlugins}
|
|
components={components}
|
|
>
|
|
{cleanContent}
|
|
</MessageResponse>
|
|
);
|
|
}
|