This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/website/app/admin/magic-help/page.tsx
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

1018 lines
49 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
/**
* Magic Help Admin Page
*
* Comprehensive admin interface for TrOCR Handwriting Recognition and Exam Correction.
* Features:
* - Model status monitoring
* - OCR testing with image upload
* - Training data management
* - Fine-tuning controls
* - Architecture documentation
* - Configuration settings
*/
import { useState, useEffect, useCallback } from 'react'
import AdminLayout from '@/components/admin/AdminLayout'
type TabId = 'overview' | 'test' | 'training' | 'architecture' | 'settings'
interface TrOCRStatus {
status: 'available' | 'not_installed' | 'error'
model_name?: string
model_id?: string
device?: string
is_loaded?: boolean
has_lora_adapter?: boolean
training_examples_count?: number
error?: string
install_command?: string
}
interface OCRResult {
text: string
confidence: number
processing_time_ms: number
model: string
has_lora_adapter: boolean
}
interface TrainingExample {
image_path: string
ground_truth: string
teacher_id: string
created_at: string
}
interface MagicSettings {
autoDetectLines: boolean
confidenceThreshold: number
maxImageSize: number
loraRank: number
loraAlpha: number
learningRate: number
epochs: number
batchSize: number
enableCache: boolean
cacheMaxAge: number
}
const DEFAULT_SETTINGS: MagicSettings = {
autoDetectLines: true,
confidenceThreshold: 0.7,
maxImageSize: 4096,
loraRank: 8,
loraAlpha: 32,
learningRate: 0.00005,
epochs: 3,
batchSize: 4,
enableCache: true,
cacheMaxAge: 3600,
}
export default function MagicHelpPage() {
const [activeTab, setActiveTab] = useState<TabId>('overview')
const [status, setStatus] = useState<TrOCRStatus | null>(null)
const [loading, setLoading] = useState(true)
const [ocrResult, setOcrResult] = useState<OCRResult | null>(null)
const [ocrLoading, setOcrLoading] = useState(false)
const [examples, setExamples] = useState<TrainingExample[]>([])
const [trainingImage, setTrainingImage] = useState<File | null>(null)
const [trainingText, setTrainingText] = useState('')
const [fineTuning, setFineTuning] = useState(false)
const [settings, setSettings] = useState<MagicSettings>(DEFAULT_SETTINGS)
const [settingsSaved, setSettingsSaved] = useState(false)
const fetchStatus = useCallback(async () => {
try {
const res = await fetch('/api/klausur/trocr/status')
const data = await res.json()
setStatus(data)
} catch {
setStatus({ status: 'error', error: 'Failed to fetch status' })
} finally {
setLoading(false)
}
}, [])
const fetchExamples = useCallback(async () => {
try {
const res = await fetch('/api/klausur/trocr/training/examples')
const data = await res.json()
setExamples(data.examples || [])
} catch (error) {
console.error('Failed to fetch examples:', error)
}
}, [])
useEffect(() => {
fetchStatus()
fetchExamples()
// Load settings from localStorage
const saved = localStorage.getItem('magic-help-settings')
if (saved) {
try {
setSettings(JSON.parse(saved))
} catch {
// ignore parse errors
}
}
}, [fetchStatus, fetchExamples])
const handleFileUpload = async (file: File) => {
setOcrLoading(true)
setOcrResult(null)
const formData = new FormData()
formData.append('file', file)
try {
const res = await fetch(`/api/klausur/trocr/extract?detect_lines=${settings.autoDetectLines}`, {
method: 'POST',
body: formData,
})
const data = await res.json()
if (data.text !== undefined) {
setOcrResult(data)
} else {
setOcrResult({ text: `Error: ${data.detail || 'Unknown error'}`, confidence: 0, processing_time_ms: 0, model: '', has_lora_adapter: false })
}
} catch (error) {
setOcrResult({ text: `Error: ${error}`, confidence: 0, processing_time_ms: 0, model: '', has_lora_adapter: false })
} finally {
setOcrLoading(false)
}
}
const handleAddTrainingExample = async () => {
if (!trainingImage || !trainingText.trim()) {
alert('Please provide both an image and the correct text')
return
}
const formData = new FormData()
formData.append('file', trainingImage)
try {
const res = await fetch(`/api/klausur/trocr/training/add?ground_truth=${encodeURIComponent(trainingText)}`, {
method: 'POST',
body: formData,
})
const data = await res.json()
if (data.example_id) {
alert(`Training example added! Total: ${data.total_examples}`)
setTrainingImage(null)
setTrainingText('')
fetchStatus()
fetchExamples()
} else {
alert(`Error: ${data.detail || 'Unknown error'}`)
}
} catch (error) {
alert(`Error: ${error}`)
}
}
const handleFineTune = async () => {
if (!confirm('Start fine-tuning? This may take several minutes.')) return
setFineTuning(true)
try {
const res = await fetch('/api/klausur/trocr/training/fine-tune', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
epochs: settings.epochs,
learning_rate: settings.learningRate,
lora_rank: settings.loraRank,
lora_alpha: settings.loraAlpha,
}),
})
const data = await res.json()
if (data.status === 'success') {
alert(`Fine-tuning successful!\nExamples used: ${data.examples_used}\nEpochs: ${data.epochs}`)
fetchStatus()
} else {
alert(`Fine-tuning failed: ${data.message}`)
}
} catch (error) {
alert(`Error: ${error}`)
} finally {
setFineTuning(false)
}
}
const saveSettings = () => {
localStorage.setItem('magic-help-settings', JSON.stringify(settings))
setSettingsSaved(true)
setTimeout(() => setSettingsSaved(false), 2000)
}
const getStatusBadge = () => {
if (!status) return null
switch (status.status) {
case 'available':
return <span className="px-2 py-1 text-xs font-medium rounded-full bg-green-500/20 text-green-400">Available</span>
case 'not_installed':
return <span className="px-2 py-1 text-xs font-medium rounded-full bg-red-500/20 text-red-400">Not Installed</span>
case 'error':
return <span className="px-2 py-1 text-xs font-medium rounded-full bg-yellow-500/20 text-yellow-400">Error</span>
}
}
const tabs = [
{ id: 'overview' as TabId, label: 'Übersicht', icon: '📊' },
{ id: 'test' as TabId, label: 'OCR Test', icon: '🔍' },
{ id: 'training' as TabId, label: 'Training', icon: '🎯' },
{ id: 'architecture' as TabId, label: 'Architektur', icon: '🏗️' },
{ id: 'settings' as TabId, label: 'Einstellungen', icon: '⚙️' },
]
return (
<AdminLayout>
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-white flex items-center gap-2">
<span className="text-2xl"></span>
Magic Help - Handschrifterkennung
</h1>
<p className="text-gray-400 mt-1">
KI-gestützte Klausurkorrektur mit TrOCR und Privacy-by-Design
</p>
</div>
{getStatusBadge()}
</div>
{/* Tabs */}
<div className="flex gap-2 border-b border-gray-700 pb-2">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`px-4 py-2 rounded-t-lg text-sm font-medium transition-colors ${
activeTab === tab.id
? 'bg-gray-800 text-white border-b-2 border-blue-500'
: 'text-gray-400 hover:text-white hover:bg-gray-800/50'
}`}
>
<span className="mr-2">{tab.icon}</span>
{tab.label}
</button>
))}
</div>
{/* Tab Content */}
{activeTab === 'overview' && (
<div className="space-y-6">
{/* Status Card */}
<div className="bg-gray-800/50 border border-gray-700 rounded-xl p-6">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold text-white">Systemstatus</h2>
<button
onClick={fetchStatus}
className="px-3 py-1 bg-blue-600 hover:bg-blue-700 rounded text-sm transition-colors"
>
Aktualisieren
</button>
</div>
{loading ? (
<div className="text-gray-400">Lade Status...</div>
) : status?.status === 'available' ? (
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="bg-gray-900/50 rounded-lg p-4">
<div className="text-2xl font-bold text-white">{status.model_name || 'trocr-base'}</div>
<div className="text-xs text-gray-400">Modell</div>
</div>
<div className="bg-gray-900/50 rounded-lg p-4">
<div className="text-2xl font-bold text-white">{status.device || 'CPU'}</div>
<div className="text-xs text-gray-400">Gerät</div>
</div>
<div className="bg-gray-900/50 rounded-lg p-4">
<div className="text-2xl font-bold text-white">{status.training_examples_count || 0}</div>
<div className="text-xs text-gray-400">Trainingsbeispiele</div>
</div>
<div className="bg-gray-900/50 rounded-lg p-4">
<div className="text-2xl font-bold text-white">{status.has_lora_adapter ? 'Aktiv' : 'Keiner'}</div>
<div className="text-xs text-gray-400">LoRA Adapter</div>
</div>
</div>
) : status?.status === 'not_installed' ? (
<div className="text-gray-400">
<p className="mb-2">TrOCR ist nicht installiert. Führe aus:</p>
<code className="bg-gray-900 px-3 py-2 rounded text-sm block">{status.install_command}</code>
</div>
) : (
<div className="text-red-400">{status?.error || 'Unbekannter Fehler'}</div>
)}
</div>
{/* Quick Overview Cards */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="bg-gradient-to-br from-purple-900/30 to-purple-800/20 border border-purple-700/50 rounded-xl p-6">
<div className="text-3xl mb-2">🎯</div>
<h3 className="text-lg font-semibold text-white mb-2">Handschrifterkennung</h3>
<p className="text-sm text-gray-300">
TrOCR erkennt automatisch handgeschriebenen Text in Klausuren.
Das Modell wurde speziell für deutsche Handschriften optimiert.
</p>
</div>
<div className="bg-gradient-to-br from-green-900/30 to-green-800/20 border border-green-700/50 rounded-xl p-6">
<div className="text-3xl mb-2">🔒</div>
<h3 className="text-lg font-semibold text-white mb-2">Privacy by Design</h3>
<p className="text-sm text-gray-300">
Alle Daten werden lokal verarbeitet. Schülernamen werden durch
QR-Codes pseudonymisiert - DSGVO-konform.
</p>
</div>
<div className="bg-gradient-to-br from-blue-900/30 to-blue-800/20 border border-blue-700/50 rounded-xl p-6">
<div className="text-3xl mb-2">📈</div>
<h3 className="text-lg font-semibold text-white mb-2">Kontinuierliches Lernen</h3>
<p className="text-sm text-gray-300">
Mit LoRA Fine-Tuning passt sich das Modell an individuelle
Handschriften an - ohne das Basismodell zu verändern.
</p>
</div>
</div>
{/* Workflow Overview */}
<div className="bg-gray-800/50 border border-gray-700 rounded-xl p-6">
<h2 className="text-lg font-semibold text-white mb-4">Magic Onboarding Workflow</h2>
<div className="flex flex-wrap items-center gap-4 text-sm">
<div className="flex items-center gap-2 bg-gray-900/50 rounded-lg px-4 py-3">
<span className="text-2xl">📄</span>
<div>
<div className="font-medium text-white">1. Upload</div>
<div className="text-gray-400">25 Klausuren hochladen</div>
</div>
</div>
<div className="text-gray-600"></div>
<div className="flex items-center gap-2 bg-gray-900/50 rounded-lg px-4 py-3">
<span className="text-2xl">🔍</span>
<div>
<div className="font-medium text-white">2. Analyse</div>
<div className="text-gray-400">Lokale OCR in 5-10 Sek</div>
</div>
</div>
<div className="text-gray-600"></div>
<div className="flex items-center gap-2 bg-gray-900/50 rounded-lg px-4 py-3">
<span className="text-2xl"></span>
<div>
<div className="font-medium text-white">3. Bestätigung</div>
<div className="text-gray-400">Klasse, Schüler, Fach</div>
</div>
</div>
<div className="text-gray-600"></div>
<div className="flex items-center gap-2 bg-gray-900/50 rounded-lg px-4 py-3">
<span className="text-2xl">🤖</span>
<div>
<div className="font-medium text-white">4. KI-Korrektur</div>
<div className="text-gray-400">Cloud mit Pseudonymisierung</div>
</div>
</div>
<div className="text-gray-600"></div>
<div className="flex items-center gap-2 bg-gray-900/50 rounded-lg px-4 py-3">
<span className="text-2xl">📊</span>
<div>
<div className="font-medium text-white">5. Integration</div>
<div className="text-gray-400">Notenbuch, Zeugnisse</div>
</div>
</div>
</div>
</div>
</div>
)}
{activeTab === 'test' && (
<div className="space-y-6">
{/* OCR Test */}
<div className="bg-gray-800/50 border border-gray-700 rounded-xl p-6">
<h2 className="text-lg font-semibold text-white mb-4">OCR Test</h2>
<p className="text-sm text-gray-400 mb-4">
Teste die Handschrifterkennung mit einem eigenen Bild. Das Ergebnis zeigt
den erkannten Text, Konfidenz und Verarbeitungszeit.
</p>
<div
className="border-2 border-dashed border-gray-600 rounded-lg p-8 text-center cursor-pointer hover:border-blue-500 transition-colors"
onClick={() => document.getElementById('ocr-file-input')?.click()}
onDragOver={(e) => { e.preventDefault(); e.currentTarget.classList.add('border-blue-500') }}
onDragLeave={(e) => { e.currentTarget.classList.remove('border-blue-500') }}
onDrop={(e) => {
e.preventDefault()
e.currentTarget.classList.remove('border-blue-500')
const file = e.dataTransfer.files[0]
if (file?.type.startsWith('image/')) handleFileUpload(file)
}}
>
<div className="text-4xl mb-2">📄</div>
<div className="text-gray-300">Bild hierher ziehen oder klicken zum Hochladen</div>
<div className="text-xs text-gray-500 mt-1">PNG, JPG - Handgeschriebener Text</div>
</div>
<input
type="file"
id="ocr-file-input"
accept="image/*"
className="hidden"
onChange={(e) => {
const file = e.target.files?.[0]
if (file) handleFileUpload(file)
}}
/>
{ocrLoading && (
<div className="mt-4 flex items-center gap-2 text-gray-400">
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
Analysiere Bild...
</div>
)}
{ocrResult && (
<div className="mt-4 bg-gray-900/50 rounded-lg p-4">
<h3 className="text-sm font-medium text-gray-300 mb-2">Erkannter Text:</h3>
<pre className="bg-gray-950 p-3 rounded text-sm text-white whitespace-pre-wrap max-h-48 overflow-y-auto">
{ocrResult.text || '(Kein Text erkannt)'}
</pre>
<div className="mt-3 grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
<div className="bg-gray-800 rounded p-2">
<div className="text-gray-400 text-xs">Konfidenz</div>
<div className="text-white font-medium">{(ocrResult.confidence * 100).toFixed(1)}%</div>
</div>
<div className="bg-gray-800 rounded p-2">
<div className="text-gray-400 text-xs">Verarbeitungszeit</div>
<div className="text-white font-medium">{ocrResult.processing_time_ms}ms</div>
</div>
<div className="bg-gray-800 rounded p-2">
<div className="text-gray-400 text-xs">Modell</div>
<div className="text-white font-medium">{ocrResult.model || 'TrOCR'}</div>
</div>
<div className="bg-gray-800 rounded p-2">
<div className="text-gray-400 text-xs">LoRA Adapter</div>
<div className="text-white font-medium">{ocrResult.has_lora_adapter ? 'Ja' : 'Nein'}</div>
</div>
</div>
</div>
)}
</div>
{/* Confidence Interpretation */}
<div className="bg-gray-800/50 border border-gray-700 rounded-xl p-6">
<h2 className="text-lg font-semibold text-white mb-4">Konfidenz-Interpretation</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="bg-green-900/20 border border-green-800 rounded-lg p-4">
<div className="text-green-400 font-medium">90-100%</div>
<div className="text-sm text-gray-300 mt-1">Sehr hohe Sicherheit - Text kann direkt übernommen werden</div>
</div>
<div className="bg-yellow-900/20 border border-yellow-800 rounded-lg p-4">
<div className="text-yellow-400 font-medium">70-90%</div>
<div className="text-sm text-gray-300 mt-1">Gute Sicherheit - manuelle Überprüfung empfohlen</div>
</div>
<div className="bg-red-900/20 border border-red-800 rounded-lg p-4">
<div className="text-red-400 font-medium">&lt; 70%</div>
<div className="text-sm text-gray-300 mt-1">Niedrige Sicherheit - manuelle Eingabe erforderlich</div>
</div>
</div>
</div>
</div>
)}
{activeTab === 'training' && (
<div className="space-y-6">
{/* Training Overview */}
<div className="bg-gray-800/50 border border-gray-700 rounded-xl p-6">
<h2 className="text-lg font-semibold text-white mb-4">Training mit LoRA</h2>
<p className="text-sm text-gray-400 mb-4">
LoRA (Low-Rank Adaptation) ermöglicht effizientes Fine-Tuning ohne das Basismodell zu verändern.
Das Training erfolgt lokal auf Ihrem System.
</p>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
<div className="bg-gray-900/50 rounded-lg p-4 text-center">
<div className="text-3xl font-bold text-white">{status?.training_examples_count || 0}</div>
<div className="text-xs text-gray-400">Trainingsbeispiele</div>
</div>
<div className="bg-gray-900/50 rounded-lg p-4 text-center">
<div className="text-3xl font-bold text-white">10</div>
<div className="text-xs text-gray-400">Minimum benötigt</div>
</div>
<div className="bg-gray-900/50 rounded-lg p-4 text-center">
<div className="text-3xl font-bold text-white">{settings.loraRank}</div>
<div className="text-xs text-gray-400">LoRA Rank</div>
</div>
<div className="bg-gray-900/50 rounded-lg p-4 text-center">
<div className="text-3xl font-bold text-white">{status?.has_lora_adapter ? '✓' : '✗'}</div>
<div className="text-xs text-gray-400">Adapter aktiv</div>
</div>
</div>
{/* Progress Bar */}
<div className="mb-6">
<div className="flex justify-between text-sm mb-1">
<span className="text-gray-400">Fortschritt zum Fine-Tuning</span>
<span className="text-gray-400">{Math.min(100, ((status?.training_examples_count || 0) / 10) * 100).toFixed(0)}%</span>
</div>
<div className="h-2 bg-gray-700 rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-purple-500 to-blue-500 transition-all duration-500"
style={{ width: `${Math.min(100, ((status?.training_examples_count || 0) / 10) * 100)}%` }}
/>
</div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Add Training Example */}
<div className="bg-gray-800/50 border border-gray-700 rounded-xl p-6">
<h2 className="text-lg font-semibold text-white mb-4">Trainingsbeispiel hinzufügen</h2>
<p className="text-sm text-gray-400 mb-4">
Lade ein Bild mit handgeschriebenem Text hoch und gib die korrekte Transkription ein.
</p>
<div className="space-y-4">
<div>
<label className="block text-sm text-gray-300 mb-1">Bild</label>
<input
type="file"
accept="image/*"
className="w-full bg-gray-900 border border-gray-700 rounded-lg px-3 py-2 text-sm"
onChange={(e) => setTrainingImage(e.target.files?.[0] || null)}
/>
</div>
<div>
<label className="block text-sm text-gray-300 mb-1">Korrekter Text (Ground Truth)</label>
<textarea
className="w-full bg-gray-900 border border-gray-700 rounded-lg px-3 py-2 text-sm text-white resize-none"
rows={3}
placeholder="Gib hier den korrekten Text ein..."
value={trainingText}
onChange={(e) => setTrainingText(e.target.value)}
/>
</div>
<button
onClick={handleAddTrainingExample}
className="w-full px-4 py-2 bg-purple-600 hover:bg-purple-700 rounded-lg text-sm font-medium transition-colors"
>
+ Trainingsbeispiel hinzufügen
</button>
</div>
</div>
{/* Fine-Tuning */}
<div className="bg-gray-800/50 border border-gray-700 rounded-xl p-6">
<h2 className="text-lg font-semibold text-white mb-4">Fine-Tuning starten</h2>
<p className="text-sm text-gray-400 mb-4">
Trainiere das Modell mit den gesammelten Beispielen. Der Prozess dauert
je nach Anzahl der Beispiele einige Minuten.
</p>
<div className="bg-gray-900/50 rounded-lg p-4 mb-4">
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<span className="text-gray-400">Epochen:</span>
<span className="text-white ml-2">{settings.epochs}</span>
</div>
<div>
<span className="text-gray-400">Learning Rate:</span>
<span className="text-white ml-2">{settings.learningRate}</span>
</div>
<div>
<span className="text-gray-400">LoRA Rank:</span>
<span className="text-white ml-2">{settings.loraRank}</span>
</div>
<div>
<span className="text-gray-400">Batch Size:</span>
<span className="text-white ml-2">{settings.batchSize}</span>
</div>
</div>
</div>
<button
onClick={handleFineTune}
disabled={fineTuning || (status?.training_examples_count || 0) < 10}
className="w-full px-4 py-2 bg-green-600 hover:bg-green-700 disabled:bg-gray-700 disabled:cursor-not-allowed rounded-lg text-sm font-medium transition-colors"
>
{fineTuning ? (
<span className="flex items-center justify-center gap-2">
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
Fine-Tuning läuft...
</span>
) : (
'Fine-Tuning starten'
)}
</button>
{(status?.training_examples_count || 0) < 10 && (
<p className="text-xs text-yellow-400 mt-2 text-center">
Noch {10 - (status?.training_examples_count || 0)} Beispiele benötigt
</p>
)}
</div>
</div>
{/* Training Examples List */}
{examples.length > 0 && (
<div className="bg-gray-800/50 border border-gray-700 rounded-xl p-6">
<h2 className="text-lg font-semibold text-white mb-4">Trainingsbeispiele ({examples.length})</h2>
<div className="space-y-2 max-h-64 overflow-y-auto">
{examples.map((ex, i) => (
<div key={i} className="flex items-center gap-4 bg-gray-900/50 rounded-lg p-3">
<span className="text-gray-500 font-mono text-sm w-8">{i + 1}.</span>
<span className="text-white text-sm flex-1 truncate">{ex.ground_truth}</span>
<span className="text-gray-500 text-xs">{new Date(ex.created_at).toLocaleDateString('de-DE')}</span>
</div>
))}
</div>
</div>
)}
</div>
)}
{activeTab === 'architecture' && (
<div className="space-y-6">
{/* Architecture Diagram */}
<div className="bg-gray-800/50 border border-gray-700 rounded-xl p-6">
<h2 className="text-lg font-semibold text-white mb-6">Systemarchitektur</h2>
{/* ASCII Art Diagram */}
<div className="bg-gray-900 rounded-lg p-6 font-mono text-xs overflow-x-auto">
<pre className="text-gray-300">
{`┌─────────────────────────────────────────────────────────────────────────────┐
│ MAGIC HELP ARCHITEKTUR │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ ┌──────────────────┐ ┌───────────────┐ │
│ │ FRONTEND │ │ BACKEND │ │ STORAGE │ │
│ │ (Next.js) │ │ (FastAPI) │ │ │ │
│ │ │ │ │ │ │ │
│ │ ┌─────────┐ │ REST │ ┌────────────┐ │ │ ┌─────────┐ │ │
│ │ │ Admin │──┼─────────┼──│ TrOCR │ │ │ │ Models │ │ │
│ │ │ Panel │ │ │ │ Service │──┼─────────┼──│ (ONNX) │ │ │
│ │ └─────────┘ │ │ └────────────┘ │ │ └─────────┘ │ │
│ │ │ │ │ │ │ │ │
│ │ ┌─────────┐ │ WebSocket│ ┌────────────┐ │ │ ┌─────────┐ │ │
│ │ │ Lehrer │──┼─────────┼──│ Klausur │ │ │ │ LoRA │ │ │
│ │ │ Portal │ │ │ │ Processor │──┼─────────┼──│ Adapter │ │ │
│ │ └─────────┘ │ │ └────────────┘ │ │ └─────────┘ │ │
│ │ │ │ │ │ │ │ │
│ └───────────────┘ │ ┌────────────┐ │ │ ┌─────────┐ │ │
│ │ │ Pseudo- │ │ │ │Training │ │ │
│ │ │ nymizer │──┼─────────┼──│ Data │ │ │
│ │ └────────────┘ │ │ └─────────┘ │ │
│ │ │ │ │ │
│ └──────────────────┘ └───────────────┘ │
│ │ │
│ │ (nur pseudonymisiert) │
│ ▼ │
│ ┌──────────────────┐ │
│ │ CLOUD LLM │ │
│ │ (SysEleven) │ │
│ │ Namespace- │ │
│ │ Isolation │ │
│ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘`}
</pre>
</div>
</div>
{/* Components */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="bg-gray-800/50 border border-gray-700 rounded-xl p-6">
<h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
<span>🔍</span> TrOCR Service
</h3>
<div className="space-y-3 text-sm">
<div className="flex justify-between">
<span className="text-gray-400">Modell</span>
<span className="text-white">microsoft/trocr-base-handwritten</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Größe</span>
<span className="text-white">~350 MB</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Lizenz</span>
<span className="text-white">MIT</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Framework</span>
<span className="text-white">PyTorch / Transformers</span>
</div>
</div>
<p className="text-gray-400 text-sm mt-4">
Das TrOCR-Modell von Microsoft ist speziell für Handschrifterkennung trainiert.
Es verwendet eine Vision-Transformer (ViT) Architektur für Bildverarbeitung
und einen Text-Decoder für die Textgenerierung.
</p>
</div>
<div className="bg-gray-800/50 border border-gray-700 rounded-xl p-6">
<h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
<span>🎯</span> LoRA Fine-Tuning
</h3>
<div className="space-y-3 text-sm">
<div className="flex justify-between">
<span className="text-gray-400">Methode</span>
<span className="text-white">Low-Rank Adaptation</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Adapter-Größe</span>
<span className="text-white">~10 MB</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Trainingszeit</span>
<span className="text-white">5-15 Min (CPU)</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Min. Beispiele</span>
<span className="text-white">10</span>
</div>
</div>
<p className="text-gray-400 text-sm mt-4">
LoRA fügt kleine, trainierbare Matrizen zu bestimmten Schichten hinzu,
ohne das Basismodell zu verändern. Dies ermöglicht effizientes Fine-Tuning
mit minimaler Speichernutzung.
</p>
</div>
<div className="bg-gray-800/50 border border-gray-700 rounded-xl p-6">
<h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
<span>🔒</span> Pseudonymisierung
</h3>
<div className="space-y-3 text-sm">
<div className="flex justify-between">
<span className="text-gray-400">Methode</span>
<span className="text-white">QR-Code Tokens</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Token-Format</span>
<span className="text-white">UUID v4</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Mapping</span>
<span className="text-white">Lokal beim Lehrer</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Cloud-Daten</span>
<span className="text-white">Nur Tokens + Text</span>
</div>
</div>
<p className="text-gray-400 text-sm mt-4">
Schülernamen werden durch anonyme Tokens ersetzt, bevor Daten die lokale
Umgebung verlassen. Das Mapping wird ausschließlich lokal gespeichert.
</p>
</div>
<div className="bg-gray-800/50 border border-gray-700 rounded-xl p-6">
<h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
<span></span> Cloud LLM
</h3>
<div className="space-y-3 text-sm">
<div className="flex justify-between">
<span className="text-gray-400">Provider</span>
<span className="text-white">SysEleven (DE)</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Standort</span>
<span className="text-white">Deutschland</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Isolation</span>
<span className="text-white">Namespace pro Schule</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Datenverarbeitung</span>
<span className="text-white">Nur pseudonymisiert</span>
</div>
</div>
<p className="text-gray-400 text-sm mt-4">
Die KI-Korrektur erfolgt auf deutschen Servern mit strikter Mandantentrennung.
Es werden keine Klarnamen oder identifizierenden Informationen übertragen.
</p>
</div>
</div>
{/* Data Flow */}
<div className="bg-gray-800/50 border border-gray-700 rounded-xl p-6">
<h2 className="text-lg font-semibold text-white mb-4">Datenfluss</h2>
<div className="space-y-4">
<div className="flex items-start gap-4 bg-gray-900/50 rounded-lg p-4">
<div className="w-8 h-8 rounded-full bg-blue-500/20 flex items-center justify-center text-blue-400 font-bold">1</div>
<div>
<div className="font-medium text-white">Lokale Header-Extraktion</div>
<div className="text-sm text-gray-400">TrOCR erkennt Schülernamen, Klasse und Fach direkt im Browser/PWA (offline-fähig)</div>
</div>
</div>
<div className="flex items-start gap-4 bg-gray-900/50 rounded-lg p-4">
<div className="w-8 h-8 rounded-full bg-purple-500/20 flex items-center justify-center text-purple-400 font-bold">2</div>
<div>
<div className="font-medium text-white">Pseudonymisierung</div>
<div className="text-sm text-gray-400">Namen werden durch QR-Code Tokens ersetzt, Mapping bleibt lokal</div>
</div>
</div>
<div className="flex items-start gap-4 bg-gray-900/50 rounded-lg p-4">
<div className="w-8 h-8 rounded-full bg-green-500/20 flex items-center justify-center text-green-400 font-bold">3</div>
<div>
<div className="font-medium text-white">Cloud-Korrektur</div>
<div className="text-sm text-gray-400">Nur pseudonymisierte Dokument-Tokens werden an die KI gesendet</div>
</div>
</div>
<div className="flex items-start gap-4 bg-gray-900/50 rounded-lg p-4">
<div className="w-8 h-8 rounded-full bg-yellow-500/20 flex items-center justify-center text-yellow-400 font-bold">4</div>
<div>
<div className="font-medium text-white">Re-Identifikation</div>
<div className="text-sm text-gray-400">Ergebnisse werden lokal mit dem Mapping wieder den echten Namen zugeordnet</div>
</div>
</div>
</div>
</div>
</div>
)}
{activeTab === 'settings' && (
<div className="space-y-6">
{/* OCR Settings */}
<div className="bg-gray-800/50 border border-gray-700 rounded-xl p-6">
<h2 className="text-lg font-semibold text-white mb-4">OCR Einstellungen</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
checked={settings.autoDetectLines}
onChange={(e) => setSettings({ ...settings, autoDetectLines: e.target.checked })}
className="w-5 h-5 rounded bg-gray-900 border-gray-700"
/>
<div>
<div className="text-white font-medium">Automatische Zeilenerkennung</div>
<div className="text-sm text-gray-400">Erkennt und verarbeitet einzelne Zeilen separat</div>
</div>
</label>
</div>
<div>
<label className="block text-sm text-gray-300 mb-2">Konfidenz-Schwellwert</label>
<input
type="range"
min="0"
max="1"
step="0.1"
value={settings.confidenceThreshold}
onChange={(e) => setSettings({ ...settings, confidenceThreshold: parseFloat(e.target.value) })}
className="w-full"
/>
<div className="flex justify-between text-xs text-gray-500 mt-1">
<span>0%</span>
<span className="text-white">{(settings.confidenceThreshold * 100).toFixed(0)}%</span>
<span>100%</span>
</div>
</div>
<div>
<label className="block text-sm text-gray-300 mb-2">Max. Bildgröße (px)</label>
<input
type="number"
value={settings.maxImageSize}
onChange={(e) => setSettings({ ...settings, maxImageSize: parseInt(e.target.value) })}
className="w-full bg-gray-900 border border-gray-700 rounded-lg px-3 py-2 text-white"
/>
<div className="text-xs text-gray-500 mt-1">Größere Bilder werden skaliert</div>
</div>
<div>
<label className="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
checked={settings.enableCache}
onChange={(e) => setSettings({ ...settings, enableCache: e.target.checked })}
className="w-5 h-5 rounded bg-gray-900 border-gray-700"
/>
<div>
<div className="text-white font-medium">Ergebnis-Cache aktivieren</div>
<div className="text-sm text-gray-400">Speichert OCR-Ergebnisse für identische Bilder</div>
</div>
</label>
</div>
</div>
</div>
{/* Training Settings */}
<div className="bg-gray-800/50 border border-gray-700 rounded-xl p-6">
<h2 className="text-lg font-semibold text-white mb-4">Training Einstellungen</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm text-gray-300 mb-2">LoRA Rank</label>
<select
value={settings.loraRank}
onChange={(e) => setSettings({ ...settings, loraRank: parseInt(e.target.value) })}
className="w-full bg-gray-900 border border-gray-700 rounded-lg px-3 py-2 text-white"
>
<option value="4">4 (Schnell, weniger Kapazität)</option>
<option value="8">8 (Ausgewogen)</option>
<option value="16">16 (Mehr Kapazität)</option>
<option value="32">32 (Maximum)</option>
</select>
</div>
<div>
<label className="block text-sm text-gray-300 mb-2">LoRA Alpha</label>
<input
type="number"
value={settings.loraAlpha}
onChange={(e) => setSettings({ ...settings, loraAlpha: parseInt(e.target.value) })}
className="w-full bg-gray-900 border border-gray-700 rounded-lg px-3 py-2 text-white"
/>
<div className="text-xs text-gray-500 mt-1">Empfohlen: 4 × LoRA Rank</div>
</div>
<div>
<label className="block text-sm text-gray-300 mb-2">Epochen</label>
<input
type="number"
min="1"
max="10"
value={settings.epochs}
onChange={(e) => setSettings({ ...settings, epochs: parseInt(e.target.value) })}
className="w-full bg-gray-900 border border-gray-700 rounded-lg px-3 py-2 text-white"
/>
</div>
<div>
<label className="block text-sm text-gray-300 mb-2">Batch Size</label>
<select
value={settings.batchSize}
onChange={(e) => setSettings({ ...settings, batchSize: parseInt(e.target.value) })}
className="w-full bg-gray-900 border border-gray-700 rounded-lg px-3 py-2 text-white"
>
<option value="1">1 (Wenig RAM)</option>
<option value="2">2</option>
<option value="4">4 (Standard)</option>
<option value="8">8 (Viel RAM)</option>
</select>
</div>
<div>
<label className="block text-sm text-gray-300 mb-2">Learning Rate</label>
<select
value={settings.learningRate}
onChange={(e) => setSettings({ ...settings, learningRate: parseFloat(e.target.value) })}
className="w-full bg-gray-900 border border-gray-700 rounded-lg px-3 py-2 text-white"
>
<option value="0.0001">0.0001 (Schnell)</option>
<option value="0.00005">0.00005 (Standard)</option>
<option value="0.00001">0.00001 (Konservativ)</option>
</select>
</div>
</div>
</div>
{/* Save Button */}
<div className="flex justify-end gap-4">
<button
onClick={() => setSettings(DEFAULT_SETTINGS)}
className="px-6 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg text-sm font-medium transition-colors"
>
Zurücksetzen
</button>
<button
onClick={saveSettings}
className="px-6 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg text-sm font-medium transition-colors"
>
{settingsSaved ? '✓ Gespeichert!' : 'Einstellungen speichern'}
</button>
</div>
{/* Technical Info */}
<div className="bg-gray-800/50 border border-gray-700 rounded-xl p-6">
<h2 className="text-lg font-semibold text-white mb-4">Technische Informationen</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
<div>
<span className="text-gray-400">API Endpoint:</span>
<code className="text-white ml-2 bg-gray-900 px-2 py-1 rounded text-xs">/api/klausur/trocr</code>
</div>
<div>
<span className="text-gray-400">Model Path:</span>
<code className="text-white ml-2 bg-gray-900 px-2 py-1 rounded text-xs">~/.cache/huggingface</code>
</div>
<div>
<span className="text-gray-400">LoRA Path:</span>
<code className="text-white ml-2 bg-gray-900 px-2 py-1 rounded text-xs">./models/lora</code>
</div>
<div>
<span className="text-gray-400">Training Data:</span>
<code className="text-white ml-2 bg-gray-900 px-2 py-1 rounded text-xs">./data/training</code>
</div>
</div>
</div>
</div>
)}
</div>
</AdminLayout>
)
}