feat(ocr): Add Ground Truth labeling UI for OCR comparison

Adds a step-through tool for creating 100% correct reference data (ground truth)
with position information. Users scan a page, review each vocabulary entry with
image crops, confirm or correct the OCR text, and save the result as JSON.

Backend: extract_entries_with_boxes() helper + 3 endpoints (extract-with-boxes,
ground-truth save/load). Frontend: GroundTruthPanel component with SVG overlay,
ImageCrop, keyboard shortcuts (Enter/Tab/arrows), and tab navigation in page.tsx.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
BreakPilot Dev
2026-02-10 09:04:36 +01:00
parent d4a23e8d99
commit 8c77df494b
4 changed files with 872 additions and 3 deletions

View File

@@ -12,7 +12,7 @@ import { useState, useEffect, useCallback, useMemo } from 'react'
import { PagePurpose } from '@/components/common/PagePurpose'
import { AIToolsSidebarResponsive } from '@/components/ai/AIToolsSidebar'
import { QRCodeUpload, UploadedFile } from '@/components/QRCodeUpload'
import { GridOverlay, GridStats, GridLegend, CellCorrectionDialog, BlockReviewPanel, BlockReviewSummary, getCellBlockNumber } from '@/components/ocr'
import { GridOverlay, GridStats, GridLegend, CellCorrectionDialog, BlockReviewPanel, BlockReviewSummary, getCellBlockNumber, GroundTruthPanel } from '@/components/ocr'
import type { GridData, GridCell, BlockReviewData, BlockStatus } from '@/components/ocr'
interface VocabEntry {
@@ -155,6 +155,9 @@ export default function OCRComparePage() {
const [isExporting, setIsExporting] = useState(false)
const [exportSuccess, setExportSuccess] = useState(false)
// Tab State (compare vs ground truth)
const [activeTab, setActiveTab] = useState<'compare' | 'groundtruth'>('compare')
const KLAUSUR_API = '/klausur-api'
// Load session history
@@ -1065,8 +1068,43 @@ export default function OCRComparePage() {
</div>
)}
{/* Tab Bar */}
{sessionId && pageCount > 0 && (
<div className="flex gap-1 bg-slate-100 rounded-lg p-1">
<button
onClick={() => setActiveTab('compare')}
className={`flex-1 px-4 py-2 rounded-md text-sm font-medium transition-colors ${
activeTab === 'compare'
? 'bg-white text-slate-900 shadow-sm'
: 'text-slate-600 hover:text-slate-900'
}`}
>
OCR Vergleich
</button>
<button
onClick={() => setActiveTab('groundtruth')}
className={`flex-1 px-4 py-2 rounded-md text-sm font-medium transition-colors ${
activeTab === 'groundtruth'
? 'bg-white text-slate-900 shadow-sm'
: 'text-slate-600 hover:text-slate-900'
}`}
>
Ground Truth
</button>
</div>
)}
{/* Ground Truth Panel */}
{activeTab === 'groundtruth' && sessionId && (
<GroundTruthPanel
sessionId={sessionId}
selectedPage={selectedPage}
pageImageUrl={`${KLAUSUR_API}/api/v1/vocab/sessions/${sessionId}/pdf-thumbnail/${selectedPage}?hires=true`}
/>
)}
{/* Full-Width Comparison View */}
{(thumbnails[selectedPage] || result) && sessionId && (
{activeTab === 'compare' && (thumbnails[selectedPage] || result) && sessionId && (
<div className={`bg-white rounded-xl border border-slate-200 p-4 ${
isFullscreen ? 'fixed inset-0 z-50 overflow-auto m-0 rounded-none bg-slate-50' : ''
}`}>
@@ -1477,7 +1515,7 @@ export default function OCRComparePage() {
)}
{/* Comparison Summary */}
{result?.comparison && (
{activeTab === 'compare' && result?.comparison && (
<div className="bg-white rounded-xl border border-slate-200 p-6">
<h3 className="font-semibold text-slate-900 mb-4">Vergleichszusammenfassung</h3>