feat: Frontend Sub-Sessions (Boxen) in OCR-Pipeline UI
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 29s
CI / test-go-edu-search (push) Successful in 29s
CI / test-python-klausur (push) Failing after 1m57s
CI / test-python-agent-core (push) Successful in 17s
CI / test-nodejs-website (push) Successful in 18s
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 29s
CI / test-go-edu-search (push) Successful in 29s
CI / test-python-klausur (push) Failing after 1m57s
CI / test-python-agent-core (push) Successful in 17s
CI / test-nodejs-website (push) Successful in 18s
- BoxSessionTabs: Tab-Leiste zum Wechsel zwischen Haupt- und Box-Sessions - StepColumnDetection: Box-Info + "Box-Sessions erstellen" Button - page.tsx: Session-Wechsel, Sub-Session-State, auto-return nach Abschluss - types.ts: SubSession, PageZone, erweiterte SessionInfo/ColumnResult Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import type { ColumnResult, ColumnGroundTruth, PageRegion } from '@/app/(admin)/ai/ocr-pipeline/types'
|
||||
import type { ColumnResult, ColumnGroundTruth, PageRegion, SubSession } from '@/app/(admin)/ai/ocr-pipeline/types'
|
||||
import { ColumnControls } from './ColumnControls'
|
||||
import { ManualColumnEditor } from './ManualColumnEditor'
|
||||
import type { ColumnTypeKey } from '@/app/(admin)/ai/ocr-pipeline/types'
|
||||
@@ -13,6 +13,7 @@ type ViewMode = 'normal' | 'ground-truth' | 'manual'
|
||||
interface StepColumnDetectionProps {
|
||||
sessionId: string | null
|
||||
onNext: () => void
|
||||
onBoxSessionsCreated?: (subSessions: SubSession[]) => void
|
||||
}
|
||||
|
||||
/** Convert PageRegion[] to divider percentages + column types for ManualColumnEditor */
|
||||
@@ -34,7 +35,7 @@ function columnsToEditorState(
|
||||
return { dividers, columnTypes }
|
||||
}
|
||||
|
||||
export function StepColumnDetection({ sessionId, onNext }: StepColumnDetectionProps) {
|
||||
export function StepColumnDetection({ sessionId, onNext, onBoxSessionsCreated }: StepColumnDetectionProps) {
|
||||
const [columnResult, setColumnResult] = useState<ColumnResult | null>(null)
|
||||
const [detecting, setDetecting] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
@@ -42,6 +43,8 @@ export function StepColumnDetection({ sessionId, onNext }: StepColumnDetectionPr
|
||||
const [applying, setApplying] = useState(false)
|
||||
const [imageDimensions, setImageDimensions] = useState<{ width: number; height: number } | null>(null)
|
||||
const [savedGtColumns, setSavedGtColumns] = useState<PageRegion[] | null>(null)
|
||||
const [creatingBoxSessions, setCreatingBoxSessions] = useState(false)
|
||||
const [existingSubSessions, setExistingSubSessions] = useState<SubSession[] | null>(null)
|
||||
|
||||
// Fetch session info (image dimensions) + check for cached column result
|
||||
useEffect(() => {
|
||||
@@ -55,6 +58,10 @@ export function StepColumnDetection({ sessionId, onNext }: StepColumnDetectionPr
|
||||
if (info.image_width && info.image_height) {
|
||||
setImageDimensions({ width: info.image_width, height: info.image_height })
|
||||
}
|
||||
if (info.sub_sessions && info.sub_sessions.length > 0) {
|
||||
setExistingSubSessions(info.sub_sessions)
|
||||
onBoxSessionsCreated?.(info.sub_sessions)
|
||||
}
|
||||
if (info.column_result) {
|
||||
setColumnResult(info.column_result)
|
||||
return
|
||||
@@ -178,6 +185,39 @@ export function StepColumnDetection({ sessionId, onNext }: StepColumnDetectionPr
|
||||
}
|
||||
}, [sessionId])
|
||||
|
||||
// Count box zones from column result
|
||||
const boxZones = columnResult?.zones?.filter(z => z.zone_type === 'box') || []
|
||||
const boxCount = boxZones.length
|
||||
|
||||
const createBoxSessions = useCallback(async () => {
|
||||
if (!sessionId) return
|
||||
setCreatingBoxSessions(true)
|
||||
setError(null)
|
||||
try {
|
||||
const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/create-box-sessions`, {
|
||||
method: 'POST',
|
||||
})
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({ detail: res.statusText }))
|
||||
throw new Error(err.detail || 'Box-Sessions konnten nicht erstellt werden')
|
||||
}
|
||||
const data = await res.json()
|
||||
const subs: SubSession[] = data.sub_sessions.map((s: { id: string; name?: string; box_index: number }) => ({
|
||||
id: s.id,
|
||||
name: s.name || `Box ${s.box_index + 1}`,
|
||||
box_index: s.box_index,
|
||||
current_step: 1,
|
||||
status: 'pending',
|
||||
}))
|
||||
setExistingSubSessions(subs)
|
||||
onBoxSessionsCreated?.(subs)
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : 'Fehler beim Erstellen der Box-Sessions')
|
||||
} finally {
|
||||
setCreatingBoxSessions(false)
|
||||
}
|
||||
}, [sessionId, onBoxSessionsCreated])
|
||||
|
||||
if (!sessionId) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-16 text-center">
|
||||
@@ -317,6 +357,39 @@ export function StepColumnDetection({ sessionId, onNext }: StepColumnDetectionPr
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Box zone info */}
|
||||
{viewMode === 'normal' && boxCount > 0 && (
|
||||
<div className="bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-700 rounded-xl p-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-2xl">📦</span>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-amber-800 dark:text-amber-300">
|
||||
{boxCount} Box{boxCount > 1 ? 'en' : ''} erkannt
|
||||
</div>
|
||||
<div className="text-xs text-amber-600 dark:text-amber-400">
|
||||
Box-Bereiche werden separat verarbeitet
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{existingSubSessions && existingSubSessions.length > 0 ? (
|
||||
<div className="text-xs text-amber-700 dark:text-amber-300 font-medium">
|
||||
{existingSubSessions.length} Box-Session{existingSubSessions.length > 1 ? 's' : ''} vorhanden
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
onClick={createBoxSessions}
|
||||
disabled={creatingBoxSessions}
|
||||
className="px-4 py-2 bg-amber-600 text-white rounded-lg hover:bg-amber-700 transition-colors text-sm font-medium disabled:opacity-50 flex items-center gap-2"
|
||||
>
|
||||
{creatingBoxSessions && (
|
||||
<div className="animate-spin w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full" />
|
||||
)}
|
||||
Box-Sessions erstellen
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Controls */}
|
||||
{viewMode === 'normal' && (
|
||||
<ColumnControls
|
||||
|
||||
Reference in New Issue
Block a user