backend-lehrer (5 files): - alerts_agent/db/repository.py (992 → 5), abitur_docs_api.py (956 → 3) - teacher_dashboard_api.py (951 → 3), services/pdf_service.py (916 → 3) - mail/mail_db.py (987 → 6) klausur-service (5 files): - legal_templates_ingestion.py (942 → 3), ocr_pipeline_postprocess.py (929 → 4) - ocr_pipeline_words.py (876 → 3), ocr_pipeline_ocr_merge.py (616 → 2) - KorrekturPage.tsx (956 → 6) website (5 pages): - mail (985 → 9), edu-search (958 → 8), mac-mini (950 → 7) - ocr-labeling (946 → 7), audit-workspace (871 → 4) studio-v2 (5 files + 1 deleted): - page.tsx (946 → 5), MessagesContext.tsx (925 → 4) - korrektur (914 → 6), worksheet-cleanup (899 → 6) - useVocabWorksheet.ts (888 → 3) - Deleted dead page-original.tsx (934 LOC) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
126 lines
6.4 KiB
TypeScript
126 lines
6.4 KiB
TypeScript
'use client'
|
|
|
|
import { GlassCard } from './GlassCard'
|
|
|
|
interface UploadStepProps {
|
|
isDark: boolean
|
|
previewUrl: string | null
|
|
file: File | null
|
|
removeHandwriting: boolean
|
|
setRemoveHandwriting: (v: boolean) => void
|
|
reconstructLayout: boolean
|
|
setReconstructLayout: (v: boolean) => void
|
|
inpaintingMethod: string
|
|
setInpaintingMethod: (v: string) => void
|
|
isPreviewing: boolean
|
|
onDrop: (e: React.DragEvent) => void
|
|
onFileSelect: (file: File) => void
|
|
onPreview: () => void
|
|
onQRClick: () => void
|
|
}
|
|
|
|
export function UploadStep({
|
|
isDark, previewUrl, file,
|
|
removeHandwriting, setRemoveHandwriting,
|
|
reconstructLayout, setReconstructLayout,
|
|
inpaintingMethod, setInpaintingMethod,
|
|
isPreviewing, onDrop, onFileSelect, onPreview, onQRClick
|
|
}: UploadStepProps) {
|
|
return (
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
<GlassCard className="col-span-1" delay={100}>
|
|
<div
|
|
className="border-2 border-dashed border-white/20 rounded-2xl p-8 text-center cursor-pointer transition-all hover:border-purple-400/50 hover:bg-white/5 min-h-[280px] flex flex-col items-center justify-center"
|
|
onDrop={onDrop}
|
|
onDragOver={(e) => e.preventDefault()}
|
|
onClick={() => document.getElementById('file-input')?.click()}
|
|
>
|
|
<input id="file-input" type="file" accept="image/*"
|
|
onChange={(e) => e.target.files?.[0] && onFileSelect(e.target.files[0])}
|
|
className="hidden" />
|
|
{previewUrl ? (
|
|
<div className="space-y-4">
|
|
<img src={previewUrl} alt="Preview" className="max-h-40 mx-auto rounded-xl shadow-2xl" />
|
|
<p className="text-white font-medium text-sm">{file?.name}</p>
|
|
<p className="text-white/50 text-xs">Klicke zum Ändern</p>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<svg className="w-16 h-16 mx-auto mb-4 text-white/30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
|
|
</svg>
|
|
<p className="text-xl font-semibold text-white mb-2">Datei auswählen</p>
|
|
<p className="text-white/50 text-sm mb-2">Ziehe ein Bild hierher oder klicke</p>
|
|
<p className="text-white/30 text-xs">PNG, JPG, JPEG</p>
|
|
</>
|
|
)}
|
|
</div>
|
|
</GlassCard>
|
|
|
|
<GlassCard className="col-span-1" delay={150}>
|
|
<div
|
|
className="border-2 border-dashed border-white/20 rounded-2xl p-8 text-center cursor-pointer transition-all hover:border-purple-400/50 hover:bg-white/5 min-h-[280px] flex flex-col items-center justify-center"
|
|
onClick={onQRClick}
|
|
>
|
|
<div className="w-16 h-16 mx-auto mb-4 rounded-xl bg-purple-500/20 flex items-center justify-center">
|
|
<span className="text-3xl">📱</span>
|
|
</div>
|
|
<p className="text-xl font-semibold text-white mb-2">Mit Handy scannen</p>
|
|
<p className="text-white/50 text-sm mb-2">QR-Code scannen um Foto hochzuladen</p>
|
|
<p className="text-white/30 text-xs">Im lokalen Netzwerk</p>
|
|
</div>
|
|
</GlassCard>
|
|
|
|
{file && (
|
|
<>
|
|
<GlassCard delay={200}>
|
|
<h3 className="text-lg font-semibold text-white mb-4">Optionen</h3>
|
|
<div className="space-y-4">
|
|
<label className="flex items-center gap-4 cursor-pointer group">
|
|
<input type="checkbox" checked={removeHandwriting} onChange={(e) => setRemoveHandwriting(e.target.checked)}
|
|
className="w-5 h-5 rounded bg-white/10 border-white/20 text-purple-500 focus:ring-purple-500" />
|
|
<div>
|
|
<span className="text-white font-medium group-hover:text-purple-300 transition-colors">Handschrift entfernen</span>
|
|
<p className="text-white/40 text-sm">Erkennt und entfernt handgeschriebene Inhalte</p>
|
|
</div>
|
|
</label>
|
|
<label className="flex items-center gap-4 cursor-pointer group">
|
|
<input type="checkbox" checked={reconstructLayout} onChange={(e) => setReconstructLayout(e.target.checked)}
|
|
className="w-5 h-5 rounded bg-white/10 border-white/20 text-purple-500 focus:ring-purple-500" />
|
|
<div>
|
|
<span className="text-white font-medium group-hover:text-purple-300 transition-colors">Layout rekonstruieren</span>
|
|
<p className="text-white/40 text-sm">Erstellt bearbeitbare Textblöcke</p>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
</GlassCard>
|
|
|
|
<GlassCard delay={300}>
|
|
<h3 className="text-lg font-semibold text-white mb-4">Methode</h3>
|
|
<select value={inpaintingMethod} onChange={(e) => setInpaintingMethod(e.target.value)}
|
|
className="w-full p-3 rounded-xl bg-white/10 border border-white/20 text-white focus:ring-2 focus:ring-purple-500 focus:border-transparent">
|
|
<option value="auto">Automatisch (empfohlen)</option>
|
|
<option value="opencv_telea">OpenCV Telea (schnell)</option>
|
|
<option value="opencv_ns">OpenCV NS (glatter)</option>
|
|
</select>
|
|
<p className="text-white/40 text-sm mt-3">
|
|
Die automatische Methode wählt die beste Option basierend auf dem Bildinhalt.
|
|
</p>
|
|
</GlassCard>
|
|
|
|
<div className="col-span-1 lg:col-span-2 flex justify-center">
|
|
<button onClick={onPreview} disabled={isPreviewing}
|
|
className="px-8 py-4 rounded-2xl font-semibold text-lg bg-gradient-to-r from-purple-500 to-pink-500 text-white hover:shadow-lg hover:shadow-purple-500/30 transition-all disabled:opacity-50 flex items-center gap-3">
|
|
{isPreviewing ? (
|
|
<><div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />Analysiere...</>
|
|
) : (
|
|
<><svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /></svg>Vorschau</>
|
|
)}
|
|
</button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|