'use client' import { useState, useEffect, useCallback } from 'react' import { useRouter } from 'next/navigation' import { useTheme } from '@/lib/ThemeContext' import { Sidebar } from '@/components/Sidebar' import { ThemeToggle } from '@/components/ThemeToggle' import { LanguageDropdown } from '@/components/LanguageDropdown' import { QRCodeUpload, UploadedFile } from '@/components/QRCodeUpload' import { GlassCard } from './_components/GlassCard' import { UploadStep } from './_components/UploadStep' import { PreviewStep } from './_components/PreviewStep' import { ResultStep } from './_components/ResultStep' const SESSION_ID_KEY = 'bp_cleanup_session' interface PreviewResult { has_handwriting: boolean; confidence: number; handwriting_ratio: number image_width: number; image_height: number estimated_times_ms: { detection: number; inpainting: number; reconstruction: number; total: number } } interface PipelineResult { success: boolean; handwriting_detected: boolean; handwriting_removed: boolean layout_reconstructed: boolean; cleaned_image_base64?: string; fabric_json?: any; metadata: any } export default function WorksheetCleanupPage() { const { isDark } = useTheme() const router = useRouter() const [file, setFile] = useState(null) const [previewUrl, setPreviewUrl] = useState(null) const [cleanedUrl, setCleanedUrl] = useState(null) const [maskUrl, setMaskUrl] = useState(null) const [isPreviewing, setIsPreviewing] = useState(false) const [isProcessing, setIsProcessing] = useState(false) const [error, setError] = useState(null) const [previewResult, setPreviewResult] = useState(null) const [pipelineResult, setPipelineResult] = useState(null) const [removeHandwriting, setRemoveHandwriting] = useState(true) const [reconstructLayout, setReconstructLayout] = useState(true) const [inpaintingMethod, setInpaintingMethod] = useState('auto') const [currentStep, setCurrentStep] = useState<'upload' | 'preview' | 'processing' | 'result'>('upload') const [showQRModal, setShowQRModal] = useState(false) const [uploadSessionId, setUploadSessionId] = useState('') const [mobileUploadedFiles, setMobileUploadedFiles] = useState([]) const formatFileSize = (bytes: number): string => { if (bytes === 0) return '0 B' const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB'] const i = Math.floor(Math.log(bytes) / Math.log(k)) return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i] } useEffect(() => { let sid = localStorage.getItem(SESSION_ID_KEY) if (!sid) { sid = `cleanup-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; localStorage.setItem(SESSION_ID_KEY, sid) } setUploadSessionId(sid) }, []) const getApiUrl = useCallback(() => { if (typeof window === 'undefined') return 'http://localhost:8086' const { hostname, protocol } = window.location return hostname === 'localhost' ? 'http://localhost:8086' : `${protocol}//${hostname}:8086` }, []) const handleFileSelect = useCallback((selectedFile: File) => { setFile(selectedFile); setError(null); setPreviewResult(null); setPipelineResult(null) setCleanedUrl(null); setMaskUrl(null) setPreviewUrl(URL.createObjectURL(selectedFile)); setCurrentStep('upload') }, []) const handleMobileFileSelect = useCallback(async (uploadedFile: UploadedFile) => { try { const base64Data = uploadedFile.dataUrl.split(',')[1] const byteCharacters = atob(base64Data) const byteNumbers = new Array(byteCharacters.length) for (let i = 0; i < byteCharacters.length; i++) byteNumbers[i] = byteCharacters.charCodeAt(i) const blob = new Blob([new Uint8Array(byteNumbers)], { type: uploadedFile.type }) handleFileSelect(new File([blob], uploadedFile.name, { type: uploadedFile.type })) setShowQRModal(false) } catch { setError('Fehler beim Laden der Datei vom Handy') } }, [handleFileSelect]) const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault() const f = e.dataTransfer.files[0] if (f && f.type.startsWith('image/')) handleFileSelect(f) }, [handleFileSelect]) const handlePreview = useCallback(async () => { if (!file) return; setIsPreviewing(true); setError(null) try { const fd = new FormData(); fd.append('image', file) const res = await fetch(`${getApiUrl()}/api/v1/worksheet/preview-cleanup`, { method: 'POST', body: fd }) if (!res.ok) throw new Error(`HTTP ${res.status}`) setPreviewResult(await res.json()); setCurrentStep('preview') } catch (err) { setError(err instanceof Error ? err.message : 'Vorschau fehlgeschlagen') } finally { setIsPreviewing(false) } }, [file, getApiUrl]) const handleCleanup = useCallback(async () => { if (!file) return; setIsProcessing(true); setCurrentStep('processing'); setError(null) try { const fd = new FormData(); fd.append('image', file) fd.append('remove_handwriting', String(removeHandwriting)) fd.append('reconstruct', String(reconstructLayout)); fd.append('inpainting_method', inpaintingMethod) const res = await fetch(`${getApiUrl()}/api/v1/worksheet/cleanup-pipeline`, { method: 'POST', body: fd }) if (!res.ok) { const ed = await res.json().catch(() => ({ detail: 'Unknown error' })); throw new Error(ed.detail || `HTTP ${res.status}`) } const result: PipelineResult = await res.json(); setPipelineResult(result) if (result.cleaned_image_base64) { const blob = await fetch(`data:image/png;base64,${result.cleaned_image_base64}`).then(r => r.blob()) setCleanedUrl(URL.createObjectURL(blob)) } setCurrentStep('result') } catch (err) { setError(err instanceof Error ? err.message : 'Bereinigung fehlgeschlagen'); setCurrentStep('preview') } finally { setIsProcessing(false) } }, [file, removeHandwriting, reconstructLayout, inpaintingMethod, getApiUrl]) const handleGetMask = useCallback(async () => { if (!file) return try { const fd = new FormData(); fd.append('image', file) const res = await fetch(`${getApiUrl()}/api/v1/worksheet/detect-handwriting/mask`, { method: 'POST', body: fd }) if (!res.ok) throw new Error(`HTTP ${res.status}`) setMaskUrl(URL.createObjectURL(await res.blob())) } catch (err) { console.error('Mask fetch failed:', err) } }, [file, getApiUrl]) const handleOpenInEditor = useCallback(() => { if (pipelineResult?.fabric_json) { sessionStorage.setItem('worksheetCleanupResult', JSON.stringify(pipelineResult.fabric_json)) router.push('/worksheet-editor') } }, [pipelineResult, router]) const handleReset = useCallback(() => { setFile(null); setPreviewUrl(null); setCleanedUrl(null); setMaskUrl(null) setPreviewResult(null); setPipelineResult(null); setError(null); setCurrentStep('upload') }, []) const steps = ['upload', 'preview', 'processing', 'result'] as const const currentStepIdx = steps.indexOf(currentStep) return (

Arbeitsblatt bereinigen

Handschrift entfernen und Layout rekonstruieren

{/* Step Indicator */}
{steps.map((step, idx) => (
idx ? 'bg-green-500 text-white' : isDark ? 'bg-white/10 text-white/40' : 'bg-slate-200 text-slate-400' }`}> {currentStepIdx > idx ? ( ) : idx + 1}
{idx < 3 &&
idx ? 'bg-green-500' : isDark ? 'bg-white/20' : 'bg-slate-300'}`} />}
))}
{error && (
{error}
)}
{currentStep === 'upload' && ( setShowQRModal(true)} /> )} {currentStep === 'preview' && previewResult && ( setCurrentStep('upload')} onCleanup={handleCleanup} onGetMask={handleGetMask} /> )} {currentStep === 'processing' && (

Verarbeite Bild...

{removeHandwriting ? 'Handschrift wird erkannt und entfernt' : 'Bild wird analysiert'}

)} {currentStep === 'result' && pipelineResult && ( )}
{showQRModal && (
setShowQRModal(false)} />
setShowQRModal(false)} onFilesChanged={(files) => setMobileUploadedFiles(files)} /> {mobileUploadedFiles.length > 0 && (

Datei auswählen:

{mobileUploadedFiles.map((f) => ( ))}
)}
)}
) }