bugfixes
This commit is contained in:
parent
9e1e41fe04
commit
93b51978c5
BIN
backend/dist/nsfwapp-linux-amd64
vendored
BIN
backend/dist/nsfwapp-linux-amd64
vendored
Binary file not shown.
BIN
backend/dist/nsfwapp.exe
vendored
BIN
backend/dist/nsfwapp.exe
vendored
Binary file not shown.
BIN
backend/dist/nsfwapp_amd64.deb
vendored
BIN
backend/dist/nsfwapp_amd64.deb
vendored
Binary file not shown.
@ -2548,6 +2548,31 @@ func trainingPruneImportVideoJobsLocked(now time.Time) {
|
||||
}
|
||||
}
|
||||
|
||||
func trainingLatestRunningImportVideoJobRequestID() string {
|
||||
now := time.Now().UTC()
|
||||
|
||||
trainingImportVideoJobs.Lock()
|
||||
defer trainingImportVideoJobs.Unlock()
|
||||
|
||||
trainingPruneImportVideoJobsLocked(now)
|
||||
|
||||
requestID := ""
|
||||
var latestStarted time.Time
|
||||
|
||||
for id, job := range trainingImportVideoJobs.items {
|
||||
if job == nil || !job.running {
|
||||
continue
|
||||
}
|
||||
|
||||
if requestID == "" || job.startedAt.After(latestStarted) {
|
||||
requestID = id
|
||||
latestStarted = job.startedAt
|
||||
}
|
||||
}
|
||||
|
||||
return requestID
|
||||
}
|
||||
|
||||
func trainingStartImportVideoJob(req TrainingImportVideoRequest) {
|
||||
requestID := strings.TrimSpace(req.AnalysisRequestID)
|
||||
if requestID == "" {
|
||||
@ -2591,7 +2616,11 @@ func trainingStartImportVideoJob(req TrainingImportVideoRequest) {
|
||||
func trainingImportVideoJobResponse(requestID string) (TrainingImportVideoResponse, int) {
|
||||
requestID = strings.TrimSpace(requestID)
|
||||
if requestID == "" {
|
||||
return TrainingImportVideoResponse{OK: false, Error: "requestId missing"}, http.StatusBadRequest
|
||||
requestID = trainingLatestRunningImportVideoJobRequestID()
|
||||
}
|
||||
|
||||
if requestID == "" {
|
||||
return TrainingImportVideoResponse{OK: false, Error: "Kein laufender Import-Job gefunden."}, http.StatusNotFound
|
||||
}
|
||||
|
||||
trainingImportVideoJobs.Lock()
|
||||
@ -3083,6 +3112,31 @@ func trainingPruneNextJobsLocked(now time.Time) {
|
||||
}
|
||||
}
|
||||
|
||||
func trainingLatestRunningNextJobRequestID() string {
|
||||
now := time.Now().UTC()
|
||||
|
||||
trainingNextJobs.Lock()
|
||||
defer trainingNextJobs.Unlock()
|
||||
|
||||
trainingPruneNextJobsLocked(now)
|
||||
|
||||
requestID := ""
|
||||
var latestStarted time.Time
|
||||
|
||||
for id, job := range trainingNextJobs.items {
|
||||
if job == nil || !job.running {
|
||||
continue
|
||||
}
|
||||
|
||||
if requestID == "" || job.startedAt.After(latestStarted) {
|
||||
requestID = id
|
||||
latestStarted = job.startedAt
|
||||
}
|
||||
}
|
||||
|
||||
return requestID
|
||||
}
|
||||
|
||||
func trainingStartNextJob(req TrainingNextRequest) {
|
||||
requestID := strings.TrimSpace(req.AnalysisRequestID)
|
||||
if requestID == "" {
|
||||
@ -3127,7 +3181,11 @@ func trainingStartNextJob(req TrainingNextRequest) {
|
||||
func trainingNextJobResponse(requestID string) (TrainingNextResponse, int) {
|
||||
requestID = strings.TrimSpace(requestID)
|
||||
if requestID == "" {
|
||||
return TrainingNextResponse{OK: false, Error: "requestId missing"}, http.StatusBadRequest
|
||||
requestID = trainingLatestRunningNextJobRequestID()
|
||||
}
|
||||
|
||||
if requestID == "" {
|
||||
return TrainingNextResponse{OK: false, Error: "Kein laufender Analyse-Job gefunden."}, http.StatusNotFound
|
||||
}
|
||||
|
||||
trainingNextJobs.Lock()
|
||||
|
||||
@ -22,7 +22,7 @@ const trainingNoSexPositionLabel = "keine"
|
||||
|
||||
func isNoSexPositionLabel(label string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(label)) {
|
||||
case "", trainingNoSexPositionLabel:
|
||||
case "", trainingNoSexPositionLabel, "unknown", "unbekannt", "none", "no_position", "no-position", "no position", "n/a", "na":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
||||
@ -132,6 +132,17 @@ func TestTrainingEffectiveCorrectionClearsNegativeAnnotation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoSexPositionAliasesTreatUnknownAsNoPosition(t *testing.T) {
|
||||
for _, value := range []string{"Unknown", "unknown", "unbekannt", "none", "no_position"} {
|
||||
if !isNoSexPositionLabel(value) {
|
||||
t.Fatalf("%q should be treated as no sex position", value)
|
||||
}
|
||||
if got := normalizeSexPositionLabel(value); got != trainingNoSexPositionLabel {
|
||||
t.Fatalf("normalizeSexPositionLabel(%q) = %q, want %q", value, got, trainingNoSexPositionLabel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func trainingTestPoseKeypoints(overrides map[string]TrainingKeypoint) []TrainingKeypoint {
|
||||
names := []string{
|
||||
"nose",
|
||||
|
||||
@ -1587,6 +1587,14 @@ const NO_SEX_POSITION_LABEL = 'keine'
|
||||
const NO_SEX_POSITION_ALIASES = new Set([
|
||||
'',
|
||||
NO_SEX_POSITION_LABEL,
|
||||
'unknown',
|
||||
'unbekannt',
|
||||
'none',
|
||||
'no_position',
|
||||
'no-position',
|
||||
'no position',
|
||||
'n/a',
|
||||
'na',
|
||||
])
|
||||
|
||||
function normalizeSexPositionValue(value?: string | null) {
|
||||
@ -3980,6 +3988,49 @@ const TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY = 'training:active-import-video'
|
||||
const TRAINING_ACTIVE_NEXT_STORAGE_KEY = 'training:active-next'
|
||||
const ALL_TRAINING_TARGETS: TrainingTargetKey[] = ['detector', 'pose', 'videomae']
|
||||
|
||||
function readTrainingActiveJobStorage(key: string) {
|
||||
try {
|
||||
const raw = window.localStorage.getItem(key)
|
||||
if (raw) return raw
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
try {
|
||||
return window.sessionStorage.getItem(key) || ''
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
function writeTrainingActiveJobStorage(key: string, value: string) {
|
||||
try {
|
||||
window.localStorage.setItem(key, value)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
try {
|
||||
window.sessionStorage.setItem(key, value)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
function removeTrainingActiveJobStorage(key: string) {
|
||||
try {
|
||||
window.localStorage.removeItem(key)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
try {
|
||||
window.sessionStorage.removeItem(key)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
export default function TrainingTab(props: {
|
||||
active?: boolean
|
||||
onTrainingRunningChange?: (running: boolean) => void
|
||||
@ -4715,6 +4766,7 @@ export default function TrainingTab(props: {
|
||||
return correction.posePersons ?? []
|
||||
}, [correction.posePersons])
|
||||
|
||||
const poseTrainingAllowed = !isNoSexPositionValue(correction.sexPosition)
|
||||
const hasPosePersons = posePersons.some((person) =>
|
||||
hasVisiblePoseBox(person) || person.keypoints?.some(isPoseKeypointVisible)
|
||||
)
|
||||
@ -4909,6 +4961,9 @@ export default function TrainingTab(props: {
|
||||
|
||||
const running = Boolean(data?.running)
|
||||
if (running) {
|
||||
if (!loadingRef.current) {
|
||||
resetPoseUiToBoxes()
|
||||
}
|
||||
loadingRef.current = true
|
||||
setLoading(true)
|
||||
}
|
||||
@ -4959,7 +5014,7 @@ export default function TrainingTab(props: {
|
||||
}
|
||||
|
||||
return true
|
||||
}, [setLoadingPreviewCandidate])
|
||||
}, [resetPoseUiToBoxes, setLoadingPreviewCandidate])
|
||||
|
||||
useEffect(() => {
|
||||
if (!imageSrc) {
|
||||
@ -5740,11 +5795,11 @@ export default function TrainingTab(props: {
|
||||
|
||||
const previewUrl = String(opts?.previewUrl ?? '').trim()
|
||||
|
||||
resetPoseUiToBoxes()
|
||||
setLoadingPreviewFallbackUrl(previewUrl)
|
||||
setLoadingPreviewCandidate(previewUrl)
|
||||
loadingRef.current = true
|
||||
setLoading(true)
|
||||
resetPoseUiToBoxes()
|
||||
setAnalysisSourceFile('')
|
||||
setAnalysisProgress(8)
|
||||
setAnalysisStep(
|
||||
@ -5776,7 +5831,7 @@ export default function TrainingTab(props: {
|
||||
if (uncertainMode) params.set('mode', 'uncertain')
|
||||
|
||||
try {
|
||||
window.sessionStorage.setItem(
|
||||
writeTrainingActiveJobStorage(
|
||||
TRAINING_ACTIVE_NEXT_STORAGE_KEY,
|
||||
JSON.stringify({
|
||||
requestId,
|
||||
@ -5839,7 +5894,7 @@ export default function TrainingTab(props: {
|
||||
setMessage('Analyse läuft im Backend weiter. Beim Zurückkehren wird das nächste offene Trainingsbild wieder geladen.')
|
||||
} else {
|
||||
try {
|
||||
window.sessionStorage.removeItem(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
||||
removeTrainingActiveJobStorage(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
@ -5853,7 +5908,7 @@ export default function TrainingTab(props: {
|
||||
|
||||
if (completed) {
|
||||
try {
|
||||
window.sessionStorage.removeItem(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
||||
removeTrainingActiveJobStorage(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
@ -5889,6 +5944,7 @@ export default function TrainingTab(props: {
|
||||
])
|
||||
|
||||
const reloadCurrentImage = useCallback(async () => {
|
||||
resetPoseUiToBoxes()
|
||||
setDrawingBox(null)
|
||||
setBoxInteraction(null)
|
||||
setTouchMagnifier(null)
|
||||
@ -5899,7 +5955,7 @@ export default function TrainingTab(props: {
|
||||
previewUrl: imageSrc,
|
||||
})
|
||||
setImageReloadKey((value) => value + 1)
|
||||
}, [imageSrc, loadNext])
|
||||
}, [imageSrc, loadNext, resetPoseUiToBoxes])
|
||||
|
||||
const loadTrainingStatus = useCallback(async () => {
|
||||
const res = await fetch('/api/training/status', { cache: 'no-store' })
|
||||
@ -5956,6 +6012,10 @@ export default function TrainingTab(props: {
|
||||
)
|
||||
const data = await res.json().catch(() => null)
|
||||
|
||||
if (data?.analysis) {
|
||||
applyTrainingAnalysisEvent(data.analysis)
|
||||
}
|
||||
|
||||
if (res.status === 202 || data?.running) {
|
||||
await new Promise((resolve) => window.setTimeout(resolve, 1200))
|
||||
continue
|
||||
@ -5967,7 +6027,7 @@ export default function TrainingTab(props: {
|
||||
|
||||
return completeVideoImportFromData(data)
|
||||
}
|
||||
}, [completeVideoImportFromData])
|
||||
}, [applyTrainingAnalysisEvent, completeVideoImportFromData])
|
||||
|
||||
const importVideoIntoTraining = useCallback(async (raw: any) => {
|
||||
const output = String(raw?.output || '').trim()
|
||||
@ -5990,7 +6050,7 @@ export default function TrainingTab(props: {
|
||||
|
||||
const importKey = `${detail.jobId || ''}|${detail.output}|${detail.count || TRAINING_IMPORT_FRAME_COUNT}`
|
||||
|
||||
// Verhindert Doppelimport durch sessionStorage + CustomEvent.
|
||||
// Verhindert Doppelimport durch persistent gemerkte aktive Jobs + CustomEvent.
|
||||
if (videoImportInFlightKeyRef.current === importKey) {
|
||||
return false
|
||||
}
|
||||
@ -6002,6 +6062,7 @@ export default function TrainingTab(props: {
|
||||
activeAnalysisRequestIdRef.current = requestId
|
||||
loadingRef.current = true
|
||||
|
||||
resetPoseUiToBoxes()
|
||||
setLoading(true)
|
||||
setAnalysisSourceFile(detail.sourceFile || detail.output.split(/[\\/]/).pop() || '')
|
||||
setAnalysisProgress(5)
|
||||
@ -6009,10 +6070,13 @@ export default function TrainingTab(props: {
|
||||
setError(null)
|
||||
setMessage(null)
|
||||
|
||||
let keepActiveJob = false
|
||||
let completed = false
|
||||
|
||||
try {
|
||||
try {
|
||||
window.sessionStorage.removeItem(TRAINING_PENDING_IMPORT_VIDEO_STORAGE_KEY)
|
||||
window.sessionStorage.setItem(
|
||||
writeTrainingActiveJobStorage(
|
||||
TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY,
|
||||
JSON.stringify({ requestId, importKey, detail })
|
||||
)
|
||||
@ -6040,9 +6104,10 @@ export default function TrainingTab(props: {
|
||||
|
||||
if (res.status === 202 || data?.accepted || data?.running) {
|
||||
await waitForVideoImportResult(String(data?.requestId || requestId))
|
||||
completed = true
|
||||
|
||||
try {
|
||||
window.sessionStorage.removeItem(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||
removeTrainingActiveJobStorage(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
@ -6086,6 +6151,7 @@ export default function TrainingTab(props: {
|
||||
)
|
||||
}
|
||||
|
||||
completed = true
|
||||
return true
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : String(e)
|
||||
@ -6093,10 +6159,11 @@ export default function TrainingTab(props: {
|
||||
/load failed|failed to fetch|networkerror|network error/i.test(msg)
|
||||
|
||||
if (mayStillRun) {
|
||||
keepActiveJob = true
|
||||
setMessage('Video-Import läuft im Backend weiter und wird beim Zurückkehren wieder aufgenommen.')
|
||||
} else {
|
||||
try {
|
||||
window.sessionStorage.removeItem(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||
removeTrainingActiveJobStorage(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
@ -6104,6 +6171,14 @@ export default function TrainingTab(props: {
|
||||
}
|
||||
return false
|
||||
} finally {
|
||||
if (completed) {
|
||||
removeTrainingActiveJobStorage(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||
}
|
||||
|
||||
if (keepActiveJob) {
|
||||
return
|
||||
}
|
||||
|
||||
setAnalysisProgress(100)
|
||||
setAnalysisStep('Video-Import abgeschlossen.')
|
||||
|
||||
@ -6127,6 +6202,7 @@ export default function TrainingTab(props: {
|
||||
}, [
|
||||
loadPriorityTrainingSamples,
|
||||
loadTrainingStatus,
|
||||
resetPoseUiToBoxes,
|
||||
setLoadingPreviewCandidate,
|
||||
trainingRunning,
|
||||
waitForVideoImportResult,
|
||||
@ -6480,6 +6556,163 @@ export default function TrainingTab(props: {
|
||||
})
|
||||
}, [activeBoxIndex])
|
||||
|
||||
const resumeLatestServerAnalysisJob = useCallback(async () => {
|
||||
if (
|
||||
activeAnalysisRequestIdRef.current ||
|
||||
nextAnalysisInFlightRequestIdRef.current ||
|
||||
videoImportInFlightKeyRef.current
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
const resumeImport = (data: any) => {
|
||||
const requestId = String(data?.requestId || '').trim()
|
||||
if (!requestId) return false
|
||||
|
||||
const importKey = `server|${requestId}`
|
||||
videoImportStartedRef.current = true
|
||||
videoImportInFlightKeyRef.current = importKey
|
||||
activeAnalysisRequestIdRef.current = requestId
|
||||
loadingRef.current = true
|
||||
|
||||
writeTrainingActiveJobStorage(
|
||||
TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY,
|
||||
JSON.stringify({ requestId, importKey, detail: {} })
|
||||
)
|
||||
|
||||
resetPoseUiToBoxes()
|
||||
setLoading(true)
|
||||
setAnalysisSourceFile('')
|
||||
setAnalysisProgress(5)
|
||||
setAnalysisStep('Video-Import läuft im Backend…')
|
||||
setError(null)
|
||||
|
||||
if (data?.analysis) {
|
||||
applyTrainingAnalysisEvent(data.analysis)
|
||||
}
|
||||
|
||||
void waitForVideoImportResult(requestId)
|
||||
.then(() => {
|
||||
removeTrainingActiveJobStorage(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||
})
|
||||
.catch((e) => {
|
||||
const msg = e instanceof Error ? e.message : String(e)
|
||||
const mayStillRun =
|
||||
/load failed|failed to fetch|networkerror|network error/i.test(msg)
|
||||
|
||||
if (mayStillRun) {
|
||||
setMessage('Video-Import läuft im Backend weiter und wird beim Zurückkehren wieder aufgenommen.')
|
||||
} else {
|
||||
removeTrainingActiveJobStorage(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||
setError(msg)
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (activeAnalysisRequestIdRef.current === requestId) {
|
||||
activeAnalysisRequestIdRef.current = null
|
||||
loadingRef.current = false
|
||||
setLoading(false)
|
||||
setAnalysisSourceFile('')
|
||||
setAnalysisProgress(0)
|
||||
setAnalysisStep('')
|
||||
}
|
||||
|
||||
if (videoImportInFlightKeyRef.current === importKey) {
|
||||
videoImportInFlightKeyRef.current = null
|
||||
}
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const resumeNext = (data: any) => {
|
||||
const requestId = String(data?.requestId || '').trim()
|
||||
if (!requestId) return false
|
||||
|
||||
activeAnalysisRequestIdRef.current = requestId
|
||||
nextAnalysisInFlightRequestIdRef.current = requestId
|
||||
loadingRef.current = true
|
||||
|
||||
writeTrainingActiveJobStorage(
|
||||
TRAINING_ACTIVE_NEXT_STORAGE_KEY,
|
||||
JSON.stringify({ requestId, opts: {} })
|
||||
)
|
||||
|
||||
resetPoseUiToBoxes()
|
||||
setLoading(true)
|
||||
setAnalysisSourceFile('')
|
||||
setAnalysisProgress(5)
|
||||
setAnalysisStep('Analyse läuft im Backend…')
|
||||
setError(null)
|
||||
|
||||
if (data?.analysis) {
|
||||
applyTrainingAnalysisEvent(data.analysis)
|
||||
}
|
||||
|
||||
void waitForNextResult(requestId)
|
||||
.then(() => {
|
||||
removeTrainingActiveJobStorage(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
||||
})
|
||||
.catch((e) => {
|
||||
const msg = e instanceof Error ? e.message : String(e)
|
||||
const mayStillRun =
|
||||
/load failed|failed to fetch|networkerror|network error/i.test(msg)
|
||||
|
||||
if (mayStillRun) {
|
||||
setMessage('Analyse läuft im Backend weiter und wird beim Zurückkehren wieder aufgenommen.')
|
||||
} else {
|
||||
removeTrainingActiveJobStorage(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
||||
setError(msg)
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (activeAnalysisRequestIdRef.current === requestId) {
|
||||
activeAnalysisRequestIdRef.current = null
|
||||
nextAnalysisInFlightRequestIdRef.current = null
|
||||
loadingRef.current = false
|
||||
setLoading(false)
|
||||
setAnalysisSourceFile('')
|
||||
setAnalysisProgress(0)
|
||||
setAnalysisStep('')
|
||||
}
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
try {
|
||||
const importRes = await fetch('/api/training/import-video/status', { cache: 'no-store' })
|
||||
const importData = await importRes.json().catch(() => null)
|
||||
|
||||
if (
|
||||
(importRes.status === 202 || importData?.running || importData?.accepted) &&
|
||||
importData?.ok &&
|
||||
resumeImport(importData)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
try {
|
||||
const nextRes = await fetch('/api/training/next/status', { cache: 'no-store' })
|
||||
const nextData = await nextRes.json().catch(() => null)
|
||||
|
||||
if (
|
||||
(nextRes.status === 202 || nextData?.running || nextData?.accepted) &&
|
||||
nextData?.ok &&
|
||||
resumeNext(nextData)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return false
|
||||
}, [applyTrainingAnalysisEvent, resetPoseUiToBoxes, waitForNextResult, waitForVideoImportResult])
|
||||
|
||||
useEffect(() => {
|
||||
const onImportVideo = (event: Event) => {
|
||||
const detail = (event as CustomEvent<any>).detail
|
||||
@ -6492,7 +6725,7 @@ export default function TrainingTab(props: {
|
||||
let resumedActiveNext = false
|
||||
|
||||
try {
|
||||
const activeRaw = window.sessionStorage.getItem(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||
const activeRaw = readTrainingActiveJobStorage(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||
|
||||
if (activeRaw) {
|
||||
const active = JSON.parse(activeRaw)
|
||||
@ -6506,6 +6739,7 @@ export default function TrainingTab(props: {
|
||||
|
||||
activeAnalysisRequestIdRef.current = requestId
|
||||
loadingRef.current = true
|
||||
resetPoseUiToBoxes()
|
||||
setLoading(true)
|
||||
setAnalysisSourceFile(String(active?.detail?.sourceFile || active?.detail?.output || '').split(/[\\/]/).pop() || '')
|
||||
setAnalysisProgress(5)
|
||||
@ -6514,7 +6748,7 @@ export default function TrainingTab(props: {
|
||||
|
||||
void waitForVideoImportResult(requestId)
|
||||
.then(() => {
|
||||
window.sessionStorage.removeItem(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||
removeTrainingActiveJobStorage(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||
})
|
||||
.catch((e) => {
|
||||
const msg = e instanceof Error ? e.message : String(e)
|
||||
@ -6524,7 +6758,7 @@ export default function TrainingTab(props: {
|
||||
if (mayStillRun) {
|
||||
setMessage('Video-Import läuft im Backend weiter und wird beim Zurückkehren wieder aufgenommen.')
|
||||
} else {
|
||||
window.sessionStorage.removeItem(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||
removeTrainingActiveJobStorage(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||
setError(msg)
|
||||
}
|
||||
})
|
||||
@ -6549,7 +6783,7 @@ export default function TrainingTab(props: {
|
||||
|
||||
const activeNextRaw = resumedActiveImport
|
||||
? ''
|
||||
: window.sessionStorage.getItem(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
||||
: readTrainingActiveJobStorage(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
||||
|
||||
if (activeNextRaw) {
|
||||
const active = JSON.parse(activeNextRaw)
|
||||
@ -6560,6 +6794,7 @@ export default function TrainingTab(props: {
|
||||
activeAnalysisRequestIdRef.current = requestId
|
||||
nextAnalysisInFlightRequestIdRef.current = requestId
|
||||
loadingRef.current = true
|
||||
resetPoseUiToBoxes()
|
||||
setLoading(true)
|
||||
setAnalysisSourceFile('')
|
||||
setAnalysisProgress(5)
|
||||
@ -6584,7 +6819,7 @@ export default function TrainingTab(props: {
|
||||
deferCurrentSampleToQueueEnd: Boolean(activeOpts?.deferCurrentSampleToQueueEnd),
|
||||
})
|
||||
.then(() => {
|
||||
window.sessionStorage.removeItem(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
||||
removeTrainingActiveJobStorage(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
||||
})
|
||||
.catch((e) => {
|
||||
const msg = e instanceof Error ? e.message : String(e)
|
||||
@ -6594,7 +6829,7 @@ export default function TrainingTab(props: {
|
||||
if (mayStillRun) {
|
||||
setMessage('Analyse läuft im Backend weiter und wird beim Zurückkehren wieder aufgenommen.')
|
||||
} else {
|
||||
window.sessionStorage.removeItem(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
||||
removeTrainingActiveJobStorage(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
||||
setError(msg)
|
||||
}
|
||||
})
|
||||
@ -6630,7 +6865,7 @@ export default function TrainingTab(props: {
|
||||
return () => {
|
||||
window.removeEventListener('training:import-video', onImportVideo as EventListener)
|
||||
}
|
||||
}, [applyTrainingAnalysisEvent, importVideoIntoTraining, waitForNextResult, waitForVideoImportResult])
|
||||
}, [applyTrainingAnalysisEvent, importVideoIntoTraining, resetPoseUiToBoxes, waitForNextResult, waitForVideoImportResult])
|
||||
|
||||
useEffect(() => {
|
||||
if (!tabActive || initializedRef.current) return
|
||||
@ -6645,6 +6880,15 @@ export default function TrainingTab(props: {
|
||||
|
||||
if (cancelled || initRunIdRef.current !== runId) return
|
||||
|
||||
const resumedServerJob = await resumeLatestServerAnalysisJob()
|
||||
|
||||
if (cancelled || initRunIdRef.current !== runId) return
|
||||
|
||||
if (resumedServerJob) {
|
||||
initializedRef.current = true
|
||||
return
|
||||
}
|
||||
|
||||
// Wichtig:
|
||||
// Wenn gerade ein Video-Import über "Video ins Training übernehmen" läuft,
|
||||
// darf loadNext() nicht danach ein zufälliges/letztes Sample darüberlegen.
|
||||
@ -6669,7 +6913,7 @@ export default function TrainingTab(props: {
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [tabActive, loadLabels, loadNext, loadTrainingStatus])
|
||||
}, [tabActive, loadLabels, loadNext, loadTrainingStatus, resumeLatestServerAnalysisJob])
|
||||
|
||||
useEffect(() => {
|
||||
if (!tabActive || !initializedRef.current) return
|
||||
@ -6693,7 +6937,7 @@ export default function TrainingTab(props: {
|
||||
let importRequestId = activeAnalysisRequestIdRef.current || ''
|
||||
|
||||
try {
|
||||
const activeNextRaw = window.sessionStorage.getItem(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
||||
const activeNextRaw = readTrainingActiveJobStorage(TRAINING_ACTIVE_NEXT_STORAGE_KEY)
|
||||
if (!nextRequestId && activeNextRaw) {
|
||||
nextRequestId = String(JSON.parse(activeNextRaw)?.requestId || '').trim()
|
||||
}
|
||||
@ -6702,7 +6946,7 @@ export default function TrainingTab(props: {
|
||||
}
|
||||
|
||||
try {
|
||||
const activeImportRaw = window.sessionStorage.getItem(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||
const activeImportRaw = readTrainingActiveJobStorage(TRAINING_ACTIVE_IMPORT_VIDEO_STORAGE_KEY)
|
||||
if (activeImportRaw) {
|
||||
importRequestId = String(JSON.parse(activeImportRaw)?.requestId || importRequestId || '').trim()
|
||||
}
|
||||
@ -6869,6 +7113,7 @@ export default function TrainingTab(props: {
|
||||
) => {
|
||||
if (!sample || trainingRunning) return
|
||||
|
||||
resetPoseUiToBoxes()
|
||||
setSavingOverlayText(editingFeedback ? 'Feedback wird aktualisiert…' : 'Feedback wird gespeichert…')
|
||||
setSaving(true)
|
||||
setError(null)
|
||||
@ -7029,6 +7274,7 @@ export default function TrainingTab(props: {
|
||||
loadTrainingStatus,
|
||||
loadQueuedTrainingSample,
|
||||
loadNextImportedQueuedSample,
|
||||
resetPoseUiToBoxes,
|
||||
trainingRunning,
|
||||
]
|
||||
)
|
||||
@ -7053,6 +7299,7 @@ export default function TrainingTab(props: {
|
||||
|
||||
const skippedSampleId = sample.sampleId
|
||||
|
||||
resetPoseUiToBoxes()
|
||||
setSavingOverlayText('Bild wird übersprungen…')
|
||||
setSaving(true)
|
||||
setError(null)
|
||||
@ -7101,6 +7348,7 @@ export default function TrainingTab(props: {
|
||||
loadNext,
|
||||
loadQueuedTrainingSample,
|
||||
loadNextImportedQueuedSample,
|
||||
resetPoseUiToBoxes,
|
||||
])
|
||||
|
||||
const openTrainingStartModal = useCallback(() => {
|
||||
@ -7363,6 +7611,16 @@ export default function TrainingTab(props: {
|
||||
latestGestureBoxRef.current = null
|
||||
}, [cancelPointerMoveFrame])
|
||||
|
||||
const resetBoxDrawingMode = useCallback(() => {
|
||||
releaseActivePointerCapture()
|
||||
clearBoxGestureRefs()
|
||||
setDrawingBox(null)
|
||||
setBoxInteraction(null)
|
||||
setTouchMagnifier(null)
|
||||
setBoxLabel('')
|
||||
setActiveBoxIndex(null)
|
||||
}, [clearBoxGestureRefs, releaseActivePointerCapture])
|
||||
|
||||
const applyPointerMoveSnapshot = useCallback((snapshot: {
|
||||
clientX: number
|
||||
clientY: number
|
||||
@ -8071,7 +8329,7 @@ export default function TrainingTab(props: {
|
||||
}, [markManualCorrection])
|
||||
|
||||
const addPosePerson = useCallback(() => {
|
||||
if (uiLocked) return
|
||||
if (uiLocked || !poseTrainingAllowed) return
|
||||
|
||||
markManualCorrection()
|
||||
|
||||
@ -8089,8 +8347,7 @@ export default function TrainingTab(props: {
|
||||
})
|
||||
|
||||
setShowPoseSkeleton(true)
|
||||
setActiveBoxIndex(null)
|
||||
setBoxLabel('')
|
||||
resetBoxDrawingMode()
|
||||
setActivePosePersonIndex(nextPersonIndex)
|
||||
setPendingPoseKeypoint(null)
|
||||
setSelectedPoseKeypointAction(null)
|
||||
@ -8103,7 +8360,7 @@ export default function TrainingTab(props: {
|
||||
behavior: 'smooth',
|
||||
})
|
||||
})
|
||||
}, [markManualCorrection, switchPoseKeypointMapView, uiLocked])
|
||||
}, [markManualCorrection, poseTrainingAllowed, resetBoxDrawingMode, switchPoseKeypointMapView, uiLocked])
|
||||
|
||||
const removePoseKeypoint = useCallback((personIndex: number, keypointIndex: number) => {
|
||||
markManualCorrection()
|
||||
@ -8311,7 +8568,8 @@ export default function TrainingTab(props: {
|
||||
setLoadingPreviewFailed(false)
|
||||
}, [loading, frameBusy, loadingPreviewFallbackUrl, loadingPreviewUrl])
|
||||
|
||||
const poseModeAvailable = Boolean(sample) || hasPosePersons || originalPosePersonCount > 0
|
||||
const poseModeAvailable =
|
||||
poseTrainingAllowed && (Boolean(sample) || hasPosePersons || originalPosePersonCount > 0)
|
||||
const poseModeActive = showPoseSkeleton && poseModeAvailable
|
||||
const annotationLayerHidden = frameBusy || trainingRunning || saving
|
||||
const poseSkeletonVisible = poseModeActive && hasPosePersons && !annotationLayerHidden
|
||||
@ -8322,6 +8580,15 @@ export default function TrainingTab(props: {
|
||||
focusedPoseOverlayPersonIndex === null || personIndex === focusedPoseOverlayPersonIndex,
|
||||
[focusedPoseOverlayPersonIndex]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (poseTrainingAllowed || !showPoseSkeleton) return
|
||||
|
||||
setShowPoseSkeleton(false)
|
||||
setActivePosePersonIndex(null)
|
||||
setPendingPoseKeypoint(null)
|
||||
setSelectedPoseKeypointAction(null)
|
||||
}, [poseTrainingAllowed, showPoseSkeleton])
|
||||
const poseFaceImageZoomActive =
|
||||
poseModeActive &&
|
||||
poseKeypointMapView === 'head' &&
|
||||
@ -8829,8 +9096,8 @@ export default function TrainingTab(props: {
|
||||
className={modeButtonClass(poseModeActive)}
|
||||
aria-pressed={poseModeActive}
|
||||
onClick={() => {
|
||||
resetBoxDrawingMode()
|
||||
setShowPoseSkeleton(true)
|
||||
setActiveBoxIndex(null)
|
||||
}}
|
||||
title="Pose-Skelett korrigieren"
|
||||
>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user