Phase 1 — Python (klausur-service): 5 monoliths → 36 files - dsfa_corpus_ingestion.py (1,828 LOC → 5 files) - cv_ocr_engines.py (2,102 LOC → 7 files) - cv_layout.py (3,653 LOC → 10 files) - vocab_worksheet_api.py (2,783 LOC → 8 files) - grid_build_core.py (1,958 LOC → 6 files) Phase 2 — Go (edu-search-service, school-service): 8 monoliths → 19 files - staff_crawler.go (1,402 → 4), policy/store.go (1,168 → 3) - policy_handlers.go (700 → 2), repository.go (684 → 2) - search.go (592 → 2), ai_extraction_handlers.go (554 → 2) - seed_data.go (591 → 2), grade_service.go (646 → 2) Phase 3 — TypeScript (admin-lehrer): 45 monoliths → 220+ files - sdk/types.ts (2,108 → 16 domain files) - ai/rag/page.tsx (2,686 → 14 files) - 22 page.tsx files split into _components/ + _hooks/ - 11 component files split into sub-components - 10 SDK data catalogs added to loc-exceptions - Deleted dead backup index_original.ts (4,899 LOC) All original public APIs preserved via re-export facades. Zero new errors: Python imports verified, Go builds clean, TypeScript tsc --noEmit shows only pre-existing errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
146 lines
5.4 KiB
TypeScript
146 lines
5.4 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* OCR Labeling Admin Page
|
|
*
|
|
* Labeling interface for handwriting training data collection.
|
|
* DSGVO-konform: Alle Verarbeitung lokal auf Mac Mini (Ollama).
|
|
*
|
|
* Teil der KI-Daten-Pipeline:
|
|
* OCR-Labeling → RAG Pipeline → Daten & RAG
|
|
*/
|
|
|
|
import { useState } from 'react'
|
|
import { PagePurpose } from '@/components/common/PagePurpose'
|
|
import { AIModuleSidebarResponsive } from '@/components/ai/AIModuleSidebar'
|
|
import { tabs, type TabId } from './constants'
|
|
import { useOcrLabeling } from './useOcrLabeling'
|
|
import { LabelingTab } from './_components/LabelingTab'
|
|
import { SessionsTab } from './_components/SessionsTab'
|
|
import { UploadTab } from './_components/UploadTab'
|
|
import { StatsTab } from './_components/StatsTab'
|
|
import { ExportTab } from './_components/ExportTab'
|
|
|
|
export default function OCRLabelingPage() {
|
|
const [activeTab, setActiveTab] = useState<TabId>('labeling')
|
|
const hook = useOcrLabeling()
|
|
|
|
return (
|
|
<div className="p-6">
|
|
{/* Header */}
|
|
<div className="mb-6">
|
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">OCR-Labeling</h1>
|
|
<p className="text-gray-600 dark:text-gray-400">Handschrift-Training & Ground Truth Erfassung</p>
|
|
</div>
|
|
|
|
{/* Page Purpose with Related Pages */}
|
|
<PagePurpose
|
|
title="OCR-Labeling"
|
|
purpose="Erstellen Sie Ground Truth Daten für das Training von Handschrift-Erkennungsmodellen. Labeln Sie OCR-Ergebnisse, korrigieren Sie Fehler und exportieren Sie Trainingsdaten für TrOCR, Llama Vision und andere Modelle. Teil der KI-Daten-Pipeline: Gelabelte Daten können zur RAG Pipeline exportiert werden."
|
|
audience={['Entwickler', 'Data Scientists', 'QA-Team']}
|
|
architecture={{
|
|
services: ['klausur-service (Python)'],
|
|
databases: ['PostgreSQL', 'MinIO (Bilder)'],
|
|
}}
|
|
relatedPages={[
|
|
{ name: 'Magic Help', href: '/ai/magic-help', description: 'TrOCR testen & fine-tunen' },
|
|
{ name: 'RAG Pipeline', href: '/ai/rag-pipeline', description: 'Trainierte Daten indexieren' },
|
|
{ name: 'Klausur-Korrektur', href: '/ai/klausur-korrektur', description: 'OCR in Aktion' },
|
|
{ name: 'Daten & RAG', href: '/ai/rag', description: 'Indexierte Daten durchsuchen' },
|
|
]}
|
|
collapsible={true}
|
|
defaultCollapsed={true}
|
|
/>
|
|
|
|
{/* AI Module Sidebar - Desktop: Fixed, Mobile: FAB + Drawer */}
|
|
<AIModuleSidebarResponsive currentModule="ocr-labeling" />
|
|
|
|
{/* Error Toast */}
|
|
{hook.error && (
|
|
<div className="fixed top-4 right-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded z-50">
|
|
<span>{hook.error}</span>
|
|
<button onClick={() => hook.setError(null)} className="ml-4">X</button>
|
|
</div>
|
|
)}
|
|
|
|
{/* Tabs */}
|
|
<div className="mb-6">
|
|
<div className="border-b border-slate-200">
|
|
<nav className="flex space-x-4" aria-label="Tabs">
|
|
{tabs.map((tab) => (
|
|
<button
|
|
key={tab.id}
|
|
onClick={() => setActiveTab(tab.id)}
|
|
className={`flex items-center gap-2 px-4 py-3 border-b-2 font-medium text-sm transition-colors ${
|
|
activeTab === tab.id
|
|
? 'border-primary-500 text-primary-600'
|
|
: 'border-transparent text-slate-500 hover:text-slate-700 hover:border-slate-300'
|
|
}`}
|
|
>
|
|
{tab.icon}
|
|
{tab.name}
|
|
</button>
|
|
))}
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Tab Content */}
|
|
{hook.loading ? (
|
|
<div className="flex items-center justify-center h-64">
|
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600" />
|
|
</div>
|
|
) : (
|
|
<>
|
|
{activeTab === 'labeling' && (
|
|
<LabelingTab
|
|
queue={hook.queue}
|
|
currentItem={hook.currentItem}
|
|
currentIndex={hook.currentIndex}
|
|
correctedText={hook.correctedText}
|
|
setCorrectedText={hook.setCorrectedText}
|
|
goToNext={hook.goToNext}
|
|
goToPrev={hook.goToPrev}
|
|
selectQueueItem={hook.selectQueueItem}
|
|
confirmItem={hook.confirmItem}
|
|
correctItem={hook.correctItem}
|
|
skipItem={hook.skipItem}
|
|
/>
|
|
)}
|
|
{activeTab === 'sessions' && (
|
|
<SessionsTab
|
|
sessions={hook.sessions}
|
|
selectedSession={hook.selectedSession}
|
|
setSelectedSession={hook.setSelectedSession}
|
|
fetchSessions={hook.fetchSessions}
|
|
setError={hook.setError}
|
|
/>
|
|
)}
|
|
{activeTab === 'upload' && (
|
|
<UploadTab
|
|
sessions={hook.sessions}
|
|
selectedSession={hook.selectedSession}
|
|
setSelectedSession={hook.setSelectedSession}
|
|
fetchQueue={hook.fetchQueue}
|
|
fetchStats={hook.fetchStats}
|
|
setError={hook.setError}
|
|
/>
|
|
)}
|
|
{activeTab === 'stats' && (
|
|
<StatsTab stats={hook.stats} />
|
|
)}
|
|
{activeTab === 'export' && (
|
|
<ExportTab
|
|
sessions={hook.sessions}
|
|
selectedSession={hook.selectedSession}
|
|
setSelectedSession={hook.setSelectedSession}
|
|
stats={hook.stats}
|
|
setError={hook.setError}
|
|
/>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|