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:
Benjamin Admin
2026-03-24 17:05:33 +01:00
parent f34340de9c
commit f931091b57
9 changed files with 368 additions and 391 deletions

View File

@@ -21,6 +21,7 @@ function getStatusIcon(sub: SubSession): string {
return STATUS_ICONS.pending
}
/** Tabs for box sub-sessions (from column detection zone_type='box'). */
export function BoxSessionTabs({ parentSessionId, subSessions, activeSessionId, onSessionChange }: BoxSessionTabsProps) {
if (subSessions.length === 0) return null
@@ -28,7 +29,6 @@ export function BoxSessionTabs({ parentSessionId, subSessions, activeSessionId,
return (
<div className="flex items-center gap-1.5 px-1 py-1.5 bg-gray-50 dark:bg-gray-800/50 rounded-xl border border-gray-200 dark:border-gray-700">
{/* Main session tab */}
<button
onClick={() => onSessionChange(parentSessionId)}
className={`px-3 py-1.5 rounded-lg text-xs font-medium transition-colors ${
@@ -42,7 +42,6 @@ export function BoxSessionTabs({ parentSessionId, subSessions, activeSessionId,
<div className="w-px h-5 bg-gray-200 dark:bg-gray-700" />
{/* Sub-session tabs */}
{subSessions.map((sub) => {
const isActive = activeSessionId === sub.id
const icon = getStatusIcon(sub)
@@ -59,7 +58,7 @@ export function BoxSessionTabs({ parentSessionId, subSessions, activeSessionId,
title={sub.name}
>
<span className="mr-1">{icon}</span>
Seite {sub.box_index + 1}
Box {sub.box_index + 1}
</button>
)
})}

View File

@@ -1,7 +1,7 @@
'use client'
import { useCallback, useEffect, useState } from 'react'
import type { OrientationResult, SessionInfo, SubSession } from '@/app/(admin)/ai/ocr-pipeline/types'
import type { OrientationResult, SessionInfo } from '@/app/(admin)/ai/ocr-pipeline/types'
import { ImageCompareView } from './ImageCompareView'
const KLAUSUR_API = '/klausur-api'
@@ -17,10 +17,10 @@ interface PageSplitResult {
interface StepOrientationProps {
sessionId?: string | null
onNext: (sessionId: string) => void
onSubSessionsCreated?: (subs: SubSession[]) => void
onSessionList?: () => void
}
export function StepOrientation({ sessionId: existingSessionId, onNext, onSubSessionsCreated }: StepOrientationProps) {
export function StepOrientation({ sessionId: existingSessionId, onNext, onSessionList }: StepOrientationProps) {
const [session, setSession] = useState<SessionInfo | null>(null)
const [orientationResult, setOrientationResult] = useState<OrientationResult | null>(null)
const [pageSplitResult, setPageSplitResult] = useState<PageSplitResult | null>(null)
@@ -112,16 +112,6 @@ export function StepOrientation({ sessionId: existingSessionId, onNext, onSubSes
if (splitRes.ok) {
const splitData: PageSplitResult = await splitRes.json()
setPageSplitResult(splitData)
if (splitData.multi_page && splitData.sub_sessions && onSubSessionsCreated) {
onSubSessionsCreated(
splitData.sub_sessions.map((s) => ({
id: s.id,
name: s.name,
box_index: s.page_index,
current_step: splitData.used_original ? 1 : 2,
}))
)
}
}
} catch (e) {
console.error('Page-split detection failed:', e)
@@ -133,7 +123,7 @@ export function StepOrientation({ sessionId: existingSessionId, onNext, onSubSes
setUploading(false)
setDetecting(false)
}
}, [sessionName, onSubSessionsCreated])
}, [sessionName])
const handleDrop = useCallback((e: React.DragEvent) => {
e.preventDefault()
@@ -264,10 +254,10 @@ export function StepOrientation({ sessionId: existingSessionId, onNext, onSubSes
{pageSplitResult?.multi_page && (
<div className="bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-700 p-4">
<div className="text-sm font-medium text-blue-700 dark:text-blue-300">
Doppelseite erkannt {pageSplitResult.page_count} Seiten
Doppelseite erkannt {pageSplitResult.page_count} unabhaengige Sessions erstellt
</div>
<p className="text-xs text-blue-600 dark:text-blue-400 mt-1">
Jede Seite wird einzeln durch die Pipeline (Begradigung, Entzerrung, Zuschnitt, ...) verarbeitet.
Jede Seite wird als eigene Session durch die Pipeline verarbeitet.
{pageSplitResult.used_original && ' (Seitentrennung auf dem Originalbild, da die Orientierung die Doppelseite gedreht hat.)'}
</p>
<div className="flex gap-2 mt-2">
@@ -286,12 +276,21 @@ export function StepOrientation({ sessionId: existingSessionId, onNext, onSubSes
{/* Next button */}
{orientationResult && (
<div className="flex justify-end">
<button
onClick={() => onNext(session.session_id)}
className="px-6 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 font-medium transition-colors"
>
{pageSplitResult?.multi_page ? 'Seiten verarbeiten' : 'Weiter'} &rarr;
</button>
{pageSplitResult?.multi_page ? (
<button
onClick={() => onSessionList?.()}
className="px-6 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 font-medium transition-colors"
>
Zur Session-Liste &rarr;
</button>
) : (
<button
onClick={() => onNext(session.session_id)}
className="px-6 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 font-medium transition-colors"
>
Weiter &rarr;
</button>
)}
</div>
)}