Add OCR Kombi Pipeline: modular 11-step architecture with multi-page support
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
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
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>
This commit is contained in:
109
admin-lehrer/components/ocr-kombi/StepGridBuild.tsx
Normal file
109
admin-lehrer/components/ocr-kombi/StepGridBuild.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
const KLAUSUR_API = '/klausur-api'
|
||||
|
||||
interface StepGridBuildProps {
|
||||
sessionId: string | null
|
||||
onNext: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 9: Grid Build.
|
||||
* Triggers the build-grid endpoint and shows progress.
|
||||
*/
|
||||
export function StepGridBuild({ sessionId, onNext }: StepGridBuildProps) {
|
||||
const [building, setBuilding] = useState(false)
|
||||
const [result, setResult] = useState<{ rows: number; cols: number; cells: number } | null>(null)
|
||||
const [error, setError] = useState('')
|
||||
const [autoTriggered, setAutoTriggered] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!sessionId || autoTriggered) return
|
||||
// Check if grid already exists
|
||||
checkExistingGrid()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [sessionId])
|
||||
|
||||
const checkExistingGrid = async () => {
|
||||
if (!sessionId) return
|
||||
try {
|
||||
const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/grid-editor`)
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
if (data.grid_shape) {
|
||||
setResult({ rows: data.grid_shape.rows, cols: data.grid_shape.cols, cells: data.grid_shape.total_cells })
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch { /* no existing grid */ }
|
||||
|
||||
// Auto-trigger build
|
||||
setAutoTriggered(true)
|
||||
buildGrid()
|
||||
}
|
||||
|
||||
const buildGrid = async () => {
|
||||
if (!sessionId) return
|
||||
setBuilding(true)
|
||||
setError('')
|
||||
try {
|
||||
const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/build-grid`, {
|
||||
method: 'POST',
|
||||
})
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}))
|
||||
throw new Error(data.detail || `Grid-Build fehlgeschlagen (${res.status})`)
|
||||
}
|
||||
const data = await res.json()
|
||||
const shape = data.grid_shape || { rows: 0, cols: 0, total_cells: 0 }
|
||||
setResult({ rows: shape.rows, cols: shape.cols, cells: shape.total_cells })
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : String(e))
|
||||
} finally {
|
||||
setBuilding(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{building && (
|
||||
<div className="flex items-center gap-3 p-6 bg-blue-50 dark:bg-blue-900/20 rounded-xl border border-blue-200 dark:border-blue-800">
|
||||
<div className="animate-spin w-5 h-5 border-2 border-blue-400 border-t-transparent rounded-full" />
|
||||
<span className="text-sm text-blue-600 dark:text-blue-400">Grid wird aufgebaut...</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{result && (
|
||||
<div className="space-y-3">
|
||||
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-xl border border-green-200 dark:border-green-800">
|
||||
<div className="text-sm font-medium text-green-700 dark:text-green-300">
|
||||
Grid erstellt: {result.rows} Zeilen, {result.cols} Spalten, {result.cells} Zellen
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={onNext}
|
||||
className="px-4 py-2 bg-teal-600 text-white text-sm rounded-lg hover:bg-teal-700"
|
||||
>
|
||||
Weiter zum Review
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="space-y-3">
|
||||
<div className="text-sm text-red-500 bg-red-50 dark:bg-red-900/20 p-3 rounded-lg">
|
||||
{error}
|
||||
</div>
|
||||
<button
|
||||
onClick={buildGrid}
|
||||
className="px-4 py-2 bg-orange-600 text-white text-sm rounded-lg hover:bg-orange-700"
|
||||
>
|
||||
Erneut versuchen
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user