Let clips move between folders, save busy jobs, and fail dropped Comfy runs.
Held jobs restore the form after a 3-minute wait, hidden stills stay blurred in the picker, and reloading Comfy no longer leaves xaigen spinning. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+54
-6
@@ -43,17 +43,32 @@ export async function comfyFetch(path: string, init?: RequestInit) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function uploadImage(file: { filename: string; data: Buffer; type?: string }) {
|
||||
export async function uploadImage(file: { filename: string; data: Buffer; type?: string }, jobId?: string) {
|
||||
const body = new FormData()
|
||||
const blob = new Blob([new Uint8Array(file.data)], { type: file.type || 'application/octet-stream' })
|
||||
body.append('image', blob, file.filename)
|
||||
const filename = comfyInputFilename(file.filename, jobId)
|
||||
body.append('image', blob, filename)
|
||||
body.append('overwrite', 'true')
|
||||
body.append('type', 'input')
|
||||
const res = await comfyFetch('/upload/image', { method: 'POST', body })
|
||||
if (!res.ok) {
|
||||
throw createError({ statusCode: 502, statusMessage: `Image upload failed (${res.status})` })
|
||||
}
|
||||
return (await res.json()) as { name: string; subfolder?: string; type?: string }
|
||||
const uploaded = (await res.json()) as { name: string; subfolder?: string; type?: string }
|
||||
return {
|
||||
name: uploaded.name || filename,
|
||||
subfolder: uploaded.subfolder || '',
|
||||
type: uploaded.type || 'input'
|
||||
}
|
||||
}
|
||||
|
||||
export function comfyInputFilename(original: string, jobId?: string) {
|
||||
const raw = String(original || 'still.png')
|
||||
const dot = raw.lastIndexOf('.')
|
||||
const ext = (dot >= 0 ? raw.slice(dot) : '.png').replace(/[^.a-zA-Z0-9]/g, '') || '.png'
|
||||
const base = (dot >= 0 ? raw.slice(0, dot) : raw).replace(/[^a-zA-Z0-9._-]+/g, '_').slice(0, 48) || 'still'
|
||||
const id = String(jobId || crypto.randomUUID()).replace(/-/g, '').slice(0, 10)
|
||||
return `aigen_${id}_${base}${ext}`
|
||||
}
|
||||
|
||||
export async function queuePrompt(graph: unknown, clientId: string) {
|
||||
@@ -173,14 +188,44 @@ export function extractClipMetaFromHistory(entry: unknown) {
|
||||
return meta
|
||||
}
|
||||
|
||||
export async function fetchLiveQueue() {
|
||||
try {
|
||||
const res = await comfyFetch('/queue', { signal: AbortSignal.timeout(2500) })
|
||||
if (!res.ok) return null
|
||||
const payload = await res.json() as { queue_running?: unknown[]; queue_pending?: unknown[] }
|
||||
return {
|
||||
running: Array.isArray(payload.queue_running) ? payload.queue_running.length : 0,
|
||||
pending: Array.isArray(payload.queue_pending) ? payload.queue_pending.length : 0
|
||||
}
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function isComfyPromptDropped(promptId: string) {
|
||||
if (!promptId) return false
|
||||
const queue = await fetchLiveQueue()
|
||||
if (!queue) return false
|
||||
if (queue.running > 0 || queue.pending > 0) return false
|
||||
const history = await fetchHistory(promptId)
|
||||
const entry = history?.[promptId] as { status?: { status_str?: string; completed?: boolean } } | undefined
|
||||
if (!entry) return true
|
||||
const inspected = inspectHistory(history, promptId)
|
||||
if (inspected.video) return false
|
||||
const status = entry.status?.status_str
|
||||
if (status === 'error' || status === 'interrupted') return true
|
||||
if (entry.status?.completed) return true
|
||||
return true
|
||||
}
|
||||
|
||||
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] || history) as {
|
||||
status?: { status_str?: string; completed?: boolean }
|
||||
}
|
||||
const error = entry?.status?.status_str === 'error'
|
||||
? 'ComfyUI reported an execution error'
|
||||
const error = entry?.status?.status_str === 'error' || entry?.status?.status_str === 'interrupted'
|
||||
? (entry.status.status_str === 'interrupted' ? 'Job interrupted.' : 'ComfyUI reported an execution error')
|
||||
: null
|
||||
return { video, completed: Boolean(video), error }
|
||||
}
|
||||
@@ -217,6 +262,7 @@ export function purgeComfyEnabled() {
|
||||
async function purgeOnDesktop(opts: {
|
||||
video?: { filename: string; subfolder: string; type: string }
|
||||
imageName?: string
|
||||
imageSubfolder?: string
|
||||
}) {
|
||||
const config = useRuntimeConfig()
|
||||
const controlUrl = String(config.comfyControlUrl || process.env.COMFY_CONTROL_URL || '').replace(/\/$/, '')
|
||||
@@ -232,6 +278,7 @@ async function purgeOnDesktop(opts: {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
imageName: opts.imageName || '',
|
||||
imageSubfolder: opts.imageSubfolder || '',
|
||||
video: opts.video || null
|
||||
}),
|
||||
signal: AbortSignal.timeout(8000)
|
||||
@@ -244,13 +291,14 @@ async function purgeOnDesktop(opts: {
|
||||
export async function purgeComfyArtifacts(opts: {
|
||||
video?: { filename: string; subfolder: string; type: string }
|
||||
imageName?: string
|
||||
imageSubfolder?: string
|
||||
promptId?: string
|
||||
}) {
|
||||
if (!purgeComfyEnabled()) return
|
||||
await purgeOnDesktop(opts)
|
||||
const files: { filename: string; subfolder: string; type: string }[] = []
|
||||
if (opts.video?.filename) files.push(opts.video)
|
||||
if (opts.imageName) files.push({ filename: opts.imageName, subfolder: '', type: 'input' })
|
||||
if (opts.imageName) files.push({ filename: opts.imageName, subfolder: opts.imageSubfolder || '', type: 'input' })
|
||||
for (const file of files) {
|
||||
try {
|
||||
await comfyFetch('/aigen/purge', {
|
||||
|
||||
Reference in New Issue
Block a user