Let queued video, image, and music jobs open in an inspect modal.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-09-03 00:15:02 -05:00
co-authored by Cursor
parent 53990192e4
commit f05f8f3597
12 changed files with 1257 additions and 37 deletions
+76 -1
View File
@@ -124,14 +124,32 @@
<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.
<button type="button" class="text-amber-200 hover:text-amber-100" @click="inspectJobId = studioJobId || liveMusicId">Inspect</button>
·
<NuxtLink to="/queue" class="text-amber-200 hover:text-amber-100">Open Queue</NuxtLink>
</p>
<div v-if="musicPresets.length" class="rounded-2xl border border-white/10 bg-zinc-950/40 p-4">
<label class="block text-sm">
<span class="mb-1 block font-medium text-zinc-300">Templates</span>
<select v-model="presetId" class="w-full rounded-xl border border-white/10 bg-zinc-950 px-3 py-2 text-sm" @change="applyPreset">
<option value="">Load a saved template</option>
<option v-for="preset in musicPresets" :key="preset.id" :value="preset.id">{{ preset.name }}</option>
</select>
</label>
</div>
</section>
<section class="glass min-w-0 max-w-full space-y-4 rounded-3xl p-5 sm:p-6">
<div>
<h2 class="font-display text-2xl font-bold">Output</h2>
<p class="mt-1 text-sm text-zinc-400">{{ status || 'Waiting for a generate.' }}</p>
<button
v-if="studioJobId || liveMusicId"
type="button"
class="mt-2 text-xs text-amber-200 hover:text-amber-100"
@click="inspectJobId = studioJobId || liveMusicId"
>Inspect this job</button>
</div>
<div v-if="busy" class="h-2 overflow-hidden rounded-full bg-zinc-800">
<div class="progress-glow h-full rounded-full bg-amber-400 transition-[width]" :style="{ width: `${Math.max(progress, 4)}%` }" />
@@ -150,6 +168,7 @@
</p>
</section>
</main>
<QueuedJobModal :job-id="inspectJobId" @close="inspectJobId = ''" />
</div>
</template>
@@ -167,6 +186,8 @@ import {
} from '~/utils/music'
import { ensureGenerationNotifyPermission, notifyGenerationComplete } from '~/utils/generationNotify'
import type { LibraryFolder } from '~/utils/libraryBrowse'
import { LOAD_MUSIC_JOB_KEY } from '~/utils/queuedJob'
import type { GenerationPreset, MusicPresetSettings } from '~/utils/generationPresets'
const tagsPlaceholder = DEFAULT_MUSIC_TAGS
const lyricsPlaceholder = DEFAULT_MUSIC_LYRICS
@@ -197,6 +218,10 @@ const studioJobId = ref('')
const queued = ref(false)
const queueCount = ref(0)
const downloadName = ref('track.flac')
const inspectJobId = ref('')
const liveMusicId = ref('')
const musicPresets = ref<GenerationPreset[]>([])
const presetId = ref('')
let source: EventSource | null = null
let listenGen = 0
let settled = false
@@ -253,12 +278,15 @@ async function refreshStudioQueue() {
jobs?: Array<{
id: string
status: string
kind?: string
liveJobId?: string
lastError?: string
}>
waitingCount?: number
}>('/api/studio-queue').catch(() => ({ jobs: [] as Array<{ id: string; status: string; liveJobId?: string; lastError?: string }>, waitingCount: 0 }))
}>('/api/studio-queue').catch(() => ({ jobs: [] as Array<{ id: string; status: string; kind?: string; liveJobId?: string; lastError?: string }>, waitingCount: 0 }))
const rows = data.jobs || []
const musicRow = rows.find(job => job.kind === 'music' && (job.status === 'waiting' || job.status === 'running' || job.status === 'held'))
liveMusicId.value = musicRow?.id || ''
queueCount.value = data.waitingCount || rows.filter(job => job.status === 'waiting').length
if (!studioJobId.value) return
const row = rows.find(job => job.id === studioJobId.value)
@@ -406,8 +434,55 @@ watch(instrumental, (on) => {
if (on && !lyrics.value.trim()) lyrics.value = DEFAULT_MUSIC_LYRICS
})
function applyIncomingMusic() {
try {
const raw = sessionStorage.getItem(LOAD_MUSIC_JOB_KEY)
if (!raw) return
sessionStorage.removeItem(LOAD_MUSIC_JOB_KEY)
const loaded = JSON.parse(raw) as {
name?: string
tags?: string
lyrics?: string
instrumental?: boolean
duration?: number
steps?: number
seed?: string
folderId?: string
}
if (typeof loaded.tags === 'string') tags.value = loaded.tags
if (typeof loaded.lyrics === 'string') lyrics.value = loaded.lyrics
instrumental.value = loaded.instrumental === true
if (typeof loaded.duration === 'number') duration.value = loaded.duration
if (typeof loaded.steps === 'number') steps.value = loaded.steps
if (typeof loaded.seed === 'string') seed.value = loaded.seed
if (typeof loaded.name === 'string') name.value = loaded.name
if (loaded.folderId && folders.value.some(folder => folder.id === loaded.folderId)) folderId.value = loaded.folderId
status.value = 'Loaded from the queue'
} catch { /* ignore */ }
}
async function loadMusicPresets() {
const data = await $fetch<{ presets?: GenerationPreset[] }>('/api/generation-presets', { query: { kind: 'music' } }).catch(() => ({ presets: [] }))
musicPresets.value = data.presets || []
}
function applyPreset() {
const preset = musicPresets.value.find(item => item.id === presetId.value)
if (!preset) return
const settings = preset.settings as MusicPresetSettings
if (typeof settings.tags === 'string') tags.value = settings.tags
if (typeof settings.lyrics === 'string') lyrics.value = settings.lyrics
instrumental.value = settings.instrumental === true
if (typeof settings.duration === 'number') duration.value = settings.duration
if (typeof settings.steps === 'number') steps.value = settings.steps
if (typeof settings.seed === 'number') seed.value = String(settings.seed)
name.value = preset.name
}
onMounted(async () => {
await loadLibrary()
await loadMusicPresets()
applyIncomingMusic()
await refreshStudioQueue()
stopQueuePoll()
queuePoll = setInterval(() => { void refreshStudioQueue() }, 8000)