Use when building frontend UI — document upload, results display, progress streaming, animations, or responsive layouts.
Real-time extraction status using Server-Sent Events:
// Hook pattern
function useExtractionStream(documentId: string) {
const [status, setStatus] = useState<ExtractionStatus>("idle");
const [progress, setProgress] = useState(0);
const [result, setResult] = useState<ExtractionResult | null>(null);
useEffect(() => {
const source = new EventSource(`/api/v1/documents/${documentId}/stream`);
source.addEventListener("status", (e) => setStatus(JSON.parse(e.data).stage));
source.addEventListener("progress", (e) => setProgress(JSON.parse(e.data).percent));
source.addEventListener("result", (e) => { setResult(JSON.parse(e.data)); source.close(); });
source.addEventListener("error", () => { /* reconnect logic */ });
return () => source.close();
}, [documentId]);
return { status, progress, result };
}
// Layout → Spacing → Typography → Visual
<div className="flex flex-col gap-4 p-6 text-sm font-medium bg-white rounded-lg shadow-sm border border-gray-200">
md:): two-column results layoutlg:): sidebar with document list + main results areaUse shadcn/ui for all base components:
Card — results display, document cardsButton — primary actions, file upload triggerBadge — status indicators, confidence labelsProgress — upload and extraction progress barsDialog — confirmation dialogs, detailed field viewToast — success/error notificationsSkeleton — loading placeholdersimport { motion, AnimatePresence } from "framer-motion";
// Results reveal animation
<AnimatePresence>
{result && (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
>
<ExtractionResults data={result} />
</motion.div>
)}
</AnimatePresence>
useEffectEvent(): Stable event handlers for SSE callbacks