diff --git a/backend/dist/nsfwapp-linux-amd64 b/backend/dist/nsfwapp-linux-amd64 index 5e94a33..b25e963 100644 Binary files a/backend/dist/nsfwapp-linux-amd64 and b/backend/dist/nsfwapp-linux-amd64 differ diff --git a/backend/dist/nsfwapp.exe b/backend/dist/nsfwapp.exe index 5af03b1..2802450 100644 Binary files a/backend/dist/nsfwapp.exe and b/backend/dist/nsfwapp.exe differ diff --git a/backend/dist/nsfwapp_amd64.deb b/backend/dist/nsfwapp_amd64.deb index 87cb3f3..6268d1a 100644 Binary files a/backend/dist/nsfwapp_amd64.deb and b/backend/dist/nsfwapp_amd64.deb differ diff --git a/backend/recorder.go b/backend/recorder.go index 0c9a5fe..4d235aa 100644 --- a/backend/recorder.go +++ b/backend/recorder.go @@ -1435,6 +1435,8 @@ func publishQueuedPostworkState(job *RecordJob, postKey, postFile, postAssetID s Running: st.Running, MaxParallel: st.MaxParallel, }) + + _ = publishJobSnapshot(job.ID) } func handlePostworkEnqueueFailure(job *RecordJob, out string, postTarget JobStatus, postFile, postAssetID string) { diff --git a/backend/sse.go b/backend/sse.go index ba292f9..5cfd742 100644 --- a/backend/sse.go +++ b/backend/sse.go @@ -421,12 +421,30 @@ func isVisiblePostworkJobForSSE(j *RecordJob) bool { if !isPostworkJobForSSE(j) { return false } + if isActivePostworkJobForSSE(j) { + return true + } if isTerminalJobStatusForSSE(j.Status) { return false } return true } +func isActivePostworkJobForSSE(j *RecordJob) bool { + if j == nil { + return false + } + if strings.TrimSpace(j.PostWorkKey) == "" { + return false + } + if j.PostWork == nil { + return true + } + + state := strings.ToLower(strings.TrimSpace(j.PostWork.State)) + return state == "" || state == "queued" || state == "running" +} + func shouldPublishModelEventForJob(j *RecordJob) bool { return isVisibleDownloadJobForSSE(j) || isVisiblePostworkJobForSSE(j) } diff --git a/backend/sse_test.go b/backend/sse_test.go new file mode 100644 index 0000000..d04e89e --- /dev/null +++ b/backend/sse_test.go @@ -0,0 +1,29 @@ +package main + +import "testing" + +func TestIsVisiblePostworkJobForSSEAllowsFinishedQueuedPostwork(t *testing.T) { + job := &RecordJob{ + Status: JobFinished, + Phase: "postwork", + PostWorkKey: "postwork:test", + PostWork: &PostWorkKeyStatus{ + State: "queued", + }, + } + + if !isVisiblePostworkJobForSSE(job) { + t.Fatal("expected finished job with queued postwork to remain visible for SSE") + } +} + +func TestIsVisiblePostworkJobForSSERejectsFinishedWithoutActivePostwork(t *testing.T) { + job := &RecordJob{ + Status: JobFinished, + Phase: "postwork", + } + + if isVisiblePostworkJobForSSE(job) { + t.Fatal("expected finished job without active postwork to be hidden from SSE") + } +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index bf5723f..a1734e0 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1560,6 +1560,11 @@ export default function App() { const isVisiblePostworkJobForSSE = (job: RecordJob): boolean => { if (!isPostworkJobForCount(job)) return false + const pw = (job as any)?.postWork + const state = String(pw?.state ?? '').trim().toLowerCase() + if (String((job as any)?.postWorkKey ?? '').trim() && (!state || state === 'queued' || state === 'running')) { + return true + } if (isTerminalJobStatusForCount((job as any)?.status)) return false return true } @@ -3141,6 +3146,10 @@ export default function App() { } } + const onGlobalJob = (ev: MessageEvent) => { + onModelJobEventRef.current?.(ev) + } + void loadJobs() void loadDoneCount() void loadTaskStateOnce() @@ -3161,6 +3170,7 @@ export default function App() { } es.addEventListener('doneChanged', onDoneChanged as any) + es.addEventListener('job', onGlobalJob as any) es.addEventListener('autostart', onAutostart as any) es.addEventListener('finishedPostwork', onFinishedPostwork as any) es.addEventListener('taskState', onTaskState as any) @@ -3189,6 +3199,7 @@ export default function App() { if (es) { es.removeEventListener('doneChanged', onDoneChanged as any) + es.removeEventListener('job', onGlobalJob as any) es.removeEventListener('autostart', onAutostart as any) es.removeEventListener('finishedPostwork', onFinishedPostwork as any) es.removeEventListener('taskState', onTaskState as any)