'use client' import { useEffect, useRef, useState, useMemo } from 'react' import type { GridCell } from '@/app/(admin)/ai/ocr-kombi/types' import type { WordPosition } from './usePixelWordPositions' interface LlmReviewOverlayProps { cells: GridCell[] imageNaturalSize: { w: number; h: number } | null fontScale: number leftPaddingPct: number globalBold: boolean cellWordPositions: Map } export function LlmReviewOverlay({ cells, imageNaturalSize, fontScale, leftPaddingPct, globalBold, cellWordPositions, }: LlmReviewOverlayProps) { const reconRef = useRef(null) const [reconWidth, setReconWidth] = useState(0) // Track reconstruction container width for font size calculation useEffect(() => { const el = reconRef.current if (!el) return const obs = new ResizeObserver(entries => { for (const entry of entries) setReconWidth(entry.contentRect.width) }) obs.observe(el) return () => obs.disconnect() }, []) // Snap all cells in the same column to consistent x/w positions 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]) return (
Text-Rekonstruktion ({cells.filter(c => c.text).length} Zellen)
{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) const wordPos = cellWordPositions.get(cell.cell_id) // Pixel-analysed: render word-groups at detected positions if (wordPos) { return wordPos.map((wp, i) => { const autoFontPx = cellHeightPx * wp.fontRatio * fontScale const fs = Math.max(6, autoFontPx) return ( {wp.text} ) }) } // Fallback: no pixel data - single span for entire cell const fontSize = Math.max(6, cellHeightPx * fontScale) return ( {cell.text} ) })}
) }