Python (6 files in klausur-service): - rbac.py (1,132 → 4), admin_api.py (1,012 → 4) - routes/eh.py (1,111 → 4), ocr_pipeline_geometry.py (1,105 → 5) Python (2 files in backend-lehrer): - unit_api.py (1,226 → 6), game_api.py (1,129 → 5) Website (6 page files): - 4x klausur-korrektur pages (1,249-1,328 LOC each) → shared components in website/components/klausur-korrektur/ (17 shared files) - companion (1,057 → 10), magic-help (1,017 → 8) All re-export barrels preserve backward compatibility. Zero import errors verified. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
183 lines
7.4 KiB
TypeScript
183 lines
7.4 KiB
TypeScript
'use client'
|
|
|
|
import type { TrOCRStatus, TrainingExample, MagicSettings } from './types'
|
|
|
|
interface TrainingTabProps {
|
|
status: TrOCRStatus | null
|
|
examples: TrainingExample[]
|
|
trainingImage: File | null
|
|
setTrainingImage: (file: File | null) => void
|
|
trainingText: string
|
|
setTrainingText: (text: string) => void
|
|
fineTuning: boolean
|
|
settings: MagicSettings
|
|
handleAddTrainingExample: () => void
|
|
handleFineTune: () => void
|
|
}
|
|
|
|
export default function TrainingTab({
|
|
status,
|
|
examples,
|
|
trainingImage,
|
|
setTrainingImage,
|
|
trainingText,
|
|
setTrainingText,
|
|
fineTuning,
|
|
settings,
|
|
handleAddTrainingExample,
|
|
handleFineTune,
|
|
}: TrainingTabProps) {
|
|
const examplesCount = status?.training_examples_count || 0
|
|
|
|
return (
|
|
<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">{examplesCount}</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, (examplesCount / 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, (examplesCount / 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 || examplesCount < 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>
|
|
|
|
{examplesCount < 10 && (
|
|
<p className="text-xs text-yellow-400 mt-2 text-center">
|
|
Noch {10 - examplesCount} 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>
|
|
)
|
|
}
|