function comfyBase() { const config = useRuntimeConfig() const host = (config.comfyHost || process.env.COMFY_HOST || '').replace(/\/$/, '') if (!host) { throw createError({ statusCode: 500, statusMessage: 'COMFY_HOST is not configured' }) } return host } export function getComfyHost() { return comfyBase() } export function comfyWsUrl(clientId: string) { return `${comfyBase().replace(/^http/, 'ws')}/ws?clientId=${encodeURIComponent(clientId)}` } export async function comfyFetch(path: string, init?: RequestInit) { const url = `${comfyBase()}${path}` try { return await fetch(url, init) } catch (error) { throw createError({ statusCode: 502, statusMessage: `ComfyUI host unreachable (${comfyBase()})`, data: { cause: error instanceof Error ? error.message : String(error) } }) } } export async function uploadImage(file: { filename: string; data: Buffer; type?: 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) 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 } } export async function queuePrompt(graph: unknown, clientId: string) { const res = await comfyFetch('/prompt', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: graph, client_id: clientId }) }) const payload = await res.json().catch(() => ({})) if (!res.ok) { const message = (payload as { error?: { message?: string } }).error?.message || (payload as { node_errors?: unknown }).node_errors || `Queue failed (${res.status})` throw createError({ statusCode: 502, statusMessage: String(message), data: payload }) } return payload as { prompt_id: string; number?: number } } export async function interruptComfy() { const res = await comfyFetch('/interrupt', { method: 'POST' }) return res.ok } export async function fetchHistory(promptId: string) { const res = await comfyFetch(`/history/${encodeURIComponent(promptId)}`) if (!res.ok) return null return (await res.json()) as Record } export function extractVideo(history: Record | null, promptId: string) { if (!history) return null const entry = (history[promptId] || Object.values(history)[0]) as { outputs?: Record> } | 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') } } } } return null } export async function probeComfy() { try { const res = await fetch(`${comfyBase()}/system_stats`, { signal: AbortSignal.timeout(2500) }) if (!res.ok) return { ok: false, host: comfyBase() } const stats = await res.json() return { ok: true, host: comfyBase(), stats } } catch { return { ok: false, host: comfyBase() } } }