This commit is contained in:
Linrador 2026-04-29 15:03:07 +02:00
parent b1618fd218
commit b6ad070ee4
2 changed files with 55 additions and 59 deletions

View File

@ -536,7 +536,7 @@ func trainingRunJob(root string, count int) {
detectorScript,
"--root", root,
"--base", "yolo11n.pt",
"--epochs", "80",
"--epochs", "20",
"--imgsz", "640",
)

View File

@ -213,6 +213,37 @@ function sortTrainingLabels(input: Partial<TrainingLabels> | null | undefined):
}
}
function TrainingOverlay(props: { step: string; progress: number }) {
return (
<div className="absolute inset-0 z-20 flex flex-col items-center justify-center bg-black/45 text-center text-white backdrop-blur-[1px]">
<LoadingSpinner
size="lg"
className="text-white"
srLabel="Training läuft…"
/>
<div className="mt-3 text-sm font-semibold">
Training läuft
</div>
<div className="mt-1 max-w-[260px] px-4 text-xs text-white/80">
{props.step || 'Bitte warten. Die Oberfläche ist währenddessen gesperrt.'}
</div>
<div className="mt-3 h-2 w-48 overflow-hidden rounded-full bg-white/20">
<div
className="h-full rounded-full bg-emerald-400 transition-all duration-500"
style={{ width: `${clampPercent(props.progress)}%` }}
/>
</div>
<div className="mt-1 text-[11px] text-white/70">
{Math.round(props.progress)}%
</div>
</div>
)
}
export default function TrainingTab() {
const [labels, setLabels] = useState<TrainingLabels>(emptyLabels)
const [sample, setSample] = useState<TrainingSample | null>(null)
@ -353,13 +384,10 @@ export default function TrainingTab() {
if (cancelled) return
const res = await fetch('/api/training/status', { cache: 'no-store' })
const data = await res.json().catch(() => null)
if (!cancelled && !data?.training?.running) {
// Wichtig: Auch während laufendem Training wieder das aktuelle offene Sample laden,
// damit nicht "Kein Frame geladen" angezeigt wird.
await loadNext()
}
}
void init()
@ -819,22 +847,6 @@ export default function TrainingTab() {
{trainingRunning ? 'Training läuft…' : 'Training starten'}
</Button>
{trainingRunning ? (
<div className="rounded-lg bg-gray-50 p-2 text-xs ring-1 ring-black/5 dark:bg-white/5 dark:ring-white/10">
<div className="flex items-center justify-between gap-2 text-[11px] text-gray-600 dark:text-gray-300">
<span>{shownTrainingStep || 'Training läuft…'}</span>
<span>{Math.round(shownTrainingProgress)}%</span>
</div>
<div className="mt-2 h-2 overflow-hidden rounded-full bg-gray-200 dark:bg-white/10">
<div
className="h-full rounded-full bg-emerald-500 transition-all duration-500"
style={{ width: `${clampPercent(shownTrainingProgress)}%` }}
/>
</div>
</div>
) : null}
<div className="text-[11px] leading-relaxed text-gray-500 dark:text-gray-400">
{canStartTraining ? (
<>
@ -865,14 +877,7 @@ export default function TrainingTab() {
{/* Mitte */}
<section className="min-w-0 rounded-xl border border-gray-200 bg-white p-3 shadow-sm dark:border-white/10 dark:bg-gray-900/60">
<div className="relative flex min-h-[360px] flex-1 items-center justify-center overflow-hidden rounded-lg bg-black">
{loading ? (
<LoadingSpinner
size="lg"
center
className="text-white/80"
srLabel="Frame wird geladen…"
/>
) : imageSrc ? (
{imageSrc ? (
<div className="relative flex h-full w-full items-center justify-center">
<div
ref={imageBoxRef}
@ -926,7 +931,7 @@ export default function TrainingTab() {
? 'cursor-default bg-amber-400'
: 'cursor-pointer bg-emerald-400 hover:bg-red-400',
].join(' ')}
disabled={Boolean(isDraft)}
disabled={Boolean(isDraft) || uiLocked}
title={isDraft ? box.label : `${box.label} löschen`}
onPointerDown={(e) => {
e.preventDefault()
@ -935,7 +940,7 @@ export default function TrainingTab() {
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
if (!isDraft) removeBox(index)
if (!isDraft && !uiLocked) removeBox(index)
}}
>
<span>{box.label}</span>
@ -951,35 +956,26 @@ export default function TrainingTab() {
})}
</div>
</div>
{trainingRunning ? (
<div className="absolute inset-0 z-20 flex flex-col items-center justify-center bg-black/45 text-center text-white backdrop-blur-[1px]">
<LoadingSpinner
size="lg"
className="text-white"
srLabel="Training läuft…"
<TrainingOverlay
step={shownTrainingStep}
progress={shownTrainingProgress}
/>
<div className="mt-3 text-sm font-semibold">
Training läuft
</div>
<div className="mt-1 max-w-[260px] px-4 text-xs text-white/80">
{shownTrainingStep || 'Bitte warten. Die Oberfläche ist währenddessen gesperrt.'}
</div>
<div className="mt-3 h-2 w-48 overflow-hidden rounded-full bg-white/20">
<div
className="h-full rounded-full bg-emerald-400 transition-all duration-500"
style={{ width: `${clampPercent(shownTrainingProgress)}%` }}
/>
</div>
<div className="mt-1 text-[11px] text-white/70">
{Math.round(shownTrainingProgress)}%
</div>
</div>
) : null}
</div>
) : trainingRunning ? (
<TrainingOverlay
step={shownTrainingStep || 'Aktuelles Bild wird geladen…'}
progress={shownTrainingProgress}
/>
) : loading ? (
<LoadingSpinner
size="lg"
center
className="text-white/80"
srLabel="Frame wird geladen…"
/>
) : (
<div className="text-sm text-white/80">Kein Frame geladen</div>
)}