feat: color bar respects edits + column pattern auto-correction
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 23s
CI / test-go-edu-search (push) Successful in 25s
CI / test-python-klausur (push) Failing after 1m56s
CI / test-python-agent-core (push) Successful in 14s
CI / test-nodejs-website (push) Successful in 15s
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 23s
CI / test-go-edu-search (push) Successful in 25s
CI / test-python-klausur (push) Failing after 1m56s
CI / test-python-agent-core (push) Successful in 14s
CI / test-nodejs-website (push) Successful in 15s
- Color bar (red/colored indicator) now only shows when word_boxes text still matches the cell text — editing the cell hides stale colors - New "Auto-Korrektur" button: detects dominant prefix+number patterns per column (e.g. p.70, p.71) and completes partial entries (.65 → p.65) — requires 3+ matching entries before correcting Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -443,7 +443,6 @@ export function GridTable({
|
|||||||
const isBold = col.bold || cell?.is_bold
|
const isBold = col.bold || cell?.is_bold
|
||||||
const isLowConf = cell && cell.confidence > 0 && cell.confidence < 60
|
const isLowConf = cell && cell.confidence > 0 && cell.confidence < 60
|
||||||
const isMultiSelected = selectedCells?.has(cellId)
|
const isMultiSelected = selectedCells?.has(cellId)
|
||||||
const cellColor = getCellColor(cell)
|
|
||||||
// Show per-word colored display only when word_boxes
|
// Show per-word colored display only when word_boxes
|
||||||
// match the cell text. Post-processing steps (e.g. 5h
|
// match the cell text. Post-processing steps (e.g. 5h
|
||||||
// slash-IPA → bracket conversion) modify cell.text but
|
// slash-IPA → bracket conversion) modify cell.text but
|
||||||
@@ -451,6 +450,8 @@ export function GridTable({
|
|||||||
// plain input when they diverge.
|
// plain input when they diverge.
|
||||||
const wbText = cell?.word_boxes?.map((wb) => wb.text).join(' ') ?? ''
|
const wbText = cell?.word_boxes?.map((wb) => wb.text).join(' ') ?? ''
|
||||||
const textMatches = !cell?.text || wbText === cell.text
|
const textMatches = !cell?.text || wbText === cell.text
|
||||||
|
// Color bar only when word_boxes still match edited text
|
||||||
|
const cellColor = textMatches ? getCellColor(cell) : null
|
||||||
const hasColoredWords =
|
const hasColoredWords =
|
||||||
textMatches &&
|
textMatches &&
|
||||||
(cell?.word_boxes?.some(
|
(cell?.word_boxes?.some(
|
||||||
|
|||||||
@@ -655,6 +655,81 @@ export function useGridEditor(sessionId: string | null) {
|
|||||||
[grid, pushUndo],
|
[grid, pushUndo],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// Column pattern auto-correction
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect dominant prefix+number patterns per column and complete
|
||||||
|
* partial matches. E.g. if 3+ cells read "p.70", "p.71", etc.,
|
||||||
|
* a cell reading ".65" is corrected to "p.65".
|
||||||
|
* Returns the number of corrections made.
|
||||||
|
*/
|
||||||
|
const autoCorrectColumnPatterns = useCallback(() => {
|
||||||
|
if (!grid) return 0
|
||||||
|
pushUndo(grid.zones)
|
||||||
|
|
||||||
|
let totalFixed = 0
|
||||||
|
const numberPattern = /^(.+?)(\d+)\s*$/
|
||||||
|
|
||||||
|
setGrid((prev) => {
|
||||||
|
if (!prev) return prev
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
zones: prev.zones.map((zone) => {
|
||||||
|
// Group cells by column
|
||||||
|
const cellsByCol = new Map<number, { cell: (typeof zone.cells)[0]; idx: number }[]>()
|
||||||
|
zone.cells.forEach((cell, idx) => {
|
||||||
|
const arr = cellsByCol.get(cell.col_index) || []
|
||||||
|
arr.push({ cell, idx })
|
||||||
|
cellsByCol.set(cell.col_index, arr)
|
||||||
|
})
|
||||||
|
|
||||||
|
const newCells = [...zone.cells]
|
||||||
|
|
||||||
|
for (const [, colEntries] of cellsByCol) {
|
||||||
|
// Count prefix occurrences
|
||||||
|
const prefixCounts = new Map<string, number>()
|
||||||
|
for (const { cell } of colEntries) {
|
||||||
|
const m = cell.text.trim().match(numberPattern)
|
||||||
|
if (m) {
|
||||||
|
prefixCounts.set(m[1], (prefixCounts.get(m[1]) || 0) + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find dominant prefix (>= 3 occurrences)
|
||||||
|
let dominantPrefix = ''
|
||||||
|
let maxCount = 0
|
||||||
|
for (const [prefix, count] of prefixCounts) {
|
||||||
|
if (count >= 3 && count > maxCount) {
|
||||||
|
dominantPrefix = prefix
|
||||||
|
maxCount = count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!dominantPrefix) continue
|
||||||
|
|
||||||
|
// Fix partial matches — entries that are just [.?\s*]NUMBER
|
||||||
|
for (const { cell, idx } of colEntries) {
|
||||||
|
const text = cell.text.trim()
|
||||||
|
if (!text || text.startsWith(dominantPrefix)) continue
|
||||||
|
|
||||||
|
const numMatch = text.match(/^[.\s]*(\d+)\s*$/)
|
||||||
|
if (numMatch) {
|
||||||
|
newCells[idx] = { ...newCells[idx], text: `${dominantPrefix}${numMatch[1]}` }
|
||||||
|
totalFixed++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...zone, cells: newCells }
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (totalFixed > 0) setDirty(true)
|
||||||
|
return totalFixed
|
||||||
|
}, [grid, pushUndo])
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
// Multi-select & bulk formatting
|
// Multi-select & bulk formatting
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
@@ -805,5 +880,6 @@ export function useGridEditor(sessionId: string | null) {
|
|||||||
toggleCellSelection,
|
toggleCellSelection,
|
||||||
clearCellSelection,
|
clearCellSelection,
|
||||||
toggleSelectedBold,
|
toggleSelectedBold,
|
||||||
|
autoCorrectColumnPatterns,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export function StepGridReview({ sessionId, onNext, saveRef }: StepGridReviewPro
|
|||||||
toggleCellSelection,
|
toggleCellSelection,
|
||||||
clearCellSelection,
|
clearCellSelection,
|
||||||
toggleSelectedBold,
|
toggleSelectedBold,
|
||||||
|
autoCorrectColumnPatterns,
|
||||||
} = useGridEditor(sessionId)
|
} = useGridEditor(sessionId)
|
||||||
|
|
||||||
const [showImage, setShowImage] = useState(true)
|
const [showImage, setShowImage] = useState(true)
|
||||||
@@ -246,6 +247,17 @@ export function StepGridReview({ sessionId, onNext, saveRef }: StepGridReviewPro
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<div className="ml-auto flex items-center gap-2">
|
<div className="ml-auto flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
const n = autoCorrectColumnPatterns()
|
||||||
|
if (n === 0) alert('Keine Muster-Korrekturen gefunden.')
|
||||||
|
else alert(`${n} Zelle(n) korrigiert (Muster-Vervollstaendigung).`)
|
||||||
|
}}
|
||||||
|
className="px-2.5 py-1 rounded text-xs border border-purple-200 dark:border-purple-700 bg-purple-50 dark:bg-purple-900/20 text-purple-700 dark:text-purple-300 hover:bg-purple-100 dark:hover:bg-purple-900/40 transition-colors"
|
||||||
|
title="Erkennt Muster wie p.70, p.71 und vervollstaendigt partielle Eintraege wie .65 zu p.65"
|
||||||
|
>
|
||||||
|
Auto-Korrektur
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowImage(!showImage)}
|
onClick={() => setShowImage(!showImage)}
|
||||||
className={`px-2.5 py-1 rounded text-xs border transition-colors ${
|
className={`px-2.5 py-1 rounded text-xs border transition-colors ${
|
||||||
|
|||||||
Reference in New Issue
Block a user