'use client' import { useCallback, useState } from 'react' const KLAUSUR_API = '/klausur-api' interface LlmChange { row_index: number field: 'english' | 'german' | 'example' old: string new: string } interface LlmReviewResult { changes: LlmChange[] model_used: string duration_ms: number total_entries: number corrections_found: number } interface StepLlmReviewProps { sessionId: string | null onNext: () => void } const FIELD_LABELS: Record = { english: 'EN', german: 'DE', example: 'Beispiel', } export function StepLlmReview({ sessionId, onNext }: StepLlmReviewProps) { const [status, setStatus] = useState<'idle' | 'running' | 'done' | 'error' | 'applied'>('idle') const [result, setResult] = useState(null) const [error, setError] = useState('') const [accepted, setAccepted] = useState>(new Set()) const [applying, setApplying] = useState(false) const runReview = useCallback(async () => { if (!sessionId) return setStatus('running') setError('') setResult(null) try { const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/llm-review`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}), }) if (!res.ok) { const data = await res.json().catch(() => ({})) throw new Error(data.detail || `HTTP ${res.status}`) } const data: LlmReviewResult = await res.json() setResult(data) // Accept all changes by default setAccepted(new Set(data.changes.map((_, i) => i))) setStatus('done') } catch (e: unknown) { const msg = e instanceof Error ? e.message : String(e) setError(msg) setStatus('error') } }, [sessionId]) const toggleChange = (index: number) => { setAccepted((prev) => { const next = new Set(prev) if (next.has(index)) next.delete(index) else next.add(index) return next }) } const toggleAll = () => { if (!result) return if (accepted.size === result.changes.length) { setAccepted(new Set()) } else { setAccepted(new Set(result.changes.map((_, i) => i))) } } const applyChanges = useCallback(async () => { if (!sessionId || !result) return setApplying(true) try { const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/llm-review/apply`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ accepted_indices: Array.from(accepted) }), }) if (!res.ok) { const data = await res.json().catch(() => ({})) throw new Error(data.detail || `HTTP ${res.status}`) } setStatus('applied') } catch (e: unknown) { const msg = e instanceof Error ? e.message : String(e) setError(msg) } finally { setApplying(false) } }, [sessionId, result, accepted]) if (!sessionId) { return (
Bitte zuerst eine Session auswaehlen.
) } // --- Idle state --- if (status === 'idle') { return (
๐Ÿค–

Schritt 6: LLM-Korrektur

Ein lokales Sprachmodell prueft die OCR-Ergebnisse auf typische Erkennungsfehler (z.B. "8en" statt "Ben") und schlaegt Korrekturen vor.

Modell: qwen3:30b-a3b via Ollama (lokal)

) } // --- Running state --- if (status === 'running') { return (

Korrektur laeuft...

qwen3:30b-a3b prueft die Vokabeleintraege

) } // --- Error state --- if (status === 'error') { return (
โš ๏ธ

Fehler bei LLM-Korrektur

{error}

) } // --- Applied state --- if (status === 'applied') { return (
โœ…

Korrekturen uebernommen

{accepted.size} von {result?.changes.length ?? 0} Korrekturen wurden angewendet.

) } // --- Done state: show diff table --- const changes = result?.changes ?? [] if (changes.length === 0) { return (
๐Ÿ‘

Keine Korrekturen noetig

Das LLM hat keine OCR-Fehler gefunden.

{result?.total_entries} Eintraege geprueft in {result?.duration_ms}ms ({result?.model_used})

) } return (
{/* Header */}

LLM-Korrekturvorschlaege

{changes.length} Korrektur{changes.length !== 1 ? 'en' : ''} gefunden ยท {result?.duration_ms}ms ยท {result?.model_used}

{/* Diff table */}
{changes.map((change, idx) => ( ))}
Zeile Feld Vorher Nachher
toggleChange(idx)} className="rounded border-gray-300 dark:border-gray-600" /> R{change.row_index} {FIELD_LABELS[change.field] || change.field} {change.old} {change.new}
{/* Actions */}

{accepted.size} von {changes.length} ausgewaehlt

) }