fix(citations): render external links as badges during streaming

During streaming when citations are still loading (isLoadingCitations=true),
all external links should be rendered as badges since we don't know yet
which links are citations. After streaming completes, only links in
citationMap are rendered as badges.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ruitanglin
2026-02-06 16:09:03 +08:00
parent e9648b11cd
commit e444817c5d

View File

@@ -77,25 +77,43 @@ export function MessageListItem({
/** /**
* Custom link component that handles citations and external links * Custom link component that handles citations and external links
* Only links that are in citationMap are rendered as CitationLink badges * - During streaming (isLoadingCitations=true): all external links render as badges
* Other links (project URLs, regular links) are rendered as plain links * - After streaming: only links in citationMap render as badges
* - Human messages and non-citation links render as plain links
*/ */
function MessageLink({ function MessageLink({
href, href,
children, children,
citationMap, citationMap,
isHuman, isHuman,
isLoadingCitations,
}: React.AnchorHTMLAttributes<HTMLAnchorElement> & { }: React.AnchorHTMLAttributes<HTMLAnchorElement> & {
citationMap: Map<string, Citation>; citationMap: Map<string, Citation>;
isHuman: boolean; isHuman: boolean;
isLoadingCitations: boolean;
}) { }) {
if (!href) return <span>{children}</span>; if (!href) return <span>{children}</span>;
// Human messages always render as plain links
if (isHuman) {
return (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className="text-primary underline underline-offset-2 hover:no-underline"
>
{children}
</a>
);
}
const citation = citationMap.get(href); const citation = citationMap.get(href);
const isExternalLink = href.startsWith("http://") || href.startsWith("https://");
// Only render as CitationLink badge if it's a citation (in citationMap) // During streaming: render all external links as badges (citations not yet fully loaded)
// This ensures project URLs and regular links are not rendered as badges // After streaming: only render links in citationMap as badges
if (citation && !isHuman) { if (citation || (isLoadingCitations && isExternalLink)) {
return ( return (
<CitationLink citation={citation} href={href}> <CitationLink citation={citation} href={href}>
{children} {children}
@@ -103,7 +121,7 @@ function MessageLink({
); );
} }
// All other links (including project URLs) render as plain links // Non-citation links render as plain links
return ( return (
<a <a
href={href} href={href}
@@ -202,12 +220,12 @@ function MessageContent_({
// Shared markdown components // Shared markdown components
const markdownComponents = useMemo(() => ({ const markdownComponents = useMemo(() => ({
a: (props: React.AnchorHTMLAttributes<HTMLAnchorElement>) => ( a: (props: React.AnchorHTMLAttributes<HTMLAnchorElement>) => (
<MessageLink {...props} citationMap={citationMap} isHuman={isHuman} /> <MessageLink {...props} citationMap={citationMap} isHuman={isHuman} isLoadingCitations={isLoadingCitations} />
), ),
img: (props: React.ImgHTMLAttributes<HTMLImageElement>) => ( img: (props: React.ImgHTMLAttributes<HTMLImageElement>) => (
<MessageImage {...props} threadId={thread_id} maxWidth={isHuman ? "full" : "90%"} /> <MessageImage {...props} threadId={thread_id} maxWidth={isHuman ? "full" : "90%"} />
), ),
}), [citationMap, thread_id, isHuman]); }), [citationMap, thread_id, isHuman, isLoadingCitations]);
// Render message response // Render message response
// Human messages use humanMessagePlugins (no autolink) to prevent URL bleeding into adjacent text // Human messages use humanMessagePlugins (no autolink) to prevent URL bleeding into adjacent text