Files
breakpilot-lehrer/website/app/admin/training/_components/NewTrainingModal.tsx
Benjamin Admin 0b37c5e692 [split-required] Split website + studio-v2 monoliths (Phase 3 continued)
Website (14 monoliths split):
- compliance/page.tsx (1,519 → 9), docs/audit (1,262 → 20)
- quality (1,231 → 16), alerts (1,203 → 10), docs (1,202 → 11)
- i18n.ts (1,173 → 8 language files)
- unity-bridge (1,094 → 12), backlog (1,087 → 6)
- training (1,066 → 8), rag (1,063 → 8)
- Deleted index_original.ts (4,899 LOC dead backup)

Studio-v2 (5 monoliths split):
- meet/page.tsx (1,481 → 9), messages (1,166 → 9)
- AlertsB2BContext.tsx (1,165 → 5 modules)
- alerts-b2b/page.tsx (1,019 → 6), korrektur/archiv (1,001 → 6)

All existing imports preserved. Zero new TypeScript errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 17:52:36 +02:00

231 lines
11 KiB
TypeScript

'use client'
import { useState } from 'react'
import type { TrainingConfig } from './types'
const BUNDESLAENDER = [
{ code: 'ni', name: 'Niedersachsen', allowed: true },
{ code: 'by', name: 'Bayern', allowed: true },
{ code: 'nw', name: 'NRW', allowed: true },
{ code: 'he', name: 'Hessen', allowed: true },
{ code: 'bw', name: 'Baden-Württemberg', allowed: true },
{ code: 'rp', name: 'Rheinland-Pfalz', allowed: true },
{ code: 'sn', name: 'Sachsen', allowed: true },
{ code: 'sh', name: 'Schleswig-Holstein', allowed: true },
{ code: 'th', name: 'Thüringen', allowed: true },
{ code: 'be', name: 'Berlin', allowed: false },
{ code: 'bb', name: 'Brandenburg', allowed: false },
{ code: 'hb', name: 'Bremen', allowed: false },
{ code: 'hh', name: 'Hamburg', allowed: false },
{ code: 'mv', name: 'Mecklenburg-Vorpommern', allowed: false },
{ code: 'sl', name: 'Saarland', allowed: false },
{ code: 'st', name: 'Sachsen-Anhalt', allowed: false },
]
export function NewTrainingModal({ isOpen, onClose, onSubmit }: {
isOpen: boolean
onClose: () => void
onSubmit: (config: Partial<TrainingConfig>) => void
}) {
const [step, setStep] = useState(1)
const [config, setConfig] = useState<Partial<TrainingConfig>>({
batch_size: 16,
learning_rate: 0.00005,
epochs: 10,
warmup_steps: 500,
weight_decay: 0.01,
gradient_accumulation: 4,
mixed_precision: true,
bundeslaender: [],
})
if (!isOpen) return null
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
<div className="bg-white dark:bg-gray-800 rounded-2xl shadow-2xl w-full max-w-2xl max-h-[90vh] overflow-hidden">
{/* Header */}
<div className="px-6 py-4 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center">
<div>
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
Neues Training starten
</h2>
<p className="text-sm text-gray-500">Schritt {step} von 3</p>
</div>
<button onClick={onClose} className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{/* Progress Steps */}
<div className="px-6 py-4 bg-gray-50 dark:bg-gray-900">
<div className="flex items-center justify-center gap-4">
{[1, 2, 3].map((s) => (
<div key={s} className="flex items-center">
<div className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium ${
s <= step ? 'bg-blue-600 text-white' : 'bg-gray-200 dark:bg-gray-700 text-gray-500'
}`}>
{s < step ? '✓' : s}
</div>
{s < 3 && (
<div className={`w-16 h-1 mx-2 rounded ${s < step ? 'bg-blue-600' : 'bg-gray-200 dark:bg-gray-700'}`} />
)}
</div>
))}
</div>
<div className="flex justify-center gap-20 mt-2 text-xs text-gray-500">
<span>Daten</span>
<span>Parameter</span>
<span>Bestätigen</span>
</div>
</div>
{/* Content */}
<div className="p-6 overflow-y-auto max-h-[50vh]">
{step === 1 && (
<BundeslandStep config={config} setConfig={setConfig} />
)}
{step === 2 && (
<ParameterStep config={config} setConfig={setConfig} />
)}
{step === 3 && (
<ConfirmStep config={config} />
)}
</div>
{/* Footer */}
<div className="px-6 py-4 border-t border-gray-200 dark:border-gray-700 flex justify-between">
<button
onClick={() => step > 1 ? setStep(step - 1) : onClose()}
className="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700"
>
{step > 1 ? 'Zurück' : 'Abbrechen'}
</button>
<button
onClick={() => step < 3 ? setStep(step + 1) : onSubmit(config)}
disabled={step === 1 && (!config.bundeslaender || config.bundeslaender.length === 0)}
className="px-6 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
>
{step < 3 ? 'Weiter' : 'Training starten'}
</button>
</div>
</div>
</div>
)
}
function BundeslandStep({ config, setConfig }: {
config: Partial<TrainingConfig>
setConfig: (c: Partial<TrainingConfig>) => void
}) {
return (
<div>
<h3 className="font-medium text-gray-900 dark:text-white mb-4">
Wählen Sie die Bundesländer für das Training
</h3>
<p className="text-sm text-gray-500 dark:text-gray-400 mb-4">
Nur Bundesländer mit Training-Erlaubnis können ausgewählt werden.
</p>
<div className="grid grid-cols-2 gap-3">
{BUNDESLAENDER.map((bl) => (
<label
key={bl.code}
className={`flex items-center p-3 rounded-lg border-2 transition cursor-pointer ${
config.bundeslaender?.includes(bl.code)
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
: bl.allowed
? 'border-gray-200 dark:border-gray-700 hover:border-blue-300'
: 'border-gray-200 dark:border-gray-700 opacity-50 cursor-not-allowed'
}`}
>
<input
type="checkbox"
disabled={!bl.allowed}
checked={config.bundeslaender?.includes(bl.code)}
onChange={(e) => {
if (e.target.checked) {
setConfig({ ...config, bundeslaender: [...(config.bundeslaender || []), bl.code] })
} else {
setConfig({ ...config, bundeslaender: config.bundeslaender?.filter(c => c !== bl.code) })
}
}}
className="sr-only"
/>
<span className={`w-5 h-5 rounded border-2 flex items-center justify-center mr-3 ${
config.bundeslaender?.includes(bl.code)
? 'bg-blue-500 border-blue-500 text-white'
: 'border-gray-300 dark:border-gray-600'
}`}>
{config.bundeslaender?.includes(bl.code) && '✓'}
</span>
<span className="flex-1 text-gray-900 dark:text-white">{bl.name}</span>
{!bl.allowed && (
<span className="text-xs text-red-500">Kein Training</span>
)}
</label>
))}
</div>
</div>
)
}
function ParameterStep({ config, setConfig }: {
config: Partial<TrainingConfig>
setConfig: (c: Partial<TrainingConfig>) => void
}) {
return (
<div className="space-y-6">
<h3 className="font-medium text-gray-900 dark:text-white mb-4">
Training-Parameter konfigurieren
</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Batch Size</label>
<input type="number" value={config.batch_size} onChange={(e) => setConfig({ ...config, batch_size: parseInt(e.target.value) })} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700" />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Learning Rate</label>
<input type="number" step="0.00001" value={config.learning_rate} onChange={(e) => setConfig({ ...config, learning_rate: parseFloat(e.target.value) })} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700" />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Epochen</label>
<input type="number" value={config.epochs} onChange={(e) => setConfig({ ...config, epochs: parseInt(e.target.value) })} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700" />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Warmup Steps</label>
<input type="number" value={config.warmup_steps} onChange={(e) => setConfig({ ...config, warmup_steps: parseInt(e.target.value) })} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700" />
</div>
</div>
<div className="flex items-center gap-3 p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
<input type="checkbox" id="mixedPrecision" checked={config.mixed_precision} onChange={(e) => setConfig({ ...config, mixed_precision: e.target.checked })} className="w-4 h-4 text-blue-600 rounded" />
<label htmlFor="mixedPrecision" className="text-sm text-gray-700 dark:text-gray-300">
Mixed Precision Training (FP16) - schneller und speichereffizienter
</label>
</div>
</div>
)
}
function ConfirmStep({ config }: { config: Partial<TrainingConfig> }) {
return (
<div>
<h3 className="font-medium text-gray-900 dark:text-white mb-4">Training-Konfiguration bestätigen</h3>
<div className="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 space-y-3">
<div className="flex justify-between"><span className="text-gray-600 dark:text-gray-400">Bundesländer</span><span className="font-medium text-gray-900 dark:text-white">{config.bundeslaender?.length || 0} ausgewählt</span></div>
<div className="flex justify-between"><span className="text-gray-600 dark:text-gray-400">Epochen</span><span className="font-medium text-gray-900 dark:text-white">{config.epochs}</span></div>
<div className="flex justify-between"><span className="text-gray-600 dark:text-gray-400">Batch Size</span><span className="font-medium text-gray-900 dark:text-white">{config.batch_size}</span></div>
<div className="flex justify-between"><span className="text-gray-600 dark:text-gray-400">Learning Rate</span><span className="font-medium text-gray-900 dark:text-white">{config.learning_rate}</span></div>
<div className="flex justify-between"><span className="text-gray-600 dark:text-gray-400">Mixed Precision</span><span className="font-medium text-gray-900 dark:text-white">{config.mixed_precision ? 'Aktiviert' : 'Deaktiviert'}</span></div>
</div>
<div className="mt-4 p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg">
<p className="text-sm text-yellow-800 dark:text-yellow-200">
<strong>Hinweis:</strong> Das Training kann je nach Datenmenge und Konfiguration
mehrere Stunden dauern. Sie können den Fortschritt jederzeit überwachen.
</p>
</div>
</div>
)
}