'use client' import { useCallback, useEffect, useState } from 'react' import type { ColumnResult, ColumnGroundTruth, PageRegion } from '@/app/(admin)/ai/ocr-pipeline/types' import { ColumnControls } from './ColumnControls' import { ManualColumnEditor } from './ManualColumnEditor' const KLAUSUR_API = '/klausur-api' interface StepColumnDetectionProps { sessionId: string | null onNext: () => void } export function StepColumnDetection({ sessionId, onNext }: StepColumnDetectionProps) { const [columnResult, setColumnResult] = useState(null) const [detecting, setDetecting] = useState(false) const [error, setError] = useState(null) const [manualMode, setManualMode] = useState(false) const [applying, setApplying] = useState(false) const [imageDimensions, setImageDimensions] = useState<{ width: number; height: number } | null>(null) // Fetch session info (image dimensions) + check for cached column result useEffect(() => { if (!sessionId || imageDimensions) return const fetchSessionInfo = async () => { try { const infoRes = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}`) if (infoRes.ok) { const info = await infoRes.json() if (info.image_width && info.image_height) { setImageDimensions({ width: info.image_width, height: info.image_height }) } if (info.column_result) { setColumnResult(info.column_result) return } } } catch (e) { console.error('Failed to fetch session info:', e) } // No cached result - run auto-detection runAutoDetection() } fetchSessionInfo() // eslint-disable-next-line react-hooks/exhaustive-deps }, [sessionId]) const runAutoDetection = useCallback(async () => { if (!sessionId) return setDetecting(true) setError(null) try { const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/columns`, { method: 'POST', }) if (!res.ok) { const err = await res.json().catch(() => ({ detail: res.statusText })) throw new Error(err.detail || 'Spaltenerkennung fehlgeschlagen') } const data: ColumnResult = await res.json() setColumnResult(data) } catch (e) { setError(e instanceof Error ? e.message : 'Unbekannter Fehler') } finally { setDetecting(false) } }, [sessionId]) const handleRerun = useCallback(() => { runAutoDetection() }, [runAutoDetection]) const handleGroundTruth = useCallback(async (gt: ColumnGroundTruth) => { if (!sessionId) return try { await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/ground-truth/columns`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(gt), }) } catch (e) { console.error('Ground truth save failed:', e) } }, [sessionId]) const handleManualApply = useCallback(async (columns: PageRegion[]) => { if (!sessionId) return setApplying(true) setError(null) try { const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/columns/manual`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ columns }), }) if (!res.ok) { const err = await res.json().catch(() => ({ detail: res.statusText })) throw new Error(err.detail || 'Manuelle Spalten konnten nicht gespeichert werden') } const data = await res.json() setColumnResult({ columns: data.columns, duration_seconds: data.duration_seconds ?? 0, }) setManualMode(false) } catch (e) { setError(e instanceof Error ? e.message : 'Fehler beim Speichern') } finally { setApplying(false) } }, [sessionId]) if (!sessionId) { return (
📊

Schritt 3: Spaltenerkennung

Bitte zuerst Schritt 1 und 2 abschliessen.

) } const dewarpedUrl = `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/image/dewarped` const overlayUrl = `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/image/columns-overlay` return (
{/* Loading indicator */} {detecting && (
Spaltenerkennung laeuft...
)} {manualMode ? ( /* Manual column editor */ setManualMode(false)} applying={applying} /> ) : ( /* Image comparison: overlay (left) vs clean (right) */
Mit Spalten-Overlay
{columnResult ? ( // eslint-disable-next-line @next/next/no-img-element Spalten-Overlay ) : (
{detecting ? 'Erkenne Spalten...' : 'Keine Daten'}
)}
Entzerrtes Bild
{/* eslint-disable-next-line @next/next/no-img-element */} Entzerrt
)} {/* Controls */} {!manualMode && ( setManualMode(true)} onGroundTruth={handleGroundTruth} onNext={onNext} isDetecting={detecting} /> )} {error && (
{error}
)}
) }