'use client' import { RefObject } from 'react' import type { GridResult, GridCell } from '@/app/(admin)/ai/ocr-kombi/types' import { MultilineText, colTypeLabel, colTypeColor, confColor } from './WordRecognitionUtils' const KLAUSUR_API = '/klausur-api' interface WordRecognitionOverviewProps { sessionId: string gridResult: GridResult | null detecting: boolean editedCells: GridCell[] activeIndex: number setActiveIndex: (idx: number) => void setMode: (mode: 'overview' | 'labeling') => void tableEndRef: RefObject } export function WordRecognitionOverview({ sessionId, gridResult, detecting, editedCells, activeIndex, setActiveIndex, setMode, tableEndRef, }: WordRecognitionOverviewProps) { const overlayUrl = `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/image/words-overlay` const dewarpedUrl = `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/image/cropped` const summary = gridResult?.summary const columnsUsed = gridResult?.columns_used || [] // Group cells by row for table display const cellsByRow: Map = new Map() for (const cell of editedCells) { const existing = cellsByRow.get(cell.row_index) || [] existing.push(cell) cellsByRow.set(cell.row_index, existing) } const sortedRowIndices = [...cellsByRow.keys()].sort((a, b) => a - b) return ( <> {/* Images: overlay vs clean */}
Mit Grid-Overlay
{gridResult ? ( // eslint-disable-next-line @next/next/no-img-element Wort-Overlay ) : (
{detecting ? 'Erkenne Woerter...' : 'Keine Daten'}
)}
Entzerrtes Bild
{/* eslint-disable-next-line @next/next/no-img-element */} Entzerrt
{/* Result summary (only after streaming completes) */} {gridResult && summary && !detecting && (

Ergebnis: {summary.non_empty_cells}/{summary.total_cells} Zellen mit Text ({sortedRowIndices.length} Zeilen, {columnsUsed.length} Spalten)

{gridResult.duration_seconds}s
{/* Summary badges */}
Zellen: {summary.non_empty_cells}/{summary.total_cells} {columnsUsed.map((col, i) => ( C{col.index}: {colTypeLabel(col.type)} ))} {summary.low_confidence > 0 && ( Unsicher: {summary.low_confidence} )}
{/* Entry/Cell table */}
{columnsUsed.map((col, i) => ( ))} {sortedRowIndices.map((rowIdx, posIdx) => { const rowCells = cellsByRow.get(rowIdx) || [] const avgConf = rowCells.length ? Math.round(rowCells.reduce((s, c) => s + c.confidence, 0) / rowCells.length) : 0 return ( { setActiveIndex(posIdx); setMode('labeling') }} > {columnsUsed.map((col) => { const cell = rowCells.find(c => c.col_index === col.index) return ( ) })} ) })}
Zeile {colTypeLabel(col.type)} Conf
R{String(rowIdx).padStart(2, '0')} {avgConf}%
)} {/* Streaming cell table (shown while detecting, before complete) */} {detecting && editedCells.length > 0 && !gridResult?.summary?.non_empty_cells && (

Live: {editedCells.length} Zellen erkannt...

{columnsUsed.map((col, i) => ( ))} {(() => { const liveByRow: Map = new Map() for (const cell of editedCells) { const existing = liveByRow.get(cell.row_index) || [] existing.push(cell) liveByRow.set(cell.row_index, existing) } const liveSorted = [...liveByRow.keys()].sort((a, b) => a - b) return liveSorted.map(rowIdx => { const rowCells = liveByRow.get(rowIdx) || [] const avgConf = rowCells.length ? Math.round(rowCells.reduce((s, c) => s + c.confidence, 0) / rowCells.length) : 0 return ( {columnsUsed.map((col) => { const cell = rowCells.find(c => c.col_index === col.index) return ( ) })} ) }) })()}
Zelle {colTypeLabel(col.type)} Conf
R{String(rowIdx).padStart(2, '0')} {avgConf}%
)} ) }