From ca7d44e5436cb3bc10fdd5cf290af6e79d1fecfb Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Tue, 10 Mar 2026 11:20:06 +0100 Subject: [PATCH] fix: Overlay spaltenweise Ausrichtung per Median-Snap Alle Zellen einer Spalte bekommen die gleiche x-Position (Median) damit Werte vertikal korrekt untereinander stehen. Co-Authored-By: Claude Opus 4.6 --- .../components/ocr-pipeline/StepLlmReview.tsx | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/admin-lehrer/components/ocr-pipeline/StepLlmReview.tsx b/admin-lehrer/components/ocr-pipeline/StepLlmReview.tsx index 2b249ea..6b370c1 100644 --- a/admin-lehrer/components/ocr-pipeline/StepLlmReview.tsx +++ b/admin-lehrer/components/ocr-pipeline/StepLlmReview.tsx @@ -1,6 +1,6 @@ 'use client' -import { useCallback, useEffect, useRef, useState } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import type { GridCell, GridResult, WordEntry, ColumnMeta } from '@/app/(admin)/ai/ocr-pipeline/types' const KLAUSUR_API = '/klausur-api' @@ -402,6 +402,28 @@ export function StepLlmReview({ sessionId, onNext }: StepLlmReviewProps) { // Active entry for highlighting on image const activeEntry = vocabEntries.find((_: WordEntry, i: number) => activeRowIndices.has(i)) + // Snap all cells in the same column to consistent x/w positions + // Uses the median x and max width per col_index so columns align vertically + const colPositions = useMemo(() => { + const byCol = new Map() + for (const cell of cells) { + if (!cell.bbox_pct) continue + const entry = byCol.get(cell.col_index) || { xs: [], ws: [] } + entry.xs.push(cell.bbox_pct.x) + entry.ws.push(cell.bbox_pct.w) + byCol.set(cell.col_index, entry) + } + const result = new Map() + for (const [colIdx, { xs, ws }] of byCol) { + xs.sort((a, b) => a - b) + ws.sort((a, b) => a - b) + const medianX = xs[Math.floor(xs.length / 2)] + const medianW = ws[Math.floor(ws.length / 2)] + result.set(colIdx, { x: medianX, w: medianW }) + } + return result + }, [cells]) + const pct = progress ? Math.round((progress.current / progress.total) * 100) : 0 /** Handle inline edit of a cell in the overlay */ @@ -672,6 +694,9 @@ export function StepLlmReview({ sessionId, onNext }: StepLlmReviewProps) { > {cells.map(cell => { if (!cell.bbox_pct || !cell.text) return null + const col = colPositions.get(cell.col_index) + const cellX = col?.x ?? cell.bbox_pct.x + const cellW = col?.w ?? cell.bbox_pct.w const aspect = imageNaturalSize ? imageNaturalSize.h / imageNaturalSize.w : 4 / 3 const containerH = reconWidth * aspect const cellHeightPx = containerH * (cell.bbox_pct.h / 100) @@ -683,9 +708,9 @@ export function StepLlmReview({ sessionId, onNext }: StepLlmReviewProps) { contentEditable suppressContentEditableWarning style={{ - left: `${cell.bbox_pct.x}%`, + left: `${cellX}%`, top: `${cell.bbox_pct.y}%`, - width: `${cell.bbox_pct.w}%`, + width: `${cellW}%`, height: `${cell.bbox_pct.h}%`, fontSize: `${fontSize}px`, fontWeight: globalBold ? 'bold' : (cell.is_bold ? 'bold' : 'normal'),