Fix generation progress getting stuck at 1% after Comfy finishes.
Poll Comfy history and the job snapshot so the browser still gets the video when the live stream is buffered or the websocket misses completion. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+61
-25
@@ -365,6 +365,8 @@ const currentPassword = ref('')
|
||||
const nextPassword = ref('')
|
||||
let timer: ReturnType<typeof setInterval> | null = null
|
||||
let source: EventSource | null = null
|
||||
let pollTimer: ReturnType<typeof setInterval> | null = null
|
||||
let settledUi = false
|
||||
|
||||
const elapsedLabel = computed(() => {
|
||||
const seconds = Math.floor(elapsedMs.value / 1000)
|
||||
@@ -593,6 +595,7 @@ async function generate() {
|
||||
return
|
||||
}
|
||||
busy.value = true
|
||||
settledUi = false
|
||||
progress.value = 1
|
||||
videoUrl.value = ''
|
||||
awaitingReveal.value = false
|
||||
@@ -629,37 +632,70 @@ async function generate() {
|
||||
}
|
||||
}
|
||||
|
||||
function listen(id: string, hidden: boolean) {
|
||||
function stopListen() {
|
||||
source?.close()
|
||||
source = null
|
||||
if (pollTimer) {
|
||||
clearInterval(pollTimer)
|
||||
pollTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
function applyJobEvent(payload: Record<string, any>, hidden: boolean) {
|
||||
if (payload.message) statusMessage.value = payload.message
|
||||
if (typeof payload.progress === 'number') progress.value = payload.progress
|
||||
if (typeof payload.step === 'number') step.value = payload.step
|
||||
if (typeof payload.maxStep === 'number') maxStep.value = payload.maxStep
|
||||
|
||||
const failed = payload.type === 'error' || payload.status === 'error' || payload.status === 'cancelled'
|
||||
const complete = payload.type === 'complete' || payload.status === 'complete'
|
||||
if (settledUi) return
|
||||
|
||||
if (complete && (payload.clipId || payload.filename)) {
|
||||
settledUi = true
|
||||
const clipUrl = payload.clipId
|
||||
? `/api/library/clips/${payload.clipId}/video`
|
||||
: `/api/media?filename=${encodeURIComponent(payload.filename)}&subfolder=${encodeURIComponent(payload.subfolder || 'video')}&type=${encodeURIComponent(payload.mediaType || 'output')}`
|
||||
videoUrl.value = clipUrl
|
||||
awaitingReveal.value = Boolean(payload.hideThumbnail || hidden)
|
||||
busy.value = false
|
||||
stopTimer()
|
||||
stopListen()
|
||||
loadLibrary().catch(() => null)
|
||||
return
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
settledUi = true
|
||||
busy.value = false
|
||||
stopTimer()
|
||||
stopListen()
|
||||
toast(payload.error || payload.message || 'Generation failed')
|
||||
}
|
||||
}
|
||||
|
||||
function listen(id: string, hidden: boolean) {
|
||||
stopListen()
|
||||
source = new EventSource(`/api/generate/${id}/stream`)
|
||||
source.onmessage = (event) => {
|
||||
const payload = JSON.parse(event.data)
|
||||
if (payload.message) statusMessage.value = payload.message
|
||||
if (typeof payload.progress === 'number') progress.value = payload.progress
|
||||
if (typeof payload.step === 'number') step.value = payload.step
|
||||
if (typeof payload.maxStep === 'number') maxStep.value = payload.maxStep
|
||||
if (payload.type === 'complete') {
|
||||
const clipUrl = payload.clipId
|
||||
? `/api/library/clips/${payload.clipId}/video`
|
||||
: `/api/media?filename=${encodeURIComponent(payload.filename)}&subfolder=${encodeURIComponent(payload.subfolder || 'video')}&type=${encodeURIComponent(payload.mediaType || 'output')}`
|
||||
videoUrl.value = clipUrl
|
||||
awaitingReveal.value = Boolean(payload.hideThumbnail || hidden)
|
||||
busy.value = false
|
||||
stopTimer()
|
||||
source?.close()
|
||||
loadLibrary().catch(() => null)
|
||||
applyJobEvent(JSON.parse(event.data), hidden)
|
||||
}
|
||||
const tick = async () => {
|
||||
if (settledUi) {
|
||||
stopListen()
|
||||
return
|
||||
}
|
||||
if (payload.type === 'error') {
|
||||
busy.value = false
|
||||
stopTimer()
|
||||
source?.close()
|
||||
toast(payload.error || payload.message || 'Generation failed')
|
||||
try {
|
||||
const snap = await $fetch<Record<string, any>>(`/api/generate/${id}`)
|
||||
applyJobEvent(snap, hidden)
|
||||
} catch {
|
||||
// Job snapshot can 404 briefly after a restart; keep polling while busy.
|
||||
}
|
||||
}
|
||||
source.onerror = () => {
|
||||
if (!busy.value) return
|
||||
toast('Progress stream disconnected. The ComfyUI job may still be running.')
|
||||
}
|
||||
void tick()
|
||||
pollTimer = setInterval(() => {
|
||||
void tick()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
async function cancelJob() {
|
||||
|
||||
Reference in New Issue
Block a user