"use client"; import { useCallback, useState } from "react"; interface Citation { index: number; document_id: string; chunk_id: string; document_uri: string; document_title: string | null; page_numbers: number[]; headings: string[] | null; content: string; } interface CitationBlockProps { citations: Citation[]; } interface VisualGroundingState { isOpen: boolean; chunkId: string | null; images: string[]; loading: boolean; error: string | null; } function CitationItem({ citation, onViewInDocument, }: { citation: Citation; onViewInDocument: (chunkId: string) => void; }) { const [expanded, setExpanded] = useState(false); const title = citation.document_title || citation.document_uri || "Unknown"; const pageInfo = citation.page_numbers.length > 0 ? `p. ${citation.page_numbers.join(", ")}` : null; return (
{expanded && (
{citation.headings && citation.headings.length > 0 && (
{citation.headings.join(" › ")}
)}
{citation.content}
)}
); } export default function CitationBlock({ citations }: CitationBlockProps) { const [visualGrounding, setVisualGrounding] = useState({ isOpen: false, chunkId: null, images: [], loading: false, error: null, }); const fetchVisualGrounding = useCallback(async (chunkId: string) => { setVisualGrounding({ isOpen: true, chunkId, images: [], loading: true, error: null, }); try { const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || ""; const response = await fetch(`${backendUrl}/api/visualize/${chunkId}`); const data = await response.json(); if (!response.ok) { throw new Error(data.error || "Failed to fetch visual grounding"); } setVisualGrounding((prev) => ({ ...prev, images: data.images || [], loading: false, error: data.images?.length === 0 ? data.message : null, })); } catch (err) { setVisualGrounding((prev) => ({ ...prev, loading: false, error: err instanceof Error ? err.message : "Unknown error", })); } }, []); const closeVisualGrounding = useCallback(() => { setVisualGrounding({ isOpen: false, chunkId: null, images: [], loading: false, error: null, }); }, []); if (!citations || citations.length === 0) { return null; } return ( <>
Sources ({citations.length})
{citations.map((citation) => ( ))}
{visualGrounding.isOpen && (
e.key === "Escape" && closeVisualGrounding()} role="dialog" aria-modal="true" aria-label="Visual grounding" > {/* biome-ignore lint/a11y/noStaticElementInteractions: modal content wrapper */}
e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()} >

Visual Grounding

{visualGrounding.loading && (
Loading...
)} {visualGrounding.error && (
{visualGrounding.error}
)} {!visualGrounding.loading && !visualGrounding.error && visualGrounding.images.length > 0 && (
{visualGrounding.images.map((img, idx) => ( // biome-ignore lint/suspicious/noArrayIndexKey: images have no stable id
Page {idx + 1} of {visualGrounding.images.length}
{/* biome-ignore lint/performance/noImgElement: base64 data URLs require img element */} {`Page
))}
)}
)} ); }