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>
124 lines
4.2 KiB
TypeScript
124 lines
4.2 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useState } from 'react'
|
|
import type { DeskewResult, DewarpResult, DewarpGroundTruth } from '@/app/(admin)/ai/ocr-kombi/types'
|
|
import { DewarpSummaryBanner } from './DewarpSummaryBanner'
|
|
import { DewarpFineTunePanel } from './DewarpFineTunePanel'
|
|
import { DewarpGroundTruthPanel } from './DewarpGroundTruthPanel'
|
|
|
|
interface DewarpControlsProps {
|
|
dewarpResult: DewarpResult | null
|
|
deskewResult?: DeskewResult | null
|
|
showGrid: boolean
|
|
onToggleGrid: () => void
|
|
onManualDewarp: (shearDegrees: number) => void
|
|
onCombinedAdjust?: (rotationDegrees: number, shearDegrees: number) => void
|
|
onGroundTruth: (gt: DewarpGroundTruth) => void
|
|
onNext: () => void
|
|
isApplying: boolean
|
|
}
|
|
|
|
export function DewarpControls({
|
|
dewarpResult,
|
|
deskewResult,
|
|
showGrid,
|
|
onToggleGrid,
|
|
onManualDewarp,
|
|
onCombinedAdjust,
|
|
onGroundTruth,
|
|
onNext,
|
|
isApplying,
|
|
}: DewarpControlsProps) {
|
|
const [manualShear, setManualShear] = useState(0)
|
|
const [gtSaved, setGtSaved] = useState(false)
|
|
const [showFineTune, setShowFineTune] = useState(false)
|
|
|
|
// Initialize slider to auto-detected value when result arrives
|
|
useEffect(() => {
|
|
if (dewarpResult && dewarpResult.shear_degrees !== undefined) {
|
|
setManualShear(dewarpResult.shear_degrees)
|
|
}
|
|
}, [dewarpResult?.shear_degrees])
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
{/* Summary banner */}
|
|
{dewarpResult && (
|
|
<DewarpSummaryBanner
|
|
dewarpResult={dewarpResult}
|
|
showGrid={showGrid}
|
|
onToggleGrid={onToggleGrid}
|
|
/>
|
|
)}
|
|
|
|
{/* Manual shear angle slider */}
|
|
{dewarpResult && (
|
|
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
|
|
<div className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Scherwinkel (manuell)</div>
|
|
<div className="flex items-center gap-3">
|
|
<span className="text-xs text-gray-400 w-10 text-right">-2.0{'\u00B0'}</span>
|
|
<input
|
|
type="range"
|
|
min={-200}
|
|
max={200}
|
|
step={5}
|
|
value={Math.round(manualShear * 100)}
|
|
onChange={(e) => setManualShear(parseInt(e.target.value) / 100)}
|
|
className="flex-1 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700 accent-teal-500"
|
|
/>
|
|
<span className="text-xs text-gray-400 w-10">+2.0{'\u00B0'}</span>
|
|
<span className="font-mono text-sm w-16 text-right">{manualShear.toFixed(2)}{'\u00B0'}</span>
|
|
<button
|
|
onClick={() => onManualDewarp(manualShear)}
|
|
disabled={isApplying}
|
|
className="px-3 py-1.5 text-sm bg-teal-600 text-white rounded-md hover:bg-teal-700 disabled:opacity-50 transition-colors"
|
|
>
|
|
{isApplying ? '...' : 'Anwenden'}
|
|
</button>
|
|
</div>
|
|
<p className="text-xs text-gray-400 mt-1">
|
|
Scherung der vertikalen Achse in Grad. Positiv = Spalten nach rechts kippen, negativ = nach links.
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* Fine-tuning panel */}
|
|
{dewarpResult && onCombinedAdjust && (
|
|
<DewarpFineTunePanel
|
|
dewarpResult={dewarpResult}
|
|
deskewResult={deskewResult}
|
|
showFineTune={showFineTune}
|
|
onToggleFineTune={() => setShowFineTune(v => !v)}
|
|
onCombinedAdjust={onCombinedAdjust}
|
|
onGroundTruth={onGroundTruth}
|
|
isApplying={isApplying}
|
|
gtSaved={gtSaved}
|
|
onGtSaved={() => setGtSaved(true)}
|
|
/>
|
|
)}
|
|
|
|
{/* Ground Truth */}
|
|
{dewarpResult && !showFineTune && (
|
|
<DewarpGroundTruthPanel
|
|
manualShear={manualShear}
|
|
onGroundTruth={onGroundTruth}
|
|
gtSaved={gtSaved}
|
|
onGtSaved={() => setGtSaved(true)}
|
|
/>
|
|
)}
|
|
|
|
{/* Next button */}
|
|
{dewarpResult && (
|
|
<div className="flex justify-end">
|
|
<button
|
|
onClick={onNext}
|
|
className="px-6 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700 font-medium transition-colors"
|
|
>
|
|
Uebernehmen & Weiter →
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|