added analysis progress
This commit is contained in:
parent
ba4d5ba255
commit
fbac1c6327
@ -197,7 +197,7 @@ function TrainingNoticeCard(props: {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={[
|
className={[
|
||||||
'rounded-2xl border px-4 py-3 shadow-sm',
|
'rounded-xl border px-3 py-2 shadow-sm',
|
||||||
cls.wrap,
|
cls.wrap,
|
||||||
].join(' ')}
|
].join(' ')}
|
||||||
role={props.notice.kind === 'error' ? 'alert' : 'status'}
|
role={props.notice.kind === 'error' ? 'alert' : 'status'}
|
||||||
@ -219,7 +219,7 @@ function TrainingNoticeCard(props: {
|
|||||||
{props.notice.title}
|
{props.notice.title}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-0.5 break-words text-sm leading-relaxed">
|
<div className="mt-0.5 break-words text-xs leading-snug">
|
||||||
{props.notice.message}
|
{props.notice.message}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -896,7 +896,12 @@ function TrainingOverlay(props: { step: string; progress: number }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function LoadingImageOverlay(props: { text?: string }) {
|
function LoadingImageOverlay(props: {
|
||||||
|
text?: string
|
||||||
|
progress?: number
|
||||||
|
}) {
|
||||||
|
const progress = clampPercent(props.progress ?? 0)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="absolute inset-0 z-20 flex items-center justify-center text-center text-white">
|
<div className="absolute inset-0 z-20 flex items-center justify-center text-center text-white">
|
||||||
<div className="absolute -inset-2 bg-black/45 backdrop-blur-[8px] shadow-[inset_0_0_72px_30px_rgba(0,0,0,0.75)]" />
|
<div className="absolute -inset-2 bg-black/45 backdrop-blur-[8px] shadow-[inset_0_0_72px_30px_rgba(0,0,0,0.75)]" />
|
||||||
@ -905,16 +910,27 @@ function LoadingImageOverlay(props: { text?: string }) {
|
|||||||
<LoadingSpinner
|
<LoadingSpinner
|
||||||
size="lg"
|
size="lg"
|
||||||
className="text-white"
|
className="text-white"
|
||||||
srLabel="Bild wird geladen…"
|
srLabel="Analyse läuft…"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="mt-3 text-sm font-semibold">
|
<div className="mt-3 text-sm font-semibold">
|
||||||
Bild wird geladen…
|
Analyse läuft…
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-1 max-w-[260px] px-4 text-xs text-white/80">
|
<div className="mt-1 max-w-[260px] px-4 text-xs text-white/80">
|
||||||
{props.text || 'Frame wird erstellt und analysiert. Bitte warten.'}
|
{props.text || 'Frame wird erstellt und analysiert. Bitte warten.'}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-3 h-2 w-48 overflow-hidden rounded-full bg-white/20">
|
||||||
|
<div
|
||||||
|
className="h-full rounded-full bg-indigo-400 transition-all duration-500"
|
||||||
|
style={{ width: `${progress}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-1 text-[11px] text-white/70">
|
||||||
|
{Math.round(progress)}%
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -1868,6 +1884,8 @@ export default function TrainingTab(props: {
|
|||||||
const [sample, setSample] = useState<TrainingSample | null>(null)
|
const [sample, setSample] = useState<TrainingSample | null>(null)
|
||||||
const [correction, setCorrection] = useState<CorrectionState>(() => predictionToCorrection(null))
|
const [correction, setCorrection] = useState<CorrectionState>(() => predictionToCorrection(null))
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [analysisProgress, setAnalysisProgress] = useState(0)
|
||||||
|
const [analysisStep, setAnalysisStep] = useState('')
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
const [training, setTraining] = useState(false)
|
const [training, setTraining] = useState(false)
|
||||||
const [trainingStatus, setTrainingStatus] = useState<TrainingStatus | null>(null)
|
const [trainingStatus, setTrainingStatus] = useState<TrainingStatus | null>(null)
|
||||||
@ -2012,6 +2030,25 @@ export default function TrainingTab(props: {
|
|||||||
preserveNotice?: boolean
|
preserveNotice?: boolean
|
||||||
}) => {
|
}) => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
setAnalysisProgress(8)
|
||||||
|
setAnalysisStep(
|
||||||
|
opts?.refreshPrediction
|
||||||
|
? 'Aktuelles Bild wird neu analysiert…'
|
||||||
|
: opts?.forceNew
|
||||||
|
? 'Neues Trainingsbild wird gesucht…'
|
||||||
|
: 'Trainingsbild wird geladen…'
|
||||||
|
)
|
||||||
|
|
||||||
|
let progressTimer: number | undefined
|
||||||
|
|
||||||
|
progressTimer = window.setInterval(() => {
|
||||||
|
setAnalysisProgress((value) => {
|
||||||
|
if (value < 35) return value + 4
|
||||||
|
if (value < 65) return value + 2
|
||||||
|
if (value < 88) return value + 1
|
||||||
|
return value
|
||||||
|
})
|
||||||
|
}, 350)
|
||||||
|
|
||||||
if (!opts?.preserveNotice) {
|
if (!opts?.preserveNotice) {
|
||||||
setError(null)
|
setError(null)
|
||||||
@ -2026,13 +2063,19 @@ export default function TrainingTab(props: {
|
|||||||
|
|
||||||
const url = `/api/training/next${params.toString() ? `?${params.toString()}` : ''}`
|
const url = `/api/training/next${params.toString() ? `?${params.toString()}` : ''}`
|
||||||
|
|
||||||
|
setAnalysisProgress(25)
|
||||||
|
setAnalysisStep('Frame wird vorbereitet…')
|
||||||
|
|
||||||
const res = await fetch(url, { cache: 'no-store' })
|
const res = await fetch(url, { cache: 'no-store' })
|
||||||
const data = await res.json().catch(() => null)
|
const data = await res.json().catch(() => null)
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new Error(data?.error || `HTTP ${res.status}`)
|
throw new Error(data?.error || `HTTP ${res.status}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setAnalysisProgress(92)
|
||||||
|
setAnalysisStep('Analyse-Ergebnis wird übernommen…')
|
||||||
|
|
||||||
const nextCorrection = predictionToCorrection(data)
|
const nextCorrection = predictionToCorrection(data)
|
||||||
|
|
||||||
setDrawingBox(null)
|
setDrawingBox(null)
|
||||||
@ -2063,7 +2106,18 @@ export default function TrainingTab(props: {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e instanceof Error ? e.message : String(e))
|
setError(e instanceof Error ? e.message : String(e))
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
if (progressTimer !== undefined) {
|
||||||
|
window.clearInterval(progressTimer)
|
||||||
|
}
|
||||||
|
|
||||||
|
setAnalysisProgress(100)
|
||||||
|
setAnalysisStep('Analyse abgeschlossen.')
|
||||||
|
|
||||||
|
window.setTimeout(() => {
|
||||||
|
setLoading(false)
|
||||||
|
setAnalysisProgress(0)
|
||||||
|
setAnalysisStep('')
|
||||||
|
}, 150)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@ -2683,6 +2737,24 @@ export default function TrainingTab(props: {
|
|||||||
return null
|
return null
|
||||||
}, [error, message, trainingRunning, shownTrainingStep])
|
}, [error, message, trainingRunning, shownTrainingStep])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!message) return
|
||||||
|
if (error) return
|
||||||
|
if (trainingRunning) return
|
||||||
|
|
||||||
|
const looksPartial =
|
||||||
|
message.toLowerCase().includes('übersprungen') ||
|
||||||
|
message.toLowerCase().includes('fehlgeschlagen')
|
||||||
|
|
||||||
|
if (looksPartial) return
|
||||||
|
|
||||||
|
const timer = window.setTimeout(() => {
|
||||||
|
setMessage(null)
|
||||||
|
}, 2500)
|
||||||
|
|
||||||
|
return () => window.clearTimeout(timer)
|
||||||
|
}, [message, error, trainingRunning])
|
||||||
|
|
||||||
const detectorBoxesPanel = (
|
const detectorBoxesPanel = (
|
||||||
<div className="rounded-lg bg-gray-50 p-2 ring-1 ring-black/5 dark:bg-white/5 dark:ring-white/10">
|
<div className="rounded-lg bg-gray-50 p-2 ring-1 ring-black/5 dark:bg-white/5 dark:ring-white/10">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
@ -2824,22 +2896,6 @@ export default function TrainingTab(props: {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{activeNotice ? (
|
|
||||||
<div className="mb-3">
|
|
||||||
<TrainingNoticeCard
|
|
||||||
notice={activeNotice}
|
|
||||||
onClose={
|
|
||||||
trainingRunning
|
|
||||||
? undefined
|
|
||||||
: () => {
|
|
||||||
setError(null)
|
|
||||||
setMessage(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 items-stretch gap-3 lg:grid-cols-[300px_minmax(0,1fr)_300px] xl:grid-cols-[320px_minmax(0,1fr)_320px]">
|
<div className="grid grid-cols-1 items-stretch gap-3 lg:grid-cols-[300px_minmax(0,1fr)_300px] xl:grid-cols-[320px_minmax(0,1fr)_320px]">
|
||||||
{/* Sidebar links */}
|
{/* Sidebar links */}
|
||||||
<aside className="max-h-[calc(100dvh-190px)] overflow-y-auto rounded-xl border border-gray-200 bg-white p-3 shadow-sm dark:border-white/10 dark:bg-gray-900/60">
|
<aside className="max-h-[calc(100dvh-190px)] overflow-y-auto rounded-xl border border-gray-200 bg-white p-3 shadow-sm dark:border-white/10 dark:bg-gray-900/60">
|
||||||
@ -3359,7 +3415,10 @@ export default function TrainingTab(props: {
|
|||||||
progress={shownTrainingProgress}
|
progress={shownTrainingProgress}
|
||||||
/>
|
/>
|
||||||
) : loading ? (
|
) : loading ? (
|
||||||
<LoadingImageOverlay />
|
<LoadingImageOverlay
|
||||||
|
text={analysisStep}
|
||||||
|
progress={analysisProgress}
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : trainingRunning ? (
|
) : trainingRunning ? (
|
||||||
@ -3368,7 +3427,10 @@ export default function TrainingTab(props: {
|
|||||||
progress={shownTrainingProgress}
|
progress={shownTrainingProgress}
|
||||||
/>
|
/>
|
||||||
) : loading ? (
|
) : loading ? (
|
||||||
<LoadingImageOverlay />
|
<LoadingImageOverlay
|
||||||
|
text={analysisStep}
|
||||||
|
progress={analysisProgress}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-sm text-white/80">Kein Frame geladen</div>
|
<div className="text-sm text-white/80">Kein Frame geladen</div>
|
||||||
)}
|
)}
|
||||||
@ -3411,9 +3473,25 @@ export default function TrainingTab(props: {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-2 text-center text-xs text-gray-500 dark:text-gray-400">
|
{activeNotice ? (
|
||||||
Prüfe das Bild. Wenn die Erkennung stimmt: „Passt so“. Wenn nicht: korrigieren und speichern.
|
<div className="mx-auto mt-3 w-full max-w-2xl">
|
||||||
</div>
|
<TrainingNoticeCard
|
||||||
|
notice={activeNotice}
|
||||||
|
onClose={
|
||||||
|
trainingRunning
|
||||||
|
? undefined
|
||||||
|
: () => {
|
||||||
|
setError(null)
|
||||||
|
setMessage(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="mt-2 text-center text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
Prüfe das Bild. Wenn die Erkennung stimmt: „Passt so“. Wenn nicht: korrigieren und speichern.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Rechte Details/Korrektur */}
|
{/* Rechte Details/Korrektur */}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user