{/* Loading with streaming progress */}
{detecting && (
{streamProgress
? `Zelle ${streamProgress.current}/${streamProgress.total} erkannt...`
: 'Worterkennung startet...'}
{streamProgress && streamProgress.total > 0 && (
)}
)}
{/* Layout badge + Mode toggle */}
{gridResult && (
{/* Layout badge */}
{isVocab ? 'Vokabel-Layout' : 'Generisch'}
{gridShape && (
{gridShape.rows}×{gridShape.cols} = {gridShape.total_cells} Zellen
)}
)}
{/* Overview mode */}
{mode === 'overview' && (
<>
{/* Images: overlay vs clean */}
Mit Grid-Overlay
{gridResult ? (
// eslint-disable-next-line @next/next/no-img-element
}`})
) : (
{detecting ? 'Erkenne Woerter...' : 'Keine Daten'}
)}
Entzerrtes Bild
{/* eslint-disable-next-line @next/next/no-img-element */}
{/* 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 */}
{/* Unified dynamic table — columns driven by columns_used */}
| Zeile |
{columnsUsed.map((col, i) => (
{colTypeLabel(col.type)}
|
))}
Conf |
{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') }}
>
|
R{String(rowIdx).padStart(2, '0')}
|
{columnsUsed.map((col) => {
const cell = rowCells.find(c => c.col_index === col.index)
return (
|
)
})}
{avgConf}%
|
)
})}
)}
{/* Streaming cell table (shown while detecting, before complete) */}
{detecting && editedCells.length > 0 && !gridResult?.summary?.non_empty_cells && (
Live: {editedCells.length} Zellen erkannt...
| Zelle |
{columnsUsed.map((col, i) => (
{colTypeLabel(col.type)}
|
))}
Conf |
{(() => {
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 (
|
R{String(rowIdx).padStart(2, '0')}
|
{columnsUsed.map((col) => {
const cell = rowCells.find(c => c.col_index === col.index)
return (
|
)
})}
{avgConf}%
|
)
})
})()}
)}
>
)}
{/* Labeling mode */}
{mode === 'labeling' && editedCells.length > 0 && (
{/* Left 2/3: Image with highlighted active row */}
Zeile {activeIndex + 1} von {getUniqueRowCount()}
{/* eslint-disable-next-line @next/next/no-img-element */}
}`})
{/* Highlight overlay for active row */}
{(() => {
const rowCells = getRowCells(activeIndex)
return rowCells.map(cell => (
))
})()}
{/* Right 1/3: Editable fields */}
{/* Navigation */}
{activeIndex + 1} / {getUniqueRowCount()}
{/* Status badge */}
{(() => {
const rowCells = getRowCells(activeIndex)
const avgConf = rowCells.length
? Math.round(rowCells.reduce((s, c) => s + c.confidence, 0) / rowCells.length)
: 0
return (
{avgConf}% Konfidenz
)
})()}
{/* Editable fields — one per column, driven by columns_used */}
{(() => {
const rowCells = getRowCells(activeIndex)
return columnsUsed.map((col, colIdx) => {
const cell = rowCells.find(c => c.col_index === col.index)
if (!cell) return null
return (
{cell.cell_id}
{/* Cell crop */}
)
})
})()}
{/* Action buttons */}
{/* Shortcuts hint */}
Enter = Bestaetigen & weiter
Ctrl+Down = Ueberspringen
Ctrl+Up = Zurueck
{/* Row list (compact) */}
Alle Zeilen
{sortedRowIndices.map((rowIdx, posIdx) => {
const rowCells = cellsByRow.get(rowIdx) || []
const textParts = rowCells.filter(c => c.text).map(c => c.text.replace(/\n/g, ' '))
return (
setActiveIndex(posIdx)}
className={`flex items-center gap-1 px-2 py-1 rounded text-[10px] cursor-pointer transition-colors ${
posIdx === activeIndex
? 'bg-teal-50 dark:bg-teal-900/30 border border-teal-200 dark:border-teal-700'
: 'hover:bg-gray-50 dark:hover:bg-gray-700/50'
}`}
>
R{String(rowIdx).padStart(2, '0')}
{textParts.join(' \u2192 ') || '\u2014'}
)
})}
)}
{/* Controls */}
{gridResult && (
{/* Grid method selector */}
{/* OCR Engine selector */}
{/* Pronunciation selector (only for vocab) */}
{isVocab && (
)}
{/* Show which engine was used */}
{usedEngine && (
{usedEngine}
)}
{/* Ground truth */}
{!gtSaved ? (
<>
setGtNotes(e.target.value)}
className="px-2 py-1 text-xs border rounded dark:bg-gray-700 dark:border-gray-600 w-48"
/>
>
) : (
Ground Truth gespeichert
)}
)}
{error && (
{error}
)}
)
}
/**
* CellCrop: Shows a cropped portion of the dewarped image based on percent bbox.
* Uses CSS background-image + background-position for efficient cropping.
*/
function CellCrop({ imageUrl, bbox }: { imageUrl: string; bbox: { x: number; y: number; w: number; h: number } }) {
// Scale factor: how much to zoom into the cell
const scaleX = 100 / bbox.w
const scaleY = 100 / bbox.h
const scale = Math.min(scaleX, scaleY, 8) // Cap zoom at 8x
return (