'use client' import { useEffect, useState } from 'react' import type { CropResult } from '@/app/(admin)/ai/ocr-pipeline/types' import { ImageCompareView } from './ImageCompareView' const KLAUSUR_API = '/klausur-api' interface StepCropProps { sessionId: string | null onNext: () => void } export function StepCrop({ sessionId, onNext }: StepCropProps) { const [cropResult, setCropResult] = useState(null) const [cropping, setCropping] = useState(false) const [error, setError] = useState(null) const [hasRun, setHasRun] = useState(false) // Auto-trigger crop on mount useEffect(() => { if (!sessionId || hasRun) return setHasRun(true) const runCrop = async () => { setCropping(true) setError(null) try { // Check if session already has crop result const sessionRes = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}`) if (sessionRes.ok) { const sessionData = await sessionRes.json() if (sessionData.crop_result) { setCropResult(sessionData.crop_result) setCropping(false) return } } const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/crop`, { method: 'POST', }) if (!res.ok) { throw new Error('Zuschnitt fehlgeschlagen') } const data = await res.json() setCropResult(data) } catch (e) { setError(e instanceof Error ? e.message : 'Unbekannter Fehler') } finally { setCropping(false) } } runCrop() }, [sessionId, hasRun]) const handleSkip = async () => { if (!sessionId) return try { const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/crop/skip`, { method: 'POST', }) if (res.ok) { const data = await res.json() setCropResult(data) } } catch (e) { console.error('Skip crop failed:', e) } onNext() } if (!sessionId) { return
Keine Session ausgewaehlt.
} const dewarpedUrl = `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/image/dewarped` const croppedUrl = cropResult ? `${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/image/cropped` : null return (
{/* Loading indicator */} {cropping && (
Scannerraender werden erkannt...
)} {/* Image comparison */} {/* Crop result info */} {cropResult && (
{(cropResult as Record).multi_page ? ( <> Mehrseitig: {(cropResult as Record).page_count as number} Seiten erkannt {((cropResult as Record).sub_sessions as Array<{id: string; name: string; page_index: number}> | undefined)?.map((sub) => ( Seite {sub.page_index + 1} ))} ) : cropResult.crop_applied ? ( <> Zugeschnitten {cropResult.detected_format && ( <>
Format: {cropResult.detected_format} {cropResult.format_confidence != null && ( ({Math.round(cropResult.format_confidence * 100)}%) )} )} {cropResult.original_size && cropResult.cropped_size && ( <>
{cropResult.original_size.width}x{cropResult.original_size.height} → {cropResult.cropped_size.width}x{cropResult.cropped_size.height} )} {cropResult.border_fractions && ( <>
Raender: O={pct(cropResult.border_fractions.top)} U={pct(cropResult.border_fractions.bottom)} L={pct(cropResult.border_fractions.left)} R={pct(cropResult.border_fractions.right)} )} ) : ( Kein Zuschnitt noetig )} {cropResult.duration_seconds != null && ( {cropResult.duration_seconds}s )}
)} {/* Action buttons */} {cropResult && (
)} {error && (
{error}
)}
) } function pct(v: number): string { return `${(v * 100).toFixed(1)}%` }