Files
breakpilot-lehrer/admin-lehrer/components/ocr-pipeline/DewarpFineTunePanel.tsx
Benjamin Admin b681ddb131 [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>
2026-04-24 17:28:57 +02:00

210 lines
7.8 KiB
TypeScript

'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">&#9881;&#65039;</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) &mdash; 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>
)
}