Put ACE-Step music jobs on the same studio queue as video and image.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-09-03 00:08:07 -05:00
co-authored by Cursor
parent 9cb8b9bda3
commit 53990192e4
8 changed files with 233 additions and 34 deletions
+84 -2
View File
@@ -3,6 +3,7 @@
<AppHeader
subtitle="ACE-Step · vocals"
current="music"
:queue-count="queueCount"
/>
<main class="mx-auto grid min-w-0 max-w-7xl gap-6 px-4 py-6 sm:px-6 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)] lg:items-stretch">
@@ -121,6 +122,10 @@
{{ busy ? (status || 'Generating…') : 'Generate' }}
</button>
<p v-if="blockReason" class="text-xs text-zinc-400">{{ blockReason }}</p>
<p v-else-if="queued" class="text-xs text-zinc-400">
Waiting in the job queue.
<NuxtLink to="/queue" class="text-amber-200 hover:text-amber-100">Open Queue</NuxtLink>
</p>
</section>
<section class="glass min-w-0 max-w-full space-y-4 rounded-3xl p-5 sm:p-6">
@@ -188,10 +193,14 @@ const progress = ref(0)
const audioUrl = ref('')
const trackId = ref('')
const jobId = ref('')
const studioJobId = ref('')
const queued = ref(false)
const queueCount = ref(0)
const downloadName = ref('track.flac')
let source: EventSource | null = null
let listenGen = 0
let settled = false
let queuePoll: ReturnType<typeof setInterval> | null = null
const blocked = computed(() => !tags.value.trim() || (!instrumental.value && !lyrics.value.trim()) || !folderId.value)
const blockReason = computed(() => {
@@ -232,6 +241,55 @@ function stopListen() {
source = null
}
function stopQueuePoll() {
if (queuePoll) {
clearInterval(queuePoll)
queuePoll = null
}
}
async function refreshStudioQueue() {
const data = await $fetch<{
jobs?: Array<{
id: string
status: string
liveJobId?: string
lastError?: string
}>
waitingCount?: number
}>('/api/studio-queue').catch(() => ({ jobs: [] as Array<{ id: string; status: string; liveJobId?: string; lastError?: string }>, waitingCount: 0 }))
const rows = data.jobs || []
queueCount.value = data.waitingCount || rows.filter(job => job.status === 'waiting').length
if (!studioJobId.value) return
const row = rows.find(job => job.id === studioJobId.value)
if (!row) {
if (queued.value && !jobId.value) {
queued.value = false
}
return
}
if (row.status === 'waiting' || (row.status === 'held' && !row.liveJobId)) {
queued.value = true
busy.value = true
status.value = row.status === 'held' ? 'Paused in the job queue' : 'Waiting in the job queue…'
return
}
if (row.status === 'error') {
queued.value = false
busy.value = false
error.value = row.lastError || 'Generation failed'
status.value = error.value
stopQueuePoll()
return
}
if (row.liveJobId && row.liveJobId !== jobId.value) {
queued.value = false
jobId.value = row.liveJobId
status.value = status.value || 'Generating…'
listen(row.liveJobId)
}
}
function applyEvent(payload: Record<string, any>) {
if (payload.message) status.value = payload.message
if (typeof payload.progress === 'number') progress.value = payload.progress
@@ -253,6 +311,10 @@ function applyEvent(payload: Record<string, any>) {
audioUrl.value = `/api/media?${params.toString()}`
}
stopListen()
queued.value = false
studioJobId.value = ''
stopQueuePoll()
queuePoll = setInterval(() => { void refreshStudioQueue() }, 8000)
void notifyGenerationComplete('music')
}
if (payload.type === 'error' || payload.status === 'error') {
@@ -260,7 +322,10 @@ function applyEvent(payload: Record<string, any>) {
busy.value = false
error.value = payload.error || payload.message || 'Generation failed'
status.value = error.value
queued.value = false
stopListen()
stopQueuePoll()
queuePoll = setInterval(() => { void refreshStudioQueue() }, 8000)
}
}
@@ -293,12 +358,15 @@ async function generate() {
await ensureGenerationNotifyPermission()
busy.value = true
settled = false
queued.value = false
progress.value = 2
audioUrl.value = ''
trackId.value = ''
jobId.value = ''
studioJobId.value = ''
status.value = 'Queueing ACE-Step…'
try {
const started = await $fetch<{ jobId: string; name?: string }>(
const started = await $fetch<{ jobId: string; studioJobId?: string; queued?: boolean; name?: string }>(
'/api/generate/music',
{
method: 'POST',
@@ -314,11 +382,21 @@ async function generate() {
}
}
)
jobId.value = started.jobId
studioJobId.value = started.studioJobId || ''
if (started.name) downloadName.value = `${started.name}.flac`
await refreshStudioQueue()
stopQueuePoll()
queuePoll = setInterval(() => { void refreshStudioQueue() }, 2500)
if (started.queued) {
queued.value = true
status.value = 'Waiting in the job queue…'
return
}
jobId.value = started.jobId
listen(started.jobId)
} catch (err: any) {
busy.value = false
queued.value = false
error.value = err?.data?.statusMessage || err?.statusMessage || err?.message || 'Could not queue music generation'
status.value = error.value
}
@@ -330,6 +408,9 @@ watch(instrumental, (on) => {
onMounted(async () => {
await loadLibrary()
await refreshStudioQueue()
stopQueuePoll()
queuePoll = setInterval(() => { void refreshStudioQueue() }, 8000)
const query = useRoute().query
if (typeof query.track === 'string' && query.track) {
audioUrl.value = `/api/library/tracks/${query.track}/audio`
@@ -340,5 +421,6 @@ onMounted(async () => {
onBeforeUnmount(() => {
stopListen()
stopQueuePoll()
})
</script>