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:
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user