'use client' import { useState, useCallback } from 'react' import { useTheme } from '@/lib/ThemeContext' import { useWorksheet } from '@/lib/worksheet-editor/WorksheetContext' interface CleanupPanelProps { isOpen: boolean onClose: () => void } interface CleanupCapabilities { opencv_available: boolean lama_available: boolean paddleocr_available: boolean } 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 } capabilities: { lama_available: boolean } } interface PipelineResult { success: boolean handwriting_detected: boolean handwriting_removed: boolean layout_reconstructed: boolean cleaned_image_base64?: string fabric_json?: any metadata: any } export function CleanupPanel({ isOpen, onClose }: CleanupPanelProps) { const { isDark } = useTheme() const { canvas, saveToHistory } = useWorksheet() const [file, setFile] = useState(null) const [previewUrl, setPreviewUrl] = useState(null) const [cleanedUrl, setCleanedUrl] = useState(null) const [maskUrl, setMaskUrl] = useState(null) const [isLoading, setIsLoading] = useState(false) 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 [capabilities, setCapabilities] = useState(null) // Options const [removeHandwriting, setRemoveHandwriting] = useState(true) const [reconstructLayout, setReconstructLayout] = useState(true) const [inpaintingMethod, setInpaintingMethod] = useState('auto') // Step tracking const [currentStep, setCurrentStep] = useState<'upload' | 'preview' | 'result'>('upload') 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` }, []) // Load capabilities on mount const loadCapabilities = useCallback(async () => { try { const response = await fetch(`${getApiUrl()}/api/v1/worksheet/capabilities`) if (response.ok) { const data = await response.json() setCapabilities(data) } } catch (err) { console.error('Failed to load capabilities:', err) } }, [getApiUrl]) // Handle file selection const handleFileSelect = useCallback((selectedFile: File) => { setFile(selectedFile) setError(null) setPreviewResult(null) setPipelineResult(null) setCleanedUrl(null) setMaskUrl(null) // Create preview URL const url = URL.createObjectURL(selectedFile) setPreviewUrl(url) setCurrentStep('upload') }, []) // Handle drop const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault() const droppedFile = e.dataTransfer.files[0] if (droppedFile && droppedFile.type.startsWith('image/')) { handleFileSelect(droppedFile) } }, [handleFileSelect]) // Preview cleanup const handlePreview = useCallback(async () => { if (!file) return setIsPreviewing(true) setError(null) try { const formData = new FormData() formData.append('image', file) const response = await fetch(`${getApiUrl()}/api/v1/worksheet/preview-cleanup`, { method: 'POST', body: formData }) if (!response.ok) { throw new Error(`HTTP ${response.status}`) } const result = await response.json() setPreviewResult(result) setCurrentStep('preview') // Also load capabilities await loadCapabilities() } catch (err) { console.error('Preview failed:', err) setError(err instanceof Error ? err.message : 'Vorschau fehlgeschlagen') } finally { setIsPreviewing(false) } }, [file, getApiUrl, loadCapabilities]) // Run full cleanup pipeline const handleCleanup = useCallback(async () => { if (!file) return setIsProcessing(true) setError(null) try { const formData = new FormData() formData.append('image', file) formData.append('remove_handwriting', String(removeHandwriting)) formData.append('reconstruct', String(reconstructLayout)) formData.append('inpainting_method', inpaintingMethod) const response = await fetch(`${getApiUrl()}/api/v1/worksheet/cleanup-pipeline`, { method: 'POST', body: formData }) if (!response.ok) { const errorData = await response.json().catch(() => ({ detail: 'Unknown error' })) throw new Error(errorData.detail || `HTTP ${response.status}`) } const result: PipelineResult = await response.json() setPipelineResult(result) // Create cleaned image URL if (result.cleaned_image_base64) { const cleanedBlob = await fetch(`data:image/png;base64,${result.cleaned_image_base64}`).then(r => r.blob()) setCleanedUrl(URL.createObjectURL(cleanedBlob)) } setCurrentStep('result') } catch (err) { console.error('Cleanup failed:', err) setError(err instanceof Error ? err.message : 'Bereinigung fehlgeschlagen') } finally { setIsProcessing(false) } }, [file, removeHandwriting, reconstructLayout, inpaintingMethod, getApiUrl]) // Import to canvas const handleImportToCanvas = useCallback(async () => { if (!pipelineResult?.fabric_json || !canvas) return try { // Clear canvas and load new content canvas.clear() canvas.loadFromJSON(pipelineResult.fabric_json, () => { canvas.renderAll() saveToHistory('Imported: Cleaned worksheet') }) onClose() } catch (err) { console.error('Import failed:', err) setError('Import in Canvas fehlgeschlagen') } }, [pipelineResult, canvas, saveToHistory, onClose]) // Get detection mask const handleGetMask = useCallback(async () => { if (!file) return try { const formData = new FormData() formData.append('image', file) const response = await fetch(`${getApiUrl()}/api/v1/worksheet/detect-handwriting/mask`, { method: 'POST', body: formData }) if (!response.ok) { throw new Error(`HTTP ${response.status}`) } const blob = await response.blob() setMaskUrl(URL.createObjectURL(blob)) } catch (err) { console.error('Mask fetch failed:', err) } }, [file, getApiUrl]) if (!isOpen) return null // Styles const overlayStyle = 'fixed inset-0 bg-black/50 backdrop-blur-sm z-50' const modalStyle = isDark ? 'backdrop-blur-xl bg-white/10 border border-white/20' : 'backdrop-blur-xl bg-white/90 border border-black/10 shadow-2xl' const labelStyle = isDark ? 'text-white/70' : 'text-slate-600' const cardStyle = isDark ? 'bg-white/5 border-white/10 hover:bg-white/10' : 'bg-white/50 border-slate-200 hover:bg-slate-50' return (
e.stopPropagation()} > {/* Header */}

Arbeitsblatt bereinigen

Handschrift entfernen und Layout rekonstruieren

{/* Step Indicator */}
{['upload', 'preview', 'result'].map((step, idx) => (
{idx < ['upload', 'preview', 'result'].indexOf(currentStep) ? '✓' : idx + 1}
{idx < 2 && (
)}
))}
{/* Content */}
{/* Error Display */} {error && (
{error}
)} {/* Step 1: Upload */} {currentStep === 'upload' && (
{/* Dropzone */}
e.preventDefault()} onClick={() => document.getElementById('file-input')?.click()} > e.target.files?.[0] && handleFileSelect(e.target.files[0])} className="hidden" /> {previewUrl ? (
Preview

{file?.name}

Klicke zum Ändern oder ziehe eine andere Datei hierher

) : ( <>

Bild hochladen

Ziehe ein Bild hierher oder klicke zum Auswählen

Unterstützt: PNG, JPG, JPEG

)}
{/* Options */} {file && (

Optionen

{removeHandwriting && (
)}
)}
)} {/* Step 2: Preview */} {currentStep === 'preview' && previewResult && (
{/* Detection Result */}

Erkennungsergebnis

Handschrift gefunden: {previewResult.has_handwriting ? 'Ja' : 'Nein'}
Konfidenz: {(previewResult.confidence * 100).toFixed(0)}%
Handschrift-Anteil: {(previewResult.handwriting_ratio * 100).toFixed(1)}%
Bildgröße: {previewResult.image_width} × {previewResult.image_height}
{/* Time Estimates */}

Geschätzte Zeit

Erkennung: ~{Math.round(previewResult.estimated_times_ms.detection / 1000)}s
{removeHandwriting && previewResult.has_handwriting && (
Bereinigung: ~{Math.round(previewResult.estimated_times_ms.inpainting / 1000)}s
)} {reconstructLayout && (
Layout: ~{Math.round(previewResult.estimated_times_ms.reconstruction / 1000)}s
)}
Gesamt: ~{Math.round(previewResult.estimated_times_ms.total / 1000)}s
{/* Preview Images */}

Original

{previewUrl && ( Original )}

Maske

{maskUrl ? ( Mask ) : (
Klicke "Maske laden" zum Anzeigen
)}
)} {/* Step 3: Result */} {currentStep === 'result' && pipelineResult && (
{/* Status */}
{pipelineResult.success ? ( ) : ( )}

{pipelineResult.success ? 'Erfolgreich bereinigt' : 'Bereinigung fehlgeschlagen'}

{pipelineResult.handwriting_detected ? pipelineResult.handwriting_removed ? 'Handschrift wurde erkannt und entfernt' : 'Handschrift erkannt, aber nicht entfernt' : 'Keine Handschrift gefunden'}

{/* Result Images */}

Original

{previewUrl && ( Original )}

Bereinigt

{cleanedUrl ? ( Cleaned ) : (
Kein Bild
)}
{/* Layout Info */} {pipelineResult.layout_reconstructed && pipelineResult.metadata?.layout && (

Layout-Rekonstruktion

Elemente:

{pipelineResult.metadata.layout.element_count}

Tabellen:

{pipelineResult.metadata.layout.table_count}

Größe:

{pipelineResult.metadata.layout.page_width} × {pipelineResult.metadata.layout.page_height}

)}
)}
{/* Footer */}
{currentStep !== 'upload' && ( )}
{currentStep === 'upload' && file && ( )} {currentStep === 'preview' && ( )} {currentStep === 'result' && pipelineResult?.success && ( )}
) }