Sessions werden jetzt in PostgreSQL gespeichert statt in-memory. Neue Session-Liste mit Name, Datum, Schritt. Sessions ueberleben Browser-Refresh und Container-Neustart. Step 3 nutzt analyze_layout() fuer automatische Spaltenerkennung mit farbigem Overlay. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
120 lines
4.6 KiB
TypeScript
120 lines
4.6 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import type { ColumnResult, ColumnGroundTruth, PageRegion } from '@/app/(admin)/ai/ocr-pipeline/types'
|
|
|
|
interface ColumnControlsProps {
|
|
columnResult: ColumnResult | null
|
|
onRerun: () => void
|
|
onGroundTruth: (gt: ColumnGroundTruth) => void
|
|
onNext: () => void
|
|
isDetecting: boolean
|
|
}
|
|
|
|
const TYPE_COLORS: Record<string, string> = {
|
|
column_en: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400',
|
|
column_de: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400',
|
|
column_example: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400',
|
|
header: 'bg-gray-100 text-gray-600 dark:bg-gray-700/50 dark:text-gray-400',
|
|
footer: 'bg-gray-100 text-gray-600 dark:bg-gray-700/50 dark:text-gray-400',
|
|
}
|
|
|
|
const TYPE_LABELS: Record<string, string> = {
|
|
column_en: 'EN',
|
|
column_de: 'DE',
|
|
column_example: 'Beispiel',
|
|
header: 'Header',
|
|
footer: 'Footer',
|
|
}
|
|
|
|
export function ColumnControls({ columnResult, onRerun, onGroundTruth, onNext, isDetecting }: ColumnControlsProps) {
|
|
const [gtSaved, setGtSaved] = useState(false)
|
|
|
|
if (!columnResult) return null
|
|
|
|
const columns = columnResult.columns.filter((c: PageRegion) => c.type.startsWith('column'))
|
|
const headerFooter = columnResult.columns.filter((c: PageRegion) => !c.type.startsWith('column'))
|
|
|
|
const handleGt = (isCorrect: boolean) => {
|
|
onGroundTruth({ is_correct: isCorrect })
|
|
setGtSaved(true)
|
|
}
|
|
|
|
return (
|
|
<div className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-4 space-y-4">
|
|
{/* Summary */}
|
|
<div className="flex items-center gap-3">
|
|
<div className="text-sm text-gray-600 dark:text-gray-400">
|
|
<span className="font-medium text-gray-800 dark:text-gray-200">{columns.length} Spalten</span> erkannt
|
|
{columnResult.duration_seconds > 0 && (
|
|
<span className="ml-2 text-xs">({columnResult.duration_seconds}s)</span>
|
|
)}
|
|
</div>
|
|
<button
|
|
onClick={onRerun}
|
|
disabled={isDetecting}
|
|
className="text-xs px-2 py-1 bg-gray-100 dark:bg-gray-700 rounded hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors disabled:opacity-50"
|
|
>
|
|
Erneut erkennen
|
|
</button>
|
|
</div>
|
|
|
|
{/* Column list */}
|
|
<div className="space-y-2">
|
|
{columns.map((col: PageRegion, i: number) => (
|
|
<div key={i} className="flex items-center gap-3 text-sm">
|
|
<span className={`px-2 py-0.5 rounded text-xs font-medium ${TYPE_COLORS[col.type] || ''}`}>
|
|
{TYPE_LABELS[col.type] || col.type}
|
|
</span>
|
|
<span className="text-gray-500 dark:text-gray-400 text-xs font-mono">
|
|
x={col.x} y={col.y} {col.width}x{col.height}px
|
|
</span>
|
|
</div>
|
|
))}
|
|
{headerFooter.map((r: PageRegion, i: number) => (
|
|
<div key={`hf-${i}`} className="flex items-center gap-3 text-sm">
|
|
<span className={`px-2 py-0.5 rounded text-xs font-medium ${TYPE_COLORS[r.type] || ''}`}>
|
|
{TYPE_LABELS[r.type] || r.type}
|
|
</span>
|
|
<span className="text-gray-500 dark:text-gray-400 text-xs font-mono">
|
|
x={r.x} y={r.y} {r.width}x{r.height}px
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Ground Truth + Navigation */}
|
|
<div className="flex items-center justify-between pt-2 border-t border-gray-100 dark:border-gray-700">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-sm text-gray-500 dark:text-gray-400">Spalten korrekt?</span>
|
|
{gtSaved ? (
|
|
<span className="text-xs text-green-600 dark:text-green-400">Gespeichert</span>
|
|
) : (
|
|
<>
|
|
<button
|
|
onClick={() => handleGt(true)}
|
|
className="text-xs px-3 py-1 bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400 rounded hover:bg-green-200 dark:hover:bg-green-900/50 transition-colors"
|
|
>
|
|
Ja
|
|
</button>
|
|
<button
|
|
onClick={() => handleGt(false)}
|
|
className="text-xs px-3 py-1 bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400 rounded hover:bg-red-200 dark:hover:bg-red-900/50 transition-colors"
|
|
>
|
|
Nein
|
|
</button>
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
<button
|
|
onClick={onNext}
|
|
className="px-4 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 transition-colors text-sm font-medium"
|
|
>
|
|
Weiter
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|