'use client' import { useCallback, useEffect, useState } from 'react' import { PagePurpose } from '@/components/common/PagePurpose' import { PipelineStepper } from '@/components/ocr-pipeline/PipelineStepper' import { StepDeskew } from '@/components/ocr-pipeline/StepDeskew' import { StepDewarp } from '@/components/ocr-pipeline/StepDewarp' import { StepColumnDetection } from '@/components/ocr-pipeline/StepColumnDetection' import { StepRowDetection } from '@/components/ocr-pipeline/StepRowDetection' import { StepWordRecognition } from '@/components/ocr-pipeline/StepWordRecognition' import { StepLlmReview } from '@/components/ocr-pipeline/StepLlmReview' import { StepReconstruction } from '@/components/ocr-pipeline/StepReconstruction' import { StepGroundTruth } from '@/components/ocr-pipeline/StepGroundTruth' import { PIPELINE_STEPS, type PipelineStep, type SessionListItem } from './types' const KLAUSUR_API = '/klausur-api' export default function OcrPipelinePage() { const [currentStep, setCurrentStep] = useState(0) const [sessionId, setSessionId] = useState(null) const [sessionName, setSessionName] = useState('') const [sessions, setSessions] = useState([]) const [loadingSessions, setLoadingSessions] = useState(true) const [editingName, setEditingName] = useState(null) const [editNameValue, setEditNameValue] = useState('') const [steps, setSteps] = useState( PIPELINE_STEPS.map((s, i) => ({ ...s, status: i === 0 ? 'active' : 'pending', })), ) // Load session list on mount useEffect(() => { loadSessions() }, []) const loadSessions = async () => { setLoadingSessions(true) try { const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions`) if (res.ok) { const data = await res.json() setSessions(data.sessions || []) } } catch (e) { console.error('Failed to load sessions:', e) } finally { setLoadingSessions(false) } } const openSession = useCallback(async (sid: string) => { try { const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sid}`) if (!res.ok) return const data = await res.json() setSessionId(sid) setSessionName(data.name || data.filename || '') // Determine which step to jump to based on current_step const dbStep = data.current_step || 1 // Steps: 1=deskew, 2=dewarp, 3=columns, ... // UI steps are 0-indexed: 0=deskew, 1=dewarp, 2=columns, ... const uiStep = Math.max(0, dbStep - 1) setSteps( PIPELINE_STEPS.map((s, i) => ({ ...s, status: i < uiStep ? 'completed' : i === uiStep ? 'active' : 'pending', })), ) setCurrentStep(uiStep) } catch (e) { console.error('Failed to open session:', e) } }, []) const deleteSession = useCallback(async (sid: string) => { try { await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sid}`, { method: 'DELETE' }) setSessions((prev) => prev.filter((s) => s.id !== sid)) if (sessionId === sid) { setSessionId(null) setCurrentStep(0) setSteps(PIPELINE_STEPS.map((s, i) => ({ ...s, status: i === 0 ? 'active' : 'pending' }))) } } catch (e) { console.error('Failed to delete session:', e) } }, [sessionId]) const renameSession = useCallback(async (sid: string, newName: string) => { try { await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sid}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: newName }), }) setSessions((prev) => prev.map((s) => (s.id === sid ? { ...s, name: newName } : s))) if (sessionId === sid) setSessionName(newName) } catch (e) { console.error('Failed to rename session:', e) } setEditingName(null) }, [sessionId]) const handleStepClick = (index: number) => { if (index <= currentStep || steps[index].status === 'completed') { setCurrentStep(index) } } const goToStep = (step: number) => { setCurrentStep(step) setSteps((prev) => prev.map((s, i) => ({ ...s, status: i < step ? 'completed' : i === step ? 'active' : 'pending', })), ) } const handleNext = () => { if (currentStep < steps.length - 1) { setSteps((prev) => prev.map((s, i) => { if (i === currentStep) return { ...s, status: 'completed' } if (i === currentStep + 1) return { ...s, status: 'active' } return s }), ) setCurrentStep((prev) => prev + 1) } } const handleDeskewComplete = (sid: string) => { setSessionId(sid) // Reload session list to show the new session loadSessions() handleNext() } const handleNewSession = () => { setSessionId(null) setSessionName('') setCurrentStep(0) setSteps(PIPELINE_STEPS.map((s, i) => ({ ...s, status: i === 0 ? 'active' : 'pending' }))) } const stepNames: Record = { 1: 'Begradigung', 2: 'Entzerrung', 3: 'Spalten', 4: 'Zeilen', 5: 'Woerter', 6: 'Korrektur', 7: 'Rekonstruktion', 8: 'Validierung', } const reprocessFromStep = useCallback(async (uiStep: number) => { if (!sessionId) return const dbStep = uiStep + 1 // UI is 0-indexed, DB is 1-indexed if (!confirm(`Ab Schritt ${dbStep} (${stepNames[dbStep] || '?'}) neu verarbeiten? Nachfolgende Daten werden geloescht.`)) return try { const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/reprocess`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ from_step: dbStep }), }) if (!res.ok) { const data = await res.json().catch(() => ({})) console.error('Reprocess failed:', data.detail || res.status) return } // Reset UI steps goToStep(uiStep) } catch (e) { console.error('Reprocess error:', e) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [sessionId, goToStep]) const renderStep = () => { switch (currentStep) { case 0: return case 1: return case 2: return case 3: return case 4: return case 5: return case 6: return case 7: return default: return null } } return (
{/* Session List */}

Sessions

{loadingSessions ? (
Lade Sessions...
) : sessions.length === 0 ? (
Noch keine Sessions vorhanden.
) : (
{sessions.map((s) => (
openSession(s.id)}> {editingName === s.id ? ( setEditNameValue(e.target.value)} onBlur={() => renameSession(s.id, editNameValue)} onKeyDown={(e) => { if (e.key === 'Enter') renameSession(s.id, editNameValue) if (e.key === 'Escape') setEditingName(null) }} onClick={(e) => e.stopPropagation()} className="w-full px-1 py-0.5 text-sm border rounded dark:bg-gray-700 dark:border-gray-600" /> ) : (
{s.name || s.filename}
)}
{new Date(s.created_at).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: '2-digit', hour: '2-digit', minute: '2-digit' })} Schritt {s.current_step}: {stepNames[s.current_step] || '?'}
))}
)}
{/* Active session name */} {sessionId && sessionName && (
Aktive Session: {sessionName}
)}
{renderStep()}
) }