"use client"; import { Badge } from "@/components/ui/badge"; import { HoverCard, HoverCardContent, HoverCardTrigger, } from "@/components/ui/hover-card"; import { cn, externalLinkClass, externalLinkClassNoUnderline, } from "@/lib/utils"; import { ExternalLinkIcon } from "lucide-react"; import { type AnchorHTMLAttributes, type ComponentProps, type ImgHTMLAttributes, type ReactElement, type ReactNode, Children, } from "react"; import type { Citation } from "@/core/citations"; import { extractDomainFromUrl, isExternalUrl, syntheticCitationFromLink, } from "@/core/citations"; import { Shimmer } from "./shimmer"; import { useI18n } from "@/core/i18n/hooks"; export type InlineCitationCardProps = ComponentProps; export const InlineCitationCard = (props: InlineCitationCardProps) => ( ); export type InlineCitationCardBodyProps = ComponentProps<"div">; export const InlineCitationCardBody = ({ className, ...props }: InlineCitationCardBodyProps) => ( ); export type InlineCitationSourceProps = ComponentProps<"div"> & { title?: string; url?: string; description?: string; }; export const InlineCitationSource = ({ title, url, description, className, children, ...props }: InlineCitationSourceProps) => (
{title && (

{title}

)} {url && (

{url}

)} {description && (

{description}

)} {children}
); /** * Shared CitationLink component that renders a citation as a hover card badge * Used across message-list-item, artifact-file-detail, and message-group * * When citation is provided, displays title and snippet from the citation. * When citation is omitted, falls back to displaying the domain name extracted from href. */ export type CitationLinkProps = { citation?: Citation; href: string; children: React.ReactNode; }; export const CitationLink = ({ citation, href, children, }: CitationLinkProps) => { const domain = extractDomainFromUrl(href); // Priority: citation.title > children (if meaningful) > domain // - citation.title: from parsed block, most accurate // - children: from markdown link text [Text](url), used when no citation data // - domain: fallback when both above are unavailable // Skip children if it's a generic placeholder like "Source" const childrenText = typeof children === "string" ? children : null; const isGenericText = childrenText === "Source" || childrenText === "来源"; const displayText = citation?.title || (!isGenericText && childrenText) || domain; return ( e.stopPropagation()} > {displayText} ); }; /** * Renders a link with optional citation badge. Use in markdown components (message + artifact). * - citationMap: URL -> Citation; links in map render as CitationLink. * - isHuman: when true, never render as CitationLink (plain link). * - isLoadingCitations: when true and not human, non-citation links use no-underline style. * - syntheticExternal: when true, external URLs not in citationMap render as CitationLink with synthetic citation. */ export type CitationAwareLinkProps = ComponentProps<"a"> & { citationMap: Map; isHuman?: boolean; isLoadingCitations?: boolean; syntheticExternal?: boolean; }; export const CitationAwareLink = ({ href, children, citationMap, isHuman = false, isLoadingCitations = false, syntheticExternal = false, className, ...rest }: CitationAwareLinkProps) => { if (!href) return {children}; const citation = citationMap.get(href); if (citation && !isHuman) { return ( {children} ); } if (syntheticExternal && isExternalUrl(href)) { const linkText = typeof children === "string" ? children : String(Children.toArray(children).join("")).trim() || href; return ( {children} ); } const noUnderline = !isHuman && isLoadingCitations; return ( {children} ); }; /** * Options for creating markdown components that render links as citations. * Used by message list (all modes: Flash/Thinking/Pro/Ultra), artifact preview, and CoT. */ export type CreateCitationMarkdownComponentsOptions = { citationMap: Map; isHuman?: boolean; isLoadingCitations?: boolean; syntheticExternal?: boolean; /** Optional custom img component (e.g. MessageImage with threadId). Omit for artifact. */ img?: (props: ImgHTMLAttributes & { threadId?: string; maxWidth?: string }) => ReactNode; }; /** * Create markdown `components` (a, optional img) that use CitationAwareLink. * Reused across message-list-item (all modes), artifact-file-detail, and any CoT markdown. */ export function createCitationMarkdownComponents( options: CreateCitationMarkdownComponentsOptions, ): { a: (props: AnchorHTMLAttributes) => ReactElement; img?: (props: ImgHTMLAttributes & { threadId?: string; maxWidth?: string }) => ReactNode; } { const { citationMap, isHuman = false, isLoadingCitations = false, syntheticExternal = false, img, } = options; const a = (props: AnchorHTMLAttributes) => ( ); return img ? { a, img } : { a }; } /** * Shared CitationsLoadingIndicator component * Used across message-list-item and message-group to show loading citations */ export type CitationsLoadingIndicatorProps = { citations: Citation[]; className?: string; }; export const CitationsLoadingIndicator = ({ citations, className, }: CitationsLoadingIndicatorProps) => { const { t } = useI18n(); return (
{citations.length > 0 ? t.citations.loadingCitationsWithCount(citations.length) : t.citations.loadingCitations} {citations.length > 0 && (
{citations.map((citation) => ( {citation.title || extractDomainFromUrl(citation.url)} ))}
)}
); };