UI fixes
This commit is contained in:
parent
b1618fd218
commit
b6ad070ee4
@ -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",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user