import { Check, Copy } from "lucide-react"; import { useRef, useState } from "react"; import { ProgressCard } from "@/components/common/progress-card"; import { useFileStore } from "@/stores/file-store"; function getToken(): string { return localStorage.getItem("stirling-token") || ""; } type OcrEngine = "tesseract" | "paddleocr"; const LANGUAGES = [ { code: "en", label: "English" }, { code: "de", label: "German" }, { code: "fr", label: "French" }, { code: "es", label: "Spanish" }, { code: "zh", label: "Chinese" }, { code: "ja", label: "Japanese" }, { code: "ko", label: "Korean" }, ]; export function OcrSettings() { const { files, processing, error, setProcessing, setError } = useFileStore(); const [engine, setEngine] = useState("tesseract"); const [language, setLanguage] = useState("en"); const [text, setText] = useState(null); const [detectedEngine, setDetectedEngine] = useState(""); const [copied, setCopied] = useState(false); const [progressPhase, setProgressPhase] = useState<"idle" | "uploading" | "processing">("idle"); const [progressPercent, setProgressPercent] = useState(0); const [progressStage, setProgressStage] = useState(); const [elapsed, setElapsed] = useState(0); const elapsedRef = useRef | null>(null); const handleProcess = async () => { if (files.length === 0) return; setError(null); setText(null); setProcessing(true); setProgressPhase("uploading"); setProgressPercent(0); setProgressStage(undefined); setElapsed(0); const startTime = Date.now(); elapsedRef.current = setInterval(() => { setElapsed(Math.floor((Date.now() - startTime) / 1000)); }, 1000); const clientJobId = crypto.randomUUID(); // Open SSE for server-side progress const es = new EventSource(`/api/v1/jobs/${clientJobId}/progress`); es.onmessage = (event) => { try { const data = JSON.parse(event.data); if (data.type === "single" && typeof data.percent === "number") { setProgressPhase("processing"); setProgressPercent(15 + (data.percent / 100) * 85); setProgressStage(data.stage); } } catch {} }; es.onerror = () => es.close(); const formData = new FormData(); formData.append("file", files[0]); formData.append("settings", JSON.stringify({ engine, language })); formData.append("clientJobId", clientJobId); const xhr = new XMLHttpRequest(); xhr.upload.onprogress = (e) => { if (e.lengthComputable) { setProgressPercent((e.loaded / e.total) * 15); } }; xhr.upload.onload = () => { setProgressPhase("processing"); setProgressPercent(15); setProgressStage("Starting..."); }; xhr.onload = () => { if (elapsedRef.current) clearInterval(elapsedRef.current); es.close(); if (xhr.status >= 200 && xhr.status < 300) { try { const data = JSON.parse(xhr.responseText); setText(data.text || ""); setDetectedEngine(data.engine || engine); } catch { setError("Invalid response"); } } else { try { const body = JSON.parse(xhr.responseText); setError(body.error || body.details || `Failed: ${xhr.status}`); } catch { setError(`Processing failed: ${xhr.status}`); } } setProcessing(false); setProgressPhase("idle"); }; xhr.onerror = () => { if (elapsedRef.current) clearInterval(elapsedRef.current); es.close(); setError("Network error"); setProcessing(false); setProgressPhase("idle"); }; xhr.open("POST", "/api/v1/tools/ocr"); xhr.setRequestHeader("Authorization", `Bearer ${getToken()}`); xhr.send(formData); }; const handleCopy = async () => { if (text) { await navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); } }; const hasFile = files.length > 0; return (
{/* Engine selector */}
{/* Language selector */}
{/* Error */} {error &&

{error}

} {/* Process button */} {processing ? ( ) : ( )} {/* Result */} {text !== null && (