[split-required] Split 58 monoliths across Python, Go, TypeScript (Phases 1-3)
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>
This commit is contained in:
209
admin-lehrer/components/ocr-pipeline/DewarpFineTunePanel.tsx
Normal file
209
admin-lehrer/components/ocr-pipeline/DewarpFineTunePanel.tsx
Normal file
@@ -0,0 +1,209 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import type { DeskewResult, DewarpResult, DewarpGroundTruth } from '@/app/(admin)/ai/ocr-kombi/types'
|
||||
import { METHOD_LABELS, SHEAR_METHOD_KEYS } from './dewarp-constants'
|
||||
import { FineTuneSlider } from './FineTuneSlider'
|
||||
|
||||
interface DewarpFineTunePanelProps {
|
||||
dewarpResult: DewarpResult
|
||||
deskewResult?: DeskewResult | null
|
||||
showFineTune: boolean
|
||||
onToggleFineTune: () => void
|
||||
onCombinedAdjust: (rotationDegrees: number, shearDegrees: number) => void
|
||||
onGroundTruth: (gt: DewarpGroundTruth) => void
|
||||
isApplying: boolean
|
||||
gtSaved: boolean
|
||||
onGtSaved: () => void
|
||||
}
|
||||
|
||||
export function DewarpFineTunePanel({
|
||||
dewarpResult,
|
||||
deskewResult,
|
||||
showFineTune,
|
||||
onToggleFineTune,
|
||||
onCombinedAdjust,
|
||||
onGroundTruth,
|
||||
isApplying,
|
||||
gtSaved,
|
||||
onGtSaved,
|
||||
}: DewarpFineTunePanelProps) {
|
||||
// Fine-tuning rotation sliders (3 passes)
|
||||
const [p1Iterative, setP1Iterative] = useState(0)
|
||||
const [p2Residual, setP2Residual] = useState(0)
|
||||
const [p3Textline, setP3Textline] = useState(0)
|
||||
|
||||
// Fine-tuning shear sliders (4 methods) + selected method
|
||||
const [shearValues, setShearValues] = useState<Record<string, number>>({
|
||||
vertical_edge: 0,
|
||||
projection: 0,
|
||||
hough_lines: 0,
|
||||
text_lines: 0,
|
||||
})
|
||||
const [selectedShearMethod, setSelectedShearMethod] = useState<string>('vertical_edge')
|
||||
|
||||
// Initialize fine-tuning sliders from deskew result
|
||||
useEffect(() => {
|
||||
if (deskewResult) {
|
||||
setP1Iterative(deskewResult.angle_iterative ?? 0)
|
||||
setP2Residual(deskewResult.angle_residual ?? 0)
|
||||
setP3Textline(deskewResult.angle_textline ?? 0)
|
||||
}
|
||||
}, [deskewResult])
|
||||
|
||||
// Initialize shear sliders from dewarp detections
|
||||
useEffect(() => {
|
||||
if (dewarpResult?.detections) {
|
||||
const newValues = { ...shearValues }
|
||||
let bestMethod = selectedShearMethod
|
||||
let bestConf = -1
|
||||
for (const d of dewarpResult.detections) {
|
||||
if (d.method in newValues) {
|
||||
newValues[d.method] = d.shear_degrees
|
||||
if (d.confidence > bestConf) {
|
||||
bestConf = d.confidence
|
||||
bestMethod = d.method
|
||||
}
|
||||
}
|
||||
}
|
||||
setShearValues(newValues)
|
||||
// Select the method that was actually used, or the highest confidence
|
||||
if (dewarpResult.method_used && dewarpResult.method_used in newValues) {
|
||||
setSelectedShearMethod(dewarpResult.method_used)
|
||||
} else {
|
||||
setSelectedShearMethod(bestMethod)
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dewarpResult?.detections])
|
||||
|
||||
const rotationSum = p1Iterative + p2Residual + p3Textline
|
||||
const activeShear = shearValues[selectedShearMethod] ?? 0
|
||||
|
||||
const handleShearValueChange = (method: string, value: number) => {
|
||||
setShearValues((prev) => ({ ...prev, [method]: value }))
|
||||
}
|
||||
|
||||
const handleFineTunePreview = () => {
|
||||
onCombinedAdjust(rotationSum, activeShear)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
|
||||
<button
|
||||
onClick={onToggleFineTune}
|
||||
className="w-full flex items-center justify-between p-4 text-left"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm">⚙️</span>
|
||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">Feinabstimmung</span>
|
||||
<span className="text-xs text-gray-400">(7 Regler)</span>
|
||||
</div>
|
||||
<span className="text-gray-400 text-sm">{showFineTune ? '\u25B2' : '\u25BC'}</span>
|
||||
</button>
|
||||
|
||||
{showFineTune && (
|
||||
<div className="px-4 pb-4 space-y-5">
|
||||
{/* Rotation section */}
|
||||
<div>
|
||||
<div className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-2">
|
||||
Rotation (Begradigung)
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<FineTuneSlider
|
||||
label="P1 Iterative Projection"
|
||||
value={p1Iterative}
|
||||
onChange={setP1Iterative}
|
||||
min={-5}
|
||||
max={5}
|
||||
step={0.05}
|
||||
/>
|
||||
<FineTuneSlider
|
||||
label="P2 Word-Alignment"
|
||||
value={p2Residual}
|
||||
onChange={setP2Residual}
|
||||
min={-3}
|
||||
max={3}
|
||||
step={0.05}
|
||||
/>
|
||||
<FineTuneSlider
|
||||
label="P3 Textline-Regression"
|
||||
value={p3Textline}
|
||||
onChange={setP3Textline}
|
||||
min={-3}
|
||||
max={3}
|
||||
step={0.05}
|
||||
/>
|
||||
<div className="flex items-center gap-2 pt-1 border-t border-gray-100 dark:border-gray-700">
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400 w-36 shrink-0">Summe Rotation</span>
|
||||
<span className="font-mono text-sm font-medium text-teal-600 dark:text-teal-400">
|
||||
{rotationSum >= 0 ? '+' : ''}{rotationSum.toFixed(2)}\u00B0
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Shear section */}
|
||||
<div>
|
||||
<div className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-2">
|
||||
Scherung (Entzerrung) — einen Wert waehlen
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{SHEAR_METHOD_KEYS.map((method) => (
|
||||
<FineTuneSlider
|
||||
key={method}
|
||||
label={METHOD_LABELS[method] || method}
|
||||
value={shearValues[method]}
|
||||
onChange={(v) => handleShearValueChange(method, v)}
|
||||
min={-5}
|
||||
max={5}
|
||||
step={0.05}
|
||||
radioName="shear-method"
|
||||
radioChecked={selectedShearMethod === method}
|
||||
onRadioChange={() => setSelectedShearMethod(method)}
|
||||
/>
|
||||
))}
|
||||
<div className="flex items-center gap-2 pt-1 border-t border-gray-100 dark:border-gray-700">
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400 w-36 shrink-0">Gewaehlte Scherung</span>
|
||||
<span className="font-mono text-sm font-medium text-teal-600 dark:text-teal-400">
|
||||
{activeShear >= 0 ? '+' : ''}{activeShear.toFixed(2)}\u00B0
|
||||
</span>
|
||||
<span className="text-xs text-gray-400 ml-1">
|
||||
({METHOD_LABELS[selectedShearMethod]})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Preview + Save */}
|
||||
<div className="flex items-center gap-3 pt-2">
|
||||
<button
|
||||
onClick={handleFineTunePreview}
|
||||
disabled={isApplying}
|
||||
className="px-4 py-2 text-sm bg-teal-600 text-white rounded-md hover:bg-teal-700 disabled:opacity-50 transition-colors"
|
||||
>
|
||||
{isApplying ? 'Wird angewendet...' : 'Vorschau'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
onGroundTruth({
|
||||
is_correct: false,
|
||||
corrected_shear: activeShear,
|
||||
notes: `Fine-tuned: rotation=${rotationSum.toFixed(3)}, shear=${activeShear.toFixed(3)} (${selectedShearMethod})`,
|
||||
})
|
||||
onGtSaved()
|
||||
}}
|
||||
disabled={gtSaved}
|
||||
className="px-4 py-2 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50 transition-colors"
|
||||
>
|
||||
{gtSaved ? 'Gespeichert' : 'Als Ground Truth speichern'}
|
||||
</button>
|
||||
<span className="text-xs text-gray-400">
|
||||
Rotation: {rotationSum >= 0 ? '+' : ''}{rotationSum.toFixed(2)}\u00B0 + Scherung: {activeShear >= 0 ? '+' : ''}{activeShear.toFixed(2)}\u00B0
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user