From 95dec0e72ee923333ea799d34e71101b606a6d56 Mon Sep 17 00:00:00 2001 From: xuhd Date: Thu, 11 Jun 2026 14:11:21 +0800 Subject: [PATCH] =?UTF-8?q?fix(studio):=20=E4=BF=AE=E5=A4=8D=E6=B5=81?= =?UTF-8?q?=E5=BC=8F=E8=BF=94=E5=9B=9E=E6=97=B6=E7=A9=BA=E9=97=B4=E5=BC=95?= =?UTF-8?q?=E7=94=A8=E8=A7=92=E6=A0=87=E6=B8=B2=E6=9F=93=E9=94=99=E4=B9=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Streamdown 的 parseIncompleteMarkdown 会把流式途中未闭合的 [[cite:N 当作未完成链接改写成 [[cite:N](streamdown:incomplete-link),导致 rehypeCitations 匹配失败、引用闪成坏链接。送入渲染前剥除未完成的引用 标记,待 ]] 到齐再由 rehype 渲染为上标。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../workspace/messages/message-list-item.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/frontend-web/src/components/workspace/messages/message-list-item.tsx b/frontend-web/src/components/workspace/messages/message-list-item.tsx index a1242072..37b94528 100644 --- a/frontend-web/src/components/workspace/messages/message-list-item.tsx +++ b/frontend-web/src/components/workspace/messages/message-list-item.tsx @@ -358,6 +358,20 @@ function MessageImage({ ); } +// While a notebook citation marker is still streaming in (e.g. "[[cite:64" or +// "[[cite:101]"), Streamdown's parseIncompleteMarkdown treats the trailing +// unclosed "[" as an incomplete link and rewrites it to +// "[[cite:64](streamdown:incomplete-link)", which the rehypeCitations regex can +// no longer match — so the citation flickers as a broken link mid-stream. We +// strip the half-arrived marker so it stays hidden until the closing "]]" +// arrives, at which point rehypeCitations renders it. A complete +// "[[cite:101]]" has no trailing incomplete marker, so this is a no-op. +const INCOMPLETE_CITE_TAIL = /\[\[(?:c(?:i(?:t(?:e(?::\d*\]?)?)?)?)?)?$/; + +function stripIncompleteCitationMarker(content: string): string { + return content.replace(INCOMPLETE_CITE_TAIL, ''); +} + function MessageContent_({ className, message, @@ -460,7 +474,8 @@ function MessageContent_({ ? stripArtifactTag(stripUploadedFilesTag(rawContent)) : ''; } - return applyInlineCitations(rawContent ?? '', displaySources); + const withCitations = applyInlineCitations(rawContent ?? '', displaySources); + return stripIncompleteCitationMarker(withCitations); }, [rawContent, isHuman, displaySources]); const parsedHumanContent = useMemo( () =>