'use client' import { useState } from 'react' import { OverlayReconstruction } from './OverlayReconstruction' import type { GridCell } from '@/app/(admin)/ai/ocr-overlay/types' const KLAUSUR_API = '/klausur-api' type Phase = 'idle' | 'running' | 'compare' interface KombiResult { cells: GridCell[] image_width: number image_height: number duration_seconds: number summary: { total_cells: number non_empty_cells: number merged_words: number [key: string]: unknown } [key: string]: unknown } interface KombiCompareStepProps { sessionId: string | null onNext: () => void } export function KombiCompareStep({ sessionId, onNext }: KombiCompareStepProps) { const [phase, setPhase] = useState('idle') const [error, setError] = useState('') const [paddleResult, setPaddleResult] = useState(null) const [rapidResult, setRapidResult] = useState(null) const [paddleStatus, setPaddleStatus] = useState<'pending' | 'running' | 'done' | 'error'>('pending') const [rapidStatus, setRapidStatus] = useState<'pending' | 'running' | 'done' | 'error'>('pending') const runBothEngines = async () => { if (!sessionId) return setPhase('running') setError('') setPaddleStatus('running') setRapidStatus('running') setPaddleResult(null) setRapidResult(null) const fetchEngine = async ( endpoint: string, setResult: (r: KombiResult) => void, setStatus: (s: 'pending' | 'running' | 'done' | 'error') => void, ) => { try { const res = await fetch( `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/${endpoint}`, { method: 'POST' }, ) if (!res.ok) { const body = await res.json().catch(() => ({})) throw new Error(body.detail || `HTTP ${res.status}`) } const data = await res.json() setResult(data) setStatus('done') } catch (e: unknown) { setStatus('error') throw e } } try { await Promise.all([ fetchEngine('paddle-kombi', setPaddleResult, setPaddleStatus), fetchEngine('rapid-kombi', setRapidResult, setRapidStatus), ]) setPhase('compare') } catch (e: unknown) { // At least one failed — still show compare if the other succeeded setError(e instanceof Error ? e.message : String(e)) setPhase('compare') } } if (phase === 'idle') { return (
⚖️

Kombi-Vergleich

Beide Kombi-Modi (Paddle + Tesseract vs. RapidOCR + Tesseract) laufen parallel. Die Ergebnisse werden nebeneinander angezeigt, damit die Qualitaet direkt verglichen werden kann.

) } if (phase === 'running' && !paddleResult && !rapidResult) { return (
) } // compare phase return (
{error && (
{error}
)}

Side-by-Side Vergleich

{/* Left: Paddle-Kombi */}
🔀 Paddle + Tesseract {paddleStatus === 'error' && ( Fehler )}
{paddleResult ? ( <> {}} wordResultOverride={paddleResult} /> ) : (
{paddleStatus === 'running' ? 'Laeuft...' : 'Fehlgeschlagen'}
)}
{/* Right: Rapid-Kombi */}
⚡ RapidOCR + Tesseract {rapidStatus === 'error' && ( Fehler )}
{rapidResult ? ( <> {}} wordResultOverride={rapidResult} /> ) : (
{rapidStatus === 'running' ? 'Laeuft...' : 'Fehlgeschlagen'}
)}
) } function EngineStatusCard({ label, status }: { label: string; status: string }) { return (
{status === 'running' && (
)} {status === 'done' && } {status === 'error' && } {status === 'pending' && } {label}
) } function StatsBar({ result, engine }: { result: KombiResult; engine: string }) { const nonEmpty = result.summary?.non_empty_cells ?? 0 const totalCells = result.summary?.total_cells ?? 0 const merged = result.summary?.merged_words ?? 0 const duration = result.duration_seconds ?? 0 return (
{engine} {merged} Woerter {nonEmpty}/{totalCells} Zellen {duration.toFixed(2)}s
) }