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, detectorScript,
"--root", root, "--root", root,
"--base", "yolo11n.pt", "--base", "yolo11n.pt",
"--epochs", "80", "--epochs", "20",
"--imgsz", "640", "--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() { export default function TrainingTab() {
const [labels, setLabels] = useState<TrainingLabels>(emptyLabels) const [labels, setLabels] = useState<TrainingLabels>(emptyLabels)
const [sample, setSample] = useState<TrainingSample | null>(null) const [sample, setSample] = useState<TrainingSample | null>(null)
@ -353,12 +384,9 @@ export default function TrainingTab() {
if (cancelled) return if (cancelled) return
const res = await fetch('/api/training/status', { cache: 'no-store' }) // Wichtig: Auch während laufendem Training wieder das aktuelle offene Sample laden,
const data = await res.json().catch(() => null) // damit nicht "Kein Frame geladen" angezeigt wird.
await loadNext()
if (!cancelled && !data?.training?.running) {
await loadNext()
}
} }
void init() void init()
@ -819,22 +847,6 @@ export default function TrainingTab() {
{trainingRunning ? 'Training läuft…' : 'Training starten'} {trainingRunning ? 'Training läuft…' : 'Training starten'}
</Button> </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"> <div className="text-[11px] leading-relaxed text-gray-500 dark:text-gray-400">
{canStartTraining ? ( {canStartTraining ? (
<> <>
@ -865,14 +877,7 @@ export default function TrainingTab() {
{/* Mitte */} {/* 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"> <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"> <div className="relative flex min-h-[360px] flex-1 items-center justify-center overflow-hidden rounded-lg bg-black">
{loading ? ( {imageSrc ? (
<LoadingSpinner
size="lg"
center
className="text-white/80"
srLabel="Frame wird geladen…"
/>
) : imageSrc ? (
<div className="relative flex h-full w-full items-center justify-center"> <div className="relative flex h-full w-full items-center justify-center">
<div <div
ref={imageBoxRef} ref={imageBoxRef}
@ -926,7 +931,7 @@ export default function TrainingTab() {
? 'cursor-default bg-amber-400' ? 'cursor-default bg-amber-400'
: 'cursor-pointer bg-emerald-400 hover:bg-red-400', : 'cursor-pointer bg-emerald-400 hover:bg-red-400',
].join(' ')} ].join(' ')}
disabled={Boolean(isDraft)} disabled={Boolean(isDraft) || uiLocked}
title={isDraft ? box.label : `${box.label} löschen`} title={isDraft ? box.label : `${box.label} löschen`}
onPointerDown={(e) => { onPointerDown={(e) => {
e.preventDefault() e.preventDefault()
@ -935,7 +940,7 @@ export default function TrainingTab() {
onClick={(e) => { onClick={(e) => {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
if (!isDraft) removeBox(index) if (!isDraft && !uiLocked) removeBox(index)
}} }}
> >
<span>{box.label}</span> <span>{box.label}</span>
@ -951,35 +956,26 @@ export default function TrainingTab() {
})} })}
</div> </div>
</div> </div>
{trainingRunning ? ( {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]"> <TrainingOverlay
<LoadingSpinner step={shownTrainingStep}
size="lg" progress={shownTrainingProgress}
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">
{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} ) : null}
</div> </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> <div className="text-sm text-white/80">Kein Frame geladen</div>
)} )}