Files
breakpilot-lehrer/website/app/admin/magic-help/_components/TrainingTab.tsx
Benjamin Admin 6811264756 [split-required] Split final batch of monoliths >1000 LOC
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>
2026-04-24 23:17:30 +02:00

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>
)
}