'use client' import { useCallback, useEffect, useState } from 'react' import { useRouter, useSearchParams } from 'next/navigation' import { PIPELINE_STEPS, type PipelineStep, type PipelineStepStatus, type DocumentTypeResult } from './types' const KLAUSUR_API = '/klausur-api' export interface PipelineNav { sessionId: string | null currentStepIndex: number currentStepId: string steps: PipelineStep[] docTypeResult: DocumentTypeResult | null goToNextStep: () => void goToStep: (index: number) => void goToSession: (sessionId: string) => void goToSessionList: () => void setDocType: (result: DocumentTypeResult) => void reprocessFromStep: (uiStep: number) => Promise } const STEP_NAMES: Record = { 1: 'Orientierung', 2: 'Begradigung', 3: 'Entzerrung', 4: 'Zuschneiden', 5: 'Spalten', 6: 'Zeilen', 7: 'Woerter', 8: 'Struktur', 9: 'Korrektur', 10: 'Rekonstruktion', 11: 'Validierung', } function buildSteps(uiStep: number, skipSteps: string[]): PipelineStep[] { return PIPELINE_STEPS.map((s, i) => ({ ...s, status: ( skipSteps.includes(s.id) ? 'skipped' : i < uiStep ? 'completed' : i === uiStep ? 'active' : 'pending' ) as PipelineStepStatus, })) } export function usePipelineNavigation(): PipelineNav { const router = useRouter() const searchParams = useSearchParams() const paramSession = searchParams.get('session') const paramStep = searchParams.get('step') const [sessionId, setSessionId] = useState(paramSession) const [currentStepIndex, setCurrentStepIndex] = useState(0) const [docTypeResult, setDocTypeResult] = useState(null) const [steps, setSteps] = useState(buildSteps(0, [])) const [loaded, setLoaded] = useState(false) // Load session info when session param changes useEffect(() => { if (!paramSession) { setSessionId(null) setCurrentStepIndex(0) setDocTypeResult(null) setSteps(buildSteps(0, [])) setLoaded(true) return } const load = async () => { try { const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${paramSession}`) if (!res.ok) return const data = await res.json() setSessionId(paramSession) const savedDocType: DocumentTypeResult | null = data.doc_type_result || null setDocTypeResult(savedDocType) const dbStep = data.current_step || 1 let uiStep = Math.max(0, dbStep - 1) const skipSteps = [...(savedDocType?.skip_steps || [])] // Box sub-sessions (from column detection) skip pre-processing const isBoxSubSession = !!data.parent_session_id if (isBoxSubSession && dbStep >= 5) { const SUB_SESSION_SKIP = ['orientation', 'deskew', 'dewarp', 'crop'] for (const s of SUB_SESSION_SKIP) { if (!skipSteps.includes(s)) skipSteps.push(s) } if (uiStep < 4) uiStep = 4 } // If URL has a step param, use that instead if (paramStep) { const stepIdx = PIPELINE_STEPS.findIndex(s => s.id === paramStep) if (stepIdx >= 0) uiStep = stepIdx } setCurrentStepIndex(uiStep) setSteps(buildSteps(uiStep, skipSteps)) } catch (e) { console.error('Failed to load session:', e) } finally { setLoaded(true) } } load() }, [paramSession, paramStep]) const updateUrl = useCallback((sid: string | null, stepIdx?: number) => { if (!sid) { router.push('/ai/ocr-pipeline') return } const stepId = stepIdx !== undefined ? PIPELINE_STEPS[stepIdx]?.id : undefined const params = new URLSearchParams() params.set('session', sid) if (stepId) params.set('step', stepId) router.push(`/ai/ocr-pipeline?${params.toString()}`) }, [router]) const goToNextStep = useCallback(() => { if (currentStepIndex >= steps.length - 1) { // Last step — return to session list setSessionId(null) setCurrentStepIndex(0) setDocTypeResult(null) setSteps(buildSteps(0, [])) router.push('/ai/ocr-pipeline') return } const skipSteps = docTypeResult?.skip_steps || [] let nextStep = currentStepIndex + 1 while (nextStep < steps.length && skipSteps.includes(PIPELINE_STEPS[nextStep]?.id)) { nextStep++ } if (nextStep >= steps.length) nextStep = steps.length - 1 setSteps(prev => prev.map((s, i) => { if (i === currentStepIndex) return { ...s, status: 'completed' as PipelineStepStatus } if (i === nextStep) return { ...s, status: 'active' as PipelineStepStatus } if (i > currentStepIndex && i < nextStep && skipSteps.includes(PIPELINE_STEPS[i]?.id)) { return { ...s, status: 'skipped' as PipelineStepStatus } } return s }), ) setCurrentStepIndex(nextStep) if (sessionId) updateUrl(sessionId, nextStep) }, [currentStepIndex, steps.length, docTypeResult, sessionId, updateUrl, router]) const goToStep = useCallback((index: number) => { setCurrentStepIndex(index) setSteps(prev => prev.map((s, i) => ({ ...s, status: s.status === 'skipped' ? 'skipped' : i < index ? 'completed' : i === index ? 'active' : 'pending' as PipelineStepStatus, })), ) if (sessionId) updateUrl(sessionId, index) }, [sessionId, updateUrl]) const goToSession = useCallback((sid: string) => { updateUrl(sid) }, [updateUrl]) const goToSessionList = useCallback(() => { setSessionId(null) setCurrentStepIndex(0) setDocTypeResult(null) setSteps(buildSteps(0, [])) router.push('/ai/ocr-pipeline') }, [router]) const setDocType = useCallback((result: DocumentTypeResult) => { setDocTypeResult(result) const skipSteps = result.skip_steps || [] if (skipSteps.length > 0) { setSteps(prev => prev.map(s => skipSteps.includes(s.id) ? { ...s, status: 'skipped' as PipelineStepStatus } : s, ), ) } }, []) const reprocessFromStep = useCallback(async (uiStep: number) => { if (!sessionId) return const dbStep = uiStep + 1 if (!confirm(`Ab Schritt ${dbStep} (${STEP_NAMES[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 } goToStep(uiStep) } catch (e) { console.error('Reprocess error:', e) } }, [sessionId, goToStep]) return { sessionId, currentStepIndex, currentStepId: PIPELINE_STEPS[currentStepIndex]?.id || 'orientation', steps, docTypeResult, goToNextStep, goToStep, goToSession, goToSessionList, setDocType, reprocessFromStep, } }