"use client"; import { BookOpenText, ChevronDown, ExternalLink, Globe2 } from "lucide-react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { type ReactNode, useCallback, useId, useState, } from "react"; import { AgentDisclosure } from "@/components/agents/agent-disclosure"; import { EASE_OUT, SPRING_LAYOUT, SPRING_SWAP } from "@/lib/ease"; import { getFaviconUrl } from "@/lib/favicon"; import { cn } from "@/lib/utils"; export interface CitationItem { id: string; title: ReactNode; domain?: ReactNode; url?: string; } export interface CitationsProps { citations: CitationItem[]; title?: ReactNode; open?: boolean; defaultOpen?: boolean; onOpenChange?: (open: boolean) => void; idPrefix?: string; className?: string; } export interface CitationProps { citationId: string; index: number; /** Must match the related Citations idPrefix. */ idPrefix: string; className?: string; } export interface CitationListProps { citations: CitationItem[]; idPrefix?: string; className?: string; } export interface CitationStackProps { citations: CitationItem[]; limit?: number; className?: string; } function citationTargetId(prefix: string, citationId: string) { return `${prefix}-${citationId.replace(/[^a-zA-Z0-9_-]/g, "-")}`; } export function Citation({ citationId, index, idPrefix, className, }: CitationProps) { return ( {index} ); } export function CitationFavicon({ url, className, }: { url?: string; className?: string; }) { const favicon = url ? getFaviconUrl(url) : null; const [failedUrl, setFailedUrl] = useState(null); return ( ); } export function CitationStack({ citations, limit = 3, className, }: CitationStackProps) { return ( ); } function CitationRow({ citation, index, idPrefix, }: { citation: CitationItem; index: number; idPrefix: string; }) { const content = ( <> {citation.title} {citation.domain ? ( {citation.domain} ) : null} {index} {citation.url ? ( ) : null} ); const className = "group/citation flex items-center gap-2 rounded-md px-1.5 py-1 outline-none focus-visible:ring-2 focus-visible:ring-ring"; const id = citationTargetId(idPrefix, citation.id); return citation.url ? ( {content} ) : (
{content}
); } export function CitationList({ citations, idPrefix, className, }: CitationListProps) { const reduce = useReducedMotion() ?? false; const baseId = useId(); const resolvedPrefix = idPrefix ?? `citation-list-${baseId.replace(/:/g, "")}`; return (
{citations.map((citation, index) => ( ))}
); } export function Citations({ citations, title = "Sources", open, defaultOpen = false, onOpenChange, idPrefix, className, }: CitationsProps) { const reduce = useReducedMotion() ?? false; const baseId = useId(); const contentId = `${baseId}-content`; const resolvedPrefix = idPrefix ?? `citation-${baseId.replace(/:/g, "")}`; const [internalOpen, setInternalOpen] = useState(defaultOpen); const currentOpen = open ?? internalOpen; const setOpen = useCallback( (next: boolean) => { if (open === undefined) setInternalOpen(next); onOpenChange?.(next); }, [onOpenChange, open], ); return (
); }