Files
Benjamin Admin d26a9f60ab
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 28s
CI / test-python-klausur (push) Failing after 2m24s
CI / test-python-agent-core (push) Successful in 22s
CI / test-nodejs-website (push) Successful in 20s
Add OCR Kombi Pipeline: modular 11-step architecture with multi-page support
Phase 1 of the clean architecture refactor: Replaces the 751-line ocr-overlay
monolith with a modular pipeline. Each step gets its own component file.

Frontend: /ai/ocr-kombi route with 11 steps (Upload, Orientation, PageSplit,
Deskew, Dewarp, ContentCrop, OCR, Structure, GridBuild, GridReview, GroundTruth).
Session list supports document grouping for multi-page uploads.

Backend: New ocr_kombi/ module with multi-page PDF upload (splits PDF into N
sessions with shared document_group_id). DB migration adds document_group_id
and page_number columns.

Old /ai/ocr-overlay remains fully functional for A/B testing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 15:55:28 +01:00

67 lines
2.5 KiB
TypeScript

'use client'
import { useState } from 'react'
import { DOCUMENT_CATEGORIES, type DocumentCategory } from '@/app/(admin)/ai/ocr-pipeline/types'
interface SessionHeaderProps {
sessionName: string
activeCategory?: DocumentCategory
isGroundTruth: boolean
onUpdateCategory: (category: DocumentCategory) => void
}
export function SessionHeader({
sessionName,
activeCategory,
isGroundTruth,
onUpdateCategory,
}: SessionHeaderProps) {
const [showCategoryPicker, setShowCategoryPicker] = useState(false)
const catInfo = DOCUMENT_CATEGORIES.find(c => c.value === activeCategory)
return (
<div className="relative flex items-center gap-3 text-sm text-gray-500 dark:text-gray-400">
<span>
Aktive Session:{' '}
<span className="font-medium text-gray-700 dark:text-gray-300">{sessionName}</span>
</span>
<button
onClick={() => setShowCategoryPicker(!showCategoryPicker)}
className={`text-xs px-2.5 py-1 rounded-full border transition-colors ${
activeCategory
? 'bg-teal-50 dark:bg-teal-900/30 border-teal-200 dark:border-teal-700 text-teal-700 dark:text-teal-300 hover:bg-teal-100'
: 'bg-amber-50 dark:bg-amber-900/20 border-amber-300 dark:border-amber-700 text-amber-700 dark:text-amber-300 hover:bg-amber-100 animate-pulse'
}`}
>
{catInfo ? `${catInfo.icon} ${catInfo.label}` : 'Kategorie setzen'}
</button>
{isGroundTruth && (
<span className="text-xs px-2 py-0.5 rounded-full bg-amber-50 dark:bg-amber-900/20 border border-amber-300 dark:border-amber-700 text-amber-700 dark:text-amber-300">
GT
</span>
)}
{showCategoryPicker && (
<div className="absolute left-0 top-full mt-1 z-20 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg p-2 grid grid-cols-2 gap-1 w-64">
{DOCUMENT_CATEGORIES.map(cat => (
<button
key={cat.value}
onClick={() => {
onUpdateCategory(cat.value)
setShowCategoryPicker(false)
}}
className={`text-xs px-2 py-1.5 rounded-md text-left transition-colors ${
activeCategory === cat.value
? 'bg-teal-100 dark:bg-teal-900/40 text-teal-700 dark:text-teal-300'
: 'hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-400'
}`}
>
{cat.icon} {cat.label}
</button>
))}
</div>
)}
</div>
)
}