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>
82 lines
2.8 KiB
TypeScript
82 lines
2.8 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* Shared utilities and small presentational components for StepWordRecognition.
|
|
*/
|
|
|
|
/** Render text with \n as line breaks */
|
|
export function MultilineText({ text }: { text: string }) {
|
|
if (!text) return <span className="text-gray-300 dark:text-gray-600">—</span>
|
|
const lines = text.split('\n')
|
|
if (lines.length === 1) return <>{text}</>
|
|
return <>{lines.map((line, i) => (
|
|
<span key={i}>{line}{i < lines.length - 1 && <br />}</span>
|
|
))}</>
|
|
}
|
|
|
|
/** Column type to human-readable header */
|
|
export function colTypeLabel(colType: string): string {
|
|
const labels: Record<string, string> = {
|
|
column_en: 'English',
|
|
column_de: 'Deutsch',
|
|
column_example: 'Example',
|
|
column_text: 'Text',
|
|
column_marker: 'Marker',
|
|
page_ref: 'Seite',
|
|
}
|
|
return labels[colType] || colType.replace('column_', '')
|
|
}
|
|
|
|
/** Column type to color class */
|
|
export function colTypeColor(colType: string): string {
|
|
const colors: Record<string, string> = {
|
|
column_en: 'text-blue-600 dark:text-blue-400',
|
|
column_de: 'text-green-600 dark:text-green-400',
|
|
column_example: 'text-orange-600 dark:text-orange-400',
|
|
column_text: 'text-purple-600 dark:text-purple-400',
|
|
column_marker: 'text-gray-500 dark:text-gray-400',
|
|
}
|
|
return colors[colType] || 'text-gray-600 dark:text-gray-400'
|
|
}
|
|
|
|
/** Confidence score to color class */
|
|
export function confColor(conf: number): string {
|
|
if (conf >= 70) return 'text-green-600 dark:text-green-400'
|
|
if (conf >= 50) return 'text-yellow-600 dark:text-yellow-400'
|
|
return 'text-red-600 dark:text-red-400'
|
|
}
|
|
|
|
/** Status to badge color class */
|
|
export function statusBadge(status?: string): string {
|
|
const map: Record<string, string> = {
|
|
pending: 'bg-gray-100 dark:bg-gray-700 text-gray-500',
|
|
confirmed: 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400',
|
|
edited: 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-400',
|
|
skipped: 'bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-400',
|
|
}
|
|
return map[status || 'pending'] || map.pending
|
|
}
|
|
|
|
/**
|
|
* CellCrop: Shows a cropped portion of the dewarped image based on percent bbox.
|
|
* Uses CSS background-image + background-position for efficient cropping.
|
|
*/
|
|
export function CellCrop({ imageUrl, bbox }: { imageUrl: string; bbox: { x: number; y: number; w: number; h: number } }) {
|
|
// Scale factor: how much to zoom into the cell
|
|
const scaleX = 100 / bbox.w
|
|
const scaleY = 100 / bbox.h
|
|
const scale = Math.min(scaleX, scaleY, 8) // Cap zoom at 8x
|
|
|
|
return (
|
|
<div
|
|
className="w-full h-full"
|
|
style={{
|
|
backgroundImage: `url(${imageUrl})`,
|
|
backgroundSize: `${scale * 100}%`,
|
|
backgroundPosition: `${-bbox.x * scale}% ${-bbox.y * scale}%`,
|
|
backgroundRepeat: 'no-repeat',
|
|
}}
|
|
/>
|
|
)
|
|
}
|