Add ACE-Step 1.5 and YuE on Music, and put Video/Image/Music cards back.
Recover finished Comfy audio when queueing hangs so a completed track is not stuck on Queueing. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+108
-2
@@ -171,11 +171,12 @@ function comfyQueueMessage(payload: unknown, status: number) {
|
||||
return message || `Queue failed (${status})`
|
||||
}
|
||||
|
||||
export async function queuePrompt(graph: unknown, clientId: string) {
|
||||
export async function queuePrompt(graph: unknown, clientId: string, opts?: { timeoutMs?: number }) {
|
||||
const res = await comfyFetch('/prompt', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ prompt: graph, client_id: clientId })
|
||||
body: JSON.stringify({ prompt: graph, client_id: clientId }),
|
||||
...(opts?.timeoutMs ? { signal: AbortSignal.timeout(opts.timeoutMs) } : {})
|
||||
})
|
||||
const payload = await res.json().catch(() => ({}))
|
||||
if (!res.ok) {
|
||||
@@ -295,6 +296,111 @@ export function extractAudio(history: Record<string, unknown> | null, promptId:
|
||||
return findAudio(history)
|
||||
}
|
||||
|
||||
export function comfyAudioPrefix() {
|
||||
return String(useRuntimeConfig().comfyAudioPrefix || process.env.COMFY_AUDIO_PREFIX || 'audio/AIGen')
|
||||
}
|
||||
|
||||
export function isOurComfyAudio(audio: { filename: string; subfolder?: string }) {
|
||||
const prefix = comfyAudioPrefix().replace(/\/$/, '')
|
||||
const parts = prefix.split('/')
|
||||
const namePrefix = parts[parts.length - 1] || 'AIGen'
|
||||
const sub = parts.length > 1 ? parts.slice(0, -1).join('/') : 'audio'
|
||||
const nameOk = String(audio.filename || '').startsWith(namePrefix)
|
||||
const subOk = !audio.subfolder || audio.subfolder === sub
|
||||
return nameOk && subOk
|
||||
}
|
||||
|
||||
export function extractAceMetaFromHistory(entry: unknown) {
|
||||
const prompt = (entry as { prompt?: unknown[] })?.prompt
|
||||
const graph = Array.isArray(prompt) ? prompt[2] : null
|
||||
const meta = {
|
||||
tags: '',
|
||||
lyrics: '',
|
||||
duration: 0,
|
||||
steps: 0,
|
||||
seed: 0,
|
||||
cfg: 0,
|
||||
lyricsStrength: 0.9,
|
||||
samplerName: '',
|
||||
scheduler: '',
|
||||
engine: 'ace-step'
|
||||
}
|
||||
if (!graph || typeof graph !== 'object') return meta
|
||||
for (const node of Object.values(graph as Record<string, { class_type?: string; inputs?: Record<string, unknown> }>)) {
|
||||
const inputs = node?.inputs || {}
|
||||
if (node?.class_type === 'TextEncodeAceStepAudio' || node?.class_type === 'TextEncodeAceStepAudio1.5') {
|
||||
meta.tags = String(inputs.tags || '')
|
||||
meta.lyrics = String(inputs.lyrics || '')
|
||||
meta.lyricsStrength = Number(inputs.lyrics_strength || meta.lyricsStrength)
|
||||
}
|
||||
if (node?.class_type === 'YUE_Stage_A_Sampler') {
|
||||
meta.tags = String(inputs.genres_prompt || '')
|
||||
meta.lyrics = String(inputs.lyrics_prompt || '')
|
||||
meta.engine = 'yue'
|
||||
meta.duration = Number(inputs.prompt_end_time || meta.duration)
|
||||
meta.seed = Number(inputs.seed || meta.seed)
|
||||
}
|
||||
if (node?.class_type === 'EmptyAceStepLatentAudio' || node?.class_type === 'EmptyAceStep1.5LatentAudio') {
|
||||
meta.duration = Number(inputs.seconds || 0)
|
||||
}
|
||||
if (node?.class_type === 'KSampler') {
|
||||
meta.steps = Number(inputs.steps || 0)
|
||||
meta.seed = Number(inputs.seed || 0)
|
||||
meta.cfg = Number(inputs.cfg || 0)
|
||||
meta.samplerName = String(inputs.sampler_name || '')
|
||||
meta.scheduler = String(inputs.scheduler || '')
|
||||
}
|
||||
if (node?.class_type === 'CheckpointLoaderSimple') {
|
||||
const ckpt = String(inputs.ckpt_name || '')
|
||||
if (/1\.5|turbo_aio/i.test(ckpt)) meta.engine = 'ace-step-1.5'
|
||||
}
|
||||
}
|
||||
return meta
|
||||
}
|
||||
|
||||
function historyCompletedAt(entry: unknown) {
|
||||
const messages = (entry as { status?: { messages?: Array<[string, { timestamp?: number }]> } })?.status?.messages
|
||||
const last = messages?.find(item => item[0] === 'execution_success')
|
||||
return Number(last?.[1]?.timestamp || 0)
|
||||
}
|
||||
|
||||
export function findHistoryAudio(
|
||||
history: Record<string, unknown> | null,
|
||||
opts?: { promptId?: string; tags?: string; since?: number }
|
||||
) {
|
||||
if (!history) return null
|
||||
if (opts?.promptId) {
|
||||
const audio = extractAudio(history, opts.promptId)
|
||||
if (audio) return { promptId: opts.promptId, audio }
|
||||
}
|
||||
const want = String(opts?.tags || '').trim()
|
||||
const matches: {
|
||||
promptId: string
|
||||
audio: NonNullable<ReturnType<typeof extractAudio>>
|
||||
ts: number
|
||||
tags: string
|
||||
}[] = []
|
||||
for (const [id, entry] of Object.entries(history)) {
|
||||
const audio = extractAudio({ [id]: entry as Record<string, unknown> }, id)
|
||||
if (!audio || !isOurComfyAudio(audio)) continue
|
||||
const ts = historyCompletedAt(entry)
|
||||
if (opts?.since && ts && ts < opts.since) continue
|
||||
const meta = extractAceMetaFromHistory(entry)
|
||||
matches.push({ promptId: id, audio, ts: historyCompletedAt(entry), tags: meta.tags })
|
||||
}
|
||||
if (!matches.length) return null
|
||||
const tagged = want
|
||||
? matches.filter((item) => {
|
||||
const tags = item.tags.trim()
|
||||
return tags === want || tags.includes(want) || want.includes(tags)
|
||||
})
|
||||
: matches
|
||||
const pool = want ? tagged : matches
|
||||
if (!pool.length) return null
|
||||
pool.sort((a, b) => b.ts - a.ts)
|
||||
return { promptId: pool[0].promptId, audio: pool[0].audio }
|
||||
}
|
||||
|
||||
export function extractVideo(history: Record<string, unknown> | null, promptId: string) {
|
||||
if (!history) return null
|
||||
const wrapped = history[promptId] as { outputs?: Record<string, unknown> } | undefined
|
||||
|
||||
Reference in New Issue
Block a user