import { useState } from 'react' import { motion, AnimatePresence } from 'framer-motion' import { useAuth } from '@clerk/clerk-react' import { API_BASE } from '../config.js' async function* parseSseStream(response) { const reader = response.body.getReader() const decoder = new TextDecoder() let buffer = '' while (true) { const { done, value } = await reader.read() if (done) break buffer += decoder.decode(value, { stream: true }) const lines = buffer.split('\n') buffer = lines.pop() ?? '' let eventType = null for (const line of lines) { if (line.startsWith('event: ')) eventType = line.slice(7).trim() else if (line.startsWith('data: ')) { const raw = line.slice(6).trim() if (raw) { try { yield { type: eventType, data: JSON.parse(raw) } } catch {} } eventType = null } } } } export default function DocumentSummaryCard({ doc, cachedSummary, onSummaryReady }) { const { getToken } = useAuth() const [isOpen, setIsOpen] = useState(false) const [summary, setSummary] = useState(cachedSummary || null) const [streaming, setStreaming] = useState('') const [status, setStatus] = useState('idle') const [error, setError] = useState('') const summarize = async (e) => { e.stopPropagation() setStatus('generating') setStreaming('') setError('') setIsOpen(true) try { const token = await getToken() const res = await fetch(`${API_BASE}/document/summarize`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}), }, body: JSON.stringify({ source: doc.name }), }) if (!res.ok) throw new Error(`Server error ${res.status}`) for await (const event of parseSseStream(res)) { if (event.type === 'token') setStreaming(prev => prev + event.data.content) else if (event.type === 'done') { setSummary(event.data.entry) setStreaming('') setStatus('done') onSummaryReady?.(doc.name, event.data.entry) } else if (event.type === 'error') throw new Error(event.data.message) } } catch (err) { setError(err.message) setStatus('error') } } const isGenerating = status === 'generating' const hasSummary = !!summary return (
setIsOpen(v => !v)}>

{doc.name}

{doc.chunks} chunk{doc.chunks !== 1 ? 's' : ''} {hasSummary && Summary ready}

{isGenerating ? ( ) : ( )} {hasSummary ? 'Re-summarize' : 'Summarize'}
{isOpen && (
{isGenerating && streaming && (

{streaming}

)} {!hasSummary && !isGenerating && !streaming && (

Click Summarize to generate an AI summary for this document.

)} {hasSummary && !isGenerating && (

{summary.summary}

{summary.topics?.length > 0 && (
{summary.topics.map((t, i) => ( {t} ))}
)}
)} {error &&

{error}

}
)}
) }