Start waiting image jobs after video, and add Start now on idle queue rows.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-08-28 18:16:16 -05:00
co-authored by Cursor
parent 0d8766bcb6
commit 5f91553b8a
4 changed files with 30 additions and 14 deletions
+2 -2
View File
@@ -842,12 +842,12 @@
</p> </p>
</div> </div>
<button <button
v-if="job.status === 'waiting' && canCutInStudioJob" v-if="job.status === 'waiting'"
type="button" type="button"
class="shrink-0 rounded-full border border-sky-300/40 px-2.5 py-1 text-xs text-sky-100 hover:border-sky-200 hover:text-white" class="shrink-0 rounded-full border border-sky-300/40 px-2.5 py-1 text-xs text-sky-100 hover:border-sky-200 hover:text-white"
@click="cutInStudioJob(job.id)" @click="cutInStudioJob(job.id)"
> >
{{ job.cutIn ? 'Next' : 'Run next' }} {{ job.cutIn ? 'Next' : (canCutInStudioJob ? 'Run next' : 'Start now') }}
</button> </button>
<button <button
v-if="job.status === 'waiting' || job.status === 'held'" v-if="job.status === 'waiting' || job.status === 'held'"
+2 -2
View File
@@ -88,13 +88,13 @@
</div> </div>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
<button <button
v-if="job.status === 'waiting' && canCutIn" v-if="job.status === 'waiting'"
type="button" type="button"
class="rounded-full border border-sky-300/40 px-3 py-1.5 text-xs text-sky-100 hover:border-sky-200 disabled:cursor-not-allowed disabled:opacity-50" class="rounded-full border border-sky-300/40 px-3 py-1.5 text-xs text-sky-100 hover:border-sky-200 disabled:cursor-not-allowed disabled:opacity-50"
:disabled="Boolean(busyId)" :disabled="Boolean(busyId)"
@click="cutIn(job)" @click="cutIn(job)"
> >
{{ job.cutIn ? 'Next after current shot' : 'Run next' }} {{ job.cutIn ? 'Next after current shot' : (canCutIn ? 'Run next' : 'Start now') }}
</button> </button>
<template v-if="job.shots"> <template v-if="job.shots">
<template v-if="canProcessShots(job)"> <template v-if="canProcessShots(job)">
+2 -1
View File
@@ -1,5 +1,5 @@
import { listShotQueues, summarizeQueue } from '~/server/utils/shotQueue' import { listShotQueues, summarizeQueue } from '~/server/utils/shotQueue'
import { listStudioJobs, summarizeStudioJob } from '~/server/utils/studioQueue' import { kickStudioQueue, listStudioJobs, summarizeStudioJob } from '~/server/utils/studioQueue'
export default defineEventHandler((event) => { export default defineEventHandler((event) => {
const { owner } = assertLibraryOwner(event) const { owner } = assertLibraryOwner(event)
@@ -51,6 +51,7 @@ export default defineEventHandler((event) => {
.filter(queue => !claimed.has(queue.id) && queue.status !== 'complete') .filter(queue => !claimed.has(queue.id) && queue.status !== 'complete')
.map(summarizeQueue) .map(summarizeQueue)
: [] : []
if (jobs.some(job => job.status === 'waiting')) void kickStudioQueue()
return { return {
jobs, jobs,
orphans, orphans,
+24 -9
View File
@@ -423,10 +423,12 @@ export async function requestCutIn(owner: string, id: string) {
const jobsNow = readJobs(owner) const jobsNow = readJobs(owner)
const running = jobsNow.find(job => job.status === 'running') const running = jobsNow.find(job => job.status === 'running')
|| jobsNow.find(job => job.status === 'held') || jobsNow.find(job => job.status === 'held')
if (!running) { const target = jobsNow.find(job => job.id === id)
throw createError({ statusCode: 409, statusMessage: 'Nothing is generating, so this job can just wait its turn' }) if (!target) throw createError({ statusCode: 404, statusMessage: 'Queued job not found' })
if (target.status !== 'waiting') {
throw createError({ statusCode: 409, statusMessage: 'Only a waiting job can cut in' })
} }
if (running.id === id) { if (running?.id === id) {
throw createError({ statusCode: 409, statusMessage: 'That job is already running' }) throw createError({ statusCode: 409, statusMessage: 'That job is already running' })
} }
const updated = await mutate(owner, (jobs) => { const updated = await mutate(owner, (jobs) => {
@@ -440,7 +442,7 @@ export async function requestCutIn(owner: string, id: string) {
for (const item of jobs) { for (const item of jobs) {
if (item.id !== id && item.status === 'waiting') item.cutIn = false if (item.id !== id && item.status === 'waiting') item.cutIn = false
} }
const active = jobs.find(item => item.id === running.id) const active = running ? jobs.find(item => item.id === running.id) : undefined
if (active && active.status === 'running') { if (active && active.status === 'running') {
active.holdForCutIn = true active.holdForCutIn = true
active.resumeAutoRun = active.payload.queueAutoRun === true active.resumeAutoRun = active.payload.queueAutoRun === true
@@ -448,14 +450,13 @@ export async function requestCutIn(owner: string, id: string) {
} }
return structuredClone(job) return structuredClone(job)
}) })
const liveId = running.liveJobId if (running?.liveJobId) {
if (liveId) { const live = getJob(running.liveJobId)
const live = getJob(liveId)
if (live?.library) { if (live?.library) {
live.library.stopAfterCurrent = true live.library.stopAfterCurrent = true
} }
} }
kickStudioQueue() await kickStudioQueue()
return updated return updated
} }
@@ -533,6 +534,15 @@ export function kickStudioQueue() {
return run return run
} }
let kickRetryTimer: ReturnType<typeof setTimeout> | null = null
function scheduleKickRetry() {
if (kickRetryTimer) return
kickRetryTimer = setTimeout(() => {
kickRetryTimer = null
kickStudioQueue()
}, 2500)
}
function pendingAlive(job: StudioJob) { function pendingAlive(job: StudioJob) {
if (job.liveJobId && readPendingJob(job.liveJobId)?.promptId) return true if (job.liveJobId && readPendingJob(job.liveJobId)?.promptId) return true
if (!job.shotQueueId) return false if (!job.shotQueueId) return false
@@ -595,7 +605,12 @@ async function dispatchStudioQueue() {
jobs: current.jobs.map(item => structuredClone(item)) jobs: current.jobs.map(item => structuredClone(item))
} }
}) })
if (await videoJobsBusy()) return if (await videoJobsBusy()) {
if (listOwnersWithStudioQueues().some(owner => readJobs(owner).some(job => job.status === 'waiting'))) {
scheduleKickRetry()
}
return
}
const cutIn = pickCutIn(store.jobs) const cutIn = pickCutIn(store.jobs)
if (cutIn) { if (cutIn) {
await startStudioJob(cutIn) await startStudioJob(cutIn)