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:
Towsty
2026-08-25 18:23:23 -05:00
co-authored by Cursor
parent ca51fddea3
commit c4e6af9f77
8 changed files with 326 additions and 182 deletions
+52 -17
View File
@@ -68,28 +68,63 @@ export async function fetchHistory(promptId: string) {
return (await res.json()) as Record<string, unknown>
}
export function extractVideo(history: Record<string, unknown> | null, promptId: string) {
if (!history) return null
const entry = (history[promptId] || Object.values(history)[0]) as { outputs?: Record<string, Record<string, unknown>> } | undefined
const outputs = entry?.outputs || {}
for (const node of Object.values(outputs)) {
for (const key of ['videos', 'gifs', 'images', 'files']) {
const items = node[key]
if (!Array.isArray(items)) continue
const match = items.find((item: { filename?: string }) => String(item.filename || '').toLowerCase().endsWith('.mp4'))
|| items[0]
if (match?.filename) {
return {
filename: String(match.filename),
subfolder: String(match.subfolder || 'video'),
type: String(match.type || 'output')
}
}
function isVideoFile(item: { filename?: string; format?: string } | null | undefined) {
if (!item) return false
const name = String(item.filename || '').toLowerCase()
const format = String(item.format || '').toLowerCase()
return name.endsWith('.mp4') || name.endsWith('.webm') || format.includes('video') || format.includes('mp4')
}
function normalizeVideo(item: { filename?: string; subfolder?: string; type?: string }) {
return {
filename: String(item.filename),
subfolder: String(item.subfolder || ''),
type: String(item.type || 'output')
}
}
function findVideo(value: unknown, depth = 0): { filename: string; subfolder: string; type: string } | null {
if (!value || typeof value !== 'object' || depth > 8) return null
if (Array.isArray(value)) {
const files = value.filter((item): item is { filename?: string; format?: string; subfolder?: string; type?: string } => Boolean(item && typeof item === 'object'))
const match = files.find(item => String(item.filename || '').toLowerCase().endsWith('.mp4'))
|| files.find(isVideoFile)
if (match?.filename) return normalizeVideo(match)
for (const item of value) {
const nested = findVideo(item, depth + 1)
if (nested) return nested
}
return null
}
const record = value as { filename?: string; format?: string; subfolder?: string; type?: string }
if (record.filename && isVideoFile(record)) return normalizeVideo(record)
for (const nested of Object.values(value as Record<string, unknown>)) {
const found = findVideo(nested, depth + 1)
if (found) return found
}
return null
}
export function extractVideo(history: Record<string, unknown> | null, promptId: string) {
if (!history) return null
const entry = (history[promptId] || Object.values(history)[0]) as { outputs?: Record<string, unknown> } | undefined
return findVideo(entry?.outputs || {})
}
export function inspectHistory(history: Record<string, unknown> | null, promptId: string) {
const video = extractVideo(history, promptId)
if (!history) return { video: null, completed: false, error: null as string | null }
const entry = (history[promptId] || Object.values(history)[0]) as {
status?: { status_str?: string; completed?: boolean; messages?: unknown[] }
} | undefined
const status = entry?.status
const error = status?.status_str === 'error'
? 'ComfyUI reported an execution error'
: null
const completed = Boolean(video) || status?.completed === true || status?.status_str === 'success'
return { video, completed, error }
}
export async function probeComfy() {
try {
const res = await fetch(`${comfyBase()}/system_stats`, { signal: AbortSignal.timeout(2500) })