diff --git a/admin-lehrer/app/(admin)/ai/ocr-pipeline/page.tsx b/admin-lehrer/app/(admin)/ai/ocr-pipeline/page.tsx index ae628ce..340555d 100644 --- a/admin-lehrer/app/(admin)/ai/ocr-pipeline/page.tsx +++ b/admin-lehrer/app/(admin)/ai/ocr-pipeline/page.tsx @@ -4,6 +4,7 @@ import { useState } from 'react' import { PagePurpose } from '@/components/common/PagePurpose' import { PipelineStepper } from '@/components/ocr-pipeline/PipelineStepper' import { StepDeskew } from '@/components/ocr-pipeline/StepDeskew' +import { StepDewarp } from '@/components/ocr-pipeline/StepDewarp' import { StepColumnDetection } from '@/components/ocr-pipeline/StepColumnDetection' import { StepWordRecognition } from '@/components/ocr-pipeline/StepWordRecognition' import { StepCoordinates } from '@/components/ocr-pipeline/StepCoordinates' @@ -13,6 +14,7 @@ import { PIPELINE_STEPS, type PipelineStep } from './types' export default function OcrPipelinePage() { const [currentStep, setCurrentStep] = useState(0) + const [sessionId, setSessionId] = useState(null) const [steps, setSteps] = useState( PIPELINE_STEPS.map((s, i) => ({ ...s, @@ -39,19 +41,26 @@ export default function OcrPipelinePage() { } } + const handleDeskewComplete = (sid: string) => { + setSessionId(sid) + handleNext() + } + const renderStep = () => { switch (currentStep) { case 0: - return + return case 1: - return + return case 2: - return + return case 3: - return + return case 4: - return + return case 5: + return + case 6: return default: return null diff --git a/admin-lehrer/app/(admin)/ai/ocr-pipeline/types.ts b/admin-lehrer/app/(admin)/ai/ocr-pipeline/types.ts index cd39700..a727661 100644 --- a/admin-lehrer/app/(admin)/ai/ocr-pipeline/types.ts +++ b/admin-lehrer/app/(admin)/ai/ocr-pipeline/types.ts @@ -33,8 +33,25 @@ export interface DeskewGroundTruth { notes?: string } +export interface DewarpResult { + session_id: string + method_used: 'vertical_edge' | 'text_baseline' | 'manual' | 'none' + curvature_px: number + confidence: number + duration_seconds: number + dewarped_image_url: string + scale_applied?: number +} + +export interface DewarpGroundTruth { + is_correct: boolean + corrected_scale?: number + notes?: string +} + export const PIPELINE_STEPS: PipelineStep[] = [ { id: 'deskew', name: 'Begradigung', icon: '📐', status: 'pending' }, + { id: 'dewarp', name: 'Entzerrung', icon: '🔧', status: 'pending' }, { id: 'columns', name: 'Spalten', icon: '📊', status: 'pending' }, { id: 'words', name: 'Woerter', icon: '🔤', status: 'pending' }, { id: 'coordinates', name: 'Koordinaten', icon: '📍', status: 'pending' }, diff --git a/admin-lehrer/components/ocr-pipeline/DewarpControls.tsx b/admin-lehrer/components/ocr-pipeline/DewarpControls.tsx new file mode 100644 index 0000000..02c3c1e --- /dev/null +++ b/admin-lehrer/components/ocr-pipeline/DewarpControls.tsx @@ -0,0 +1,194 @@ +'use client' + +import { useState } from 'react' +import type { DewarpResult, DewarpGroundTruth } from '@/app/(admin)/ai/ocr-pipeline/types' + +interface DewarpControlsProps { + dewarpResult: DewarpResult | null + showGrid: boolean + onToggleGrid: () => void + onManualDewarp: (scale: number) => void + onGroundTruth: (gt: DewarpGroundTruth) => void + onNext: () => void + isApplying: boolean +} + +const METHOD_LABELS: Record = { + vertical_edge: 'Vertikale Kanten', + text_baseline: 'Textzeilen-Baseline', + manual: 'Manuell', + none: 'Keine Korrektur', +} + +export function DewarpControls({ + dewarpResult, + showGrid, + onToggleGrid, + onManualDewarp, + onGroundTruth, + onNext, + isApplying, +}: DewarpControlsProps) { + const [manualScale, setManualScale] = useState(0) + const [gtFeedback, setGtFeedback] = useState<'correct' | 'incorrect' | null>(null) + const [gtNotes, setGtNotes] = useState('') + const [gtSaved, setGtSaved] = useState(false) + + const handleGroundTruth = (isCorrect: boolean) => { + setGtFeedback(isCorrect ? 'correct' : 'incorrect') + if (isCorrect) { + onGroundTruth({ is_correct: true }) + setGtSaved(true) + } + } + + const handleGroundTruthIncorrect = () => { + onGroundTruth({ + is_correct: false, + corrected_scale: manualScale !== 0 ? manualScale : undefined, + notes: gtNotes || undefined, + }) + setGtSaved(true) + } + + return ( +
+ {/* Results */} + {dewarpResult && ( +
+
+
+ Kruemmung:{' '} + {dewarpResult.curvature_px} px +
+
+
+ Methode:{' '} + + {METHOD_LABELS[dewarpResult.method_used] || dewarpResult.method_used} + +
+
+
+ Konfidenz:{' '} + {Math.round(dewarpResult.confidence * 100)}% +
+
+ + {/* Toggle */} +
+ +
+
+ )} + + {/* Manual scale slider */} + {dewarpResult && ( +
+
Manuelle Staerke
+
+ -3.0 + setManualScale(parseFloat(e.target.value))} + className="flex-1 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700 accent-teal-500" + /> + +3.0 + {manualScale.toFixed(1)} + +
+

+ 0 = keine Korrektur, positiv = nach rechts entzerren, negativ = nach links +

+
+ )} + + {/* Ground Truth */} + {dewarpResult && ( +
+
+ Korrekt entzerrt? +
+ {!gtSaved ? ( +
+
+ + +
+ {gtFeedback === 'incorrect' && ( +
+