refactor: independent sessions for page-split + URL-based pipeline navigation
Page-split now creates independent sessions (no parent_session_id), parent marked as status='split' and hidden from list. Navigation uses useSearchParams for URL-based step tracking (browser back/forward works). page.tsx reduced from 684 to 443 lines via usePipelineNavigation hook. Box sub-sessions (column detection) remain unchanged. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
'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<void>
|
||||
}
|
||||
|
||||
const STEP_NAMES: Record<number, string> = {
|
||||
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<string | null>(paramSession)
|
||||
const [currentStepIndex, setCurrentStepIndex] = useState(0)
|
||||
const [docTypeResult, setDocTypeResult] = useState<DocumentTypeResult | null>(null)
|
||||
const [steps, setSteps] = useState<PipelineStep[]>(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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user