'use client' import { useCallback, useEffect, useState } from 'react' import type { DeskewGroundTruth, DeskewResult, SessionInfo } from '@/app/(admin)/ai/ocr-pipeline/types' import { DeskewControls } from './DeskewControls' import { ImageCompareView } from './ImageCompareView' const KLAUSUR_API = '/klausur-api' interface StepDeskewProps { sessionId: string | null onNext: () => void } export function StepDeskew({ sessionId, onNext }: StepDeskewProps) { const [session, setSession] = useState(null) const [deskewResult, setDeskewResult] = useState(null) const [deskewing, setDeskewing] = useState(false) const [applying, setApplying] = useState(false) const [showBinarized, setShowBinarized] = useState(false) const [showGrid, setShowGrid] = useState(true) const [error, setError] = useState(null) const [hasAutoRun, setHasAutoRun] = useState(false) // Load session and auto-trigger deskew useEffect(() => { if (!sessionId || session) return const loadAndDeskew = async () => { try { const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}`) if (!res.ok) return const data = await res.json() const sessionInfo: SessionInfo = { session_id: data.session_id, filename: data.filename, image_width: data.image_width, image_height: data.image_height, // Use oriented image as "before" view (deskew runs right after orientation) original_image_url: `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/image/oriented`, } setSession(sessionInfo) // If deskew result already exists, use it if (data.deskew_result) { const dr: DeskewResult = { ...data.deskew_result, deskewed_image_url: `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/image/deskewed`, binarized_image_url: `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/image/binarized`, } setDeskewResult(dr) return } // Auto-trigger deskew if not already done if (!hasAutoRun) { setHasAutoRun(true) setDeskewing(true) const deskewRes = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/deskew`, { method: 'POST', }) if (!deskewRes.ok) { throw new Error('Begradigung fehlgeschlagen') } const deskewData: DeskewResult = await deskewRes.json() deskewData.deskewed_image_url = `${KLAUSUR_API}${deskewData.deskewed_image_url}` deskewData.binarized_image_url = `${KLAUSUR_API}${deskewData.binarized_image_url}` setDeskewResult(deskewData) } } catch (e) { setError(e instanceof Error ? e.message : 'Fehler beim Laden') } finally { setDeskewing(false) } } loadAndDeskew() }, [sessionId, session, hasAutoRun]) const handleManualDeskew = useCallback(async (angle: number) => { if (!sessionId) return setApplying(true) setError(null) try { const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/deskew/manual`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ angle }), }) if (!res.ok) throw new Error('Manuelle Begradigung fehlgeschlagen') const data = await res.json() setDeskewResult((prev) => prev ? { ...prev, angle_applied: data.angle_applied, method_used: data.method_used, deskewed_image_url: `${KLAUSUR_API}${data.deskewed_image_url}?t=${Date.now()}`, } : null, ) } catch (e) { setError(e instanceof Error ? e.message : 'Fehler') } finally { setApplying(false) } }, [sessionId]) const handleGroundTruth = useCallback(async (gt: DeskewGroundTruth) => { if (!sessionId) return try { await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/ground-truth/deskew`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(gt), }) } catch (e) { console.error('Ground truth save failed:', e) } }, [sessionId]) if (!sessionId) { return
Keine Session ausgewaehlt.
} return (
{/* Filename */} {session && (
Datei: {session.filename} {' '}({session.image_width} x {session.image_height} px)
)} {/* Loading indicator */} {deskewing && (
Begradigung laeuft (beide Methoden)...
)} {/* Image comparison */} {session && ( )} {/* Controls */} setShowBinarized((v) => !v)} showGrid={showGrid} onToggleGrid={() => setShowGrid((v) => !v)} onManualDeskew={handleManualDeskew} onGroundTruth={handleGroundTruth} onNext={onNext} isApplying={applying} /> {error && (
{error}
)}
) }