/** * ExtractSection — the interactive core. Wires Dropzone <-> useExtract <-> * ResultsPanel. The parent (App) needs to know when we're extracting so the * hero's 3D paper can respond, so we lift the state up via `onStateChange`. */ import { useEffect, useState } from "react"; import { motion } from "motion/react"; import { useExtract } from "@/hooks/useExtract"; import type { DocType } from "@/types"; import type { PaperState } from "./PaperScene"; import { Dropzone } from "./Dropzone"; import { ResultsPanel } from "./ResultsPanel"; interface Props { onStateChange: (s: PaperState) => void; bindExtract: (fn: () => void) => void; } export function ExtractSection({ onStateChange, bindExtract }: Props) { const [file, setFile] = useState(null); const [docType, setDocType] = useState("receipt"); const { status, response, error, run, reset } = useExtract(); const busy = status === "loading"; // Bubble status up so hero's 3D paper can react. useEffect(() => { if (status === "loading") onStateChange("extracting"); else if (status === "success") onStateChange("extracted"); else onStateChange("idle"); }, [status, onStateChange]); const doExtract = () => { if (!file) return; run({ file, docType }); }; // The hero's "Try a sample" CTA scrolls here + focuses the browse. Expose // that hook to the parent via bindExtract. useEffect(() => { bindExtract(() => { const el = document.getElementById("extract"); el?.scrollIntoView({ behavior: "smooth", block: "start" }); }); }, [bindExtract]); return (

The workbench

Try it on a document.

{response && ( )}
{ // If a sample was picked, kick off extraction automatically. setTimeout(doExtract, 200); }} busy={busy} />
); }