Keep queueing video extensions while a clip is already generating.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-09-02 07:04:39 -05:00
co-authored by Cursor
parent b1b005669d
commit aa2cefbcf2
4 changed files with 419 additions and 268 deletions
+44 -30
View File
@@ -261,7 +261,6 @@
:scripts="directorScripts"
v-model:selected-id="directorScriptId"
v-model:name="directorScriptName"
:disabled="videoBusy"
@load="loadDirectorScript"
@save="saveDirectorScript"
@remove="deleteDirectorScript"
@@ -402,7 +401,6 @@
<button
type="button"
class="text-xs text-red-300 hover:text-red-200"
:disabled="editBusy"
@click="removeEditPass(item.id)"
>
Remove
@@ -415,13 +413,11 @@
:placeholder="extraPassPlaceholder"
:keep-items="activeKeepPrompts"
:keep-heading="keepPromptHeading"
:disabled="editBusy"
/>
</div>
<button
type="button"
class="w-full rounded-2xl border border-dashed border-white/15 px-4 py-2.5 text-sm text-zinc-300 transition hover:border-amber-300/40 hover:text-amber-100 disabled:cursor-not-allowed disabled:opacity-50"
:disabled="editBusy"
@click="queueEditPass"
>
{{ extraPassButtonLabel }}
@@ -521,7 +517,6 @@
<button
type="button"
class="text-xs text-red-300 hover:text-red-200"
:disabled="videoBusy"
@click="removeQueuedExtension(item.id)"
>
Remove
@@ -569,7 +564,6 @@
<button
type="button"
class="w-full rounded-2xl border border-dashed border-white/15 px-4 py-2.5 text-sm text-zinc-300 transition hover:border-amber-300/40 hover:text-amber-100 disabled:cursor-not-allowed disabled:opacity-50"
:disabled="videoBusy"
@click="queueExtension"
>
+ Queue Extension
@@ -1443,11 +1437,12 @@
<button
type="button"
class="w-full rounded-2xl bg-amber-400 px-4 py-2.5 text-sm font-semibold text-zinc-950 transition hover:bg-amber-300 disabled:cursor-not-allowed disabled:opacity-50"
:disabled="!extendPrompt.trim() || lastFrameLoading || videoBusy"
:disabled="!extendPrompt.trim()"
@click="generateExtension"
>
Generate Extension
{{ videoBusy ? 'Queue extension' : 'Generate Extension' }}
</button>
<p v-if="videoBusy" class="text-xs text-zinc-500">The current job keeps running in Output. This extension waits in the job queue.</p>
</div>
</div>
@@ -5744,27 +5739,19 @@ async function openExtend(item?: LibraryClip) {
async function generateExtension() {
const id = clipIdFromPlayer()
if (!id || !extendPrompt.value.trim()) return
const switching = videoBusy.value
statusBusy.value = false
videoBusy.value = true
watchingLibrary.value = false
stopListen('video')
videoSettledUi = false
progress.value = 1
awaitingReveal.value = false
concealOutput.value = hideThumbnail.value || currentClip.value?.hideThumbnail === true
lockedSave.value = false
chainStep.value = 1
chainTotal.value = 1
chainLabel.value = ''
overallProgress.value = 0
completedChainStep.value = 0
activeChainPlan.value = []
statusMessage.value = 'Extracting last frame & stitching extension...'
startTimer('video')
const hideOut = hideThumbnail.value
try {
await ensureGenerationNotifyPermission()
const started = await $fetch<{ jobId: string; steps: number; hideThumbnail?: boolean; folderLocked?: boolean }>('/api/extend', {
const started = await $fetch<{
jobId: string
studioJobId?: string
queued?: boolean
steps: number
hideThumbnail?: boolean
folderLocked?: boolean
width?: number
height?: number
}>('/api/extend', {
method: 'POST',
body: {
clipId: id,
@@ -5776,13 +5763,40 @@ async function generateExtension() {
}
})
closeExtend()
if (switching) toast('Switched to this extension. The other job will keep saving to the library.')
videoJobId.value = started.jobId
maxStep.value = started.steps
if (started.queued) {
outputStudioJobId.value = started.studioJobId || ''
outputFocus.value = 'video'
videoAwaitingBeast.value = true
statusMessage.value = 'Queued. Waiting for Beast to be free.'
toast('Queued behind the current job.')
await refreshStudioQueue()
return
}
statusBusy.value = false
videoBusy.value = true
watchingLibrary.value = false
stopListen('video')
videoSettledUi = false
progress.value = 1
awaitingReveal.value = false
concealOutput.value = started.hideThumbnail === true || hideOut
lockedSave.value = false
chainStep.value = 1
chainTotal.value = 1
chainLabel.value = ''
overallProgress.value = 0
completedChainStep.value = 0
activeChainPlan.value = []
statusMessage.value = 'Extracting last frame & stitching extension...'
startTimer('video')
if (started.width && started.height) playerSize.value = { width: started.width, height: started.height }
videoJobId.value = started.jobId
outputStudioJobId.value = started.studioJobId || ''
maxStep.value = started.steps
persistActiveJob('video', started.jobId, started.hideThumbnail === true || concealOutput.value, started.folderLocked === true)
listen('video', started.jobId, started.hideThumbnail === true || concealOutput.value, started.folderLocked === true)
void pollComfyHealth()
void refreshStudioQueue()
} catch (error: any) {
videoBusy.value = false
statusBusy.value = false