Keep remaining multi-shot clips on a Queue page so prompts can be edited between shots, with process-next or overnight run-all.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+36
-6
@@ -22,6 +22,13 @@
|
||||
<button type="button" class="rounded-full border border-white/10 px-3 py-1 text-zinc-300 hover:border-amber-300/50 hover:text-white" @click="settingsOpen = true">
|
||||
Settings
|
||||
</button>
|
||||
<NuxtLink
|
||||
to="/queue"
|
||||
class="rounded-full border px-3 py-1 text-zinc-300 hover:border-amber-300/50 hover:text-white"
|
||||
:class="queuePendingCount ? 'border-amber-300/40 text-amber-100' : 'border-white/10'"
|
||||
>
|
||||
Queue<span v-if="queuePendingCount" class="ml-1.5 text-amber-200">{{ queuePendingCount }}</span>
|
||||
</NuxtLink>
|
||||
<a v-if="authEnabled" href="/api/auth/logout" class="text-zinc-400 hover:text-white">Sign out</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -519,6 +526,16 @@
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<label
|
||||
v-if="studioMode === 'video' && queuedExtensionCount > 0"
|
||||
class="flex cursor-pointer items-start gap-3 rounded-2xl border border-white/10 bg-zinc-950/40 px-4 py-3"
|
||||
>
|
||||
<input v-model="queueAutoRun" type="checkbox" class="mt-1">
|
||||
<span>
|
||||
<span class="block text-sm font-medium text-zinc-200">Run remaining shots automatically</span>
|
||||
<span class="mt-0.5 block text-xs text-zinc-500">Overnight set-and-forget. Leave this off to edit prompts on the Queue page and process the next shot, or the next few, when you are ready.</span>
|
||||
</span>
|
||||
</label>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<button
|
||||
type="button"
|
||||
@@ -1414,6 +1431,8 @@ const renameDraft = ref('')
|
||||
const renameInput = ref<HTMLInputElement | null>(null)
|
||||
const unlockPassword = ref('')
|
||||
const settingsOpen = ref(false)
|
||||
const queueAutoRun = ref(false)
|
||||
const queuePendingCount = ref(0)
|
||||
const pickerOpen = ref(false)
|
||||
const pickerSlot = ref<'main' | 'editRef' | number>('main')
|
||||
const libraryFilter = ref<'all' | 'images' | 'videos'>('all')
|
||||
@@ -1588,11 +1607,14 @@ const editBlockReason = computed(() => {
|
||||
return ''
|
||||
})
|
||||
const generateLabel = computed(() => {
|
||||
const n = shotScriptMode.value ? Math.max(0, parsedShots.value.length - 1) : extensionQueue.value.length
|
||||
const n = queuedExtensionCount.value
|
||||
if (videoBusy.value) return 'Generating…'
|
||||
if (!n) return 'Generate Video'
|
||||
return n === 1 ? 'Generate Initial + 1 Extension' : `Generate Initial + ${n} Extensions`
|
||||
})
|
||||
const queuedExtensionCount = computed(() => (
|
||||
shotScriptMode.value ? Math.max(0, parsedShots.value.length - 1) : extensionQueue.value.length
|
||||
))
|
||||
const editLabel = computed(() => {
|
||||
const n = editPassQueue.value.length
|
||||
if (!n) return 'Edit image'
|
||||
@@ -1889,8 +1911,12 @@ function applyLibrary(data: { folders?: LibraryFolder[]; clips?: LibraryClip[];
|
||||
}
|
||||
|
||||
async function loadLibrary() {
|
||||
const data = await $fetch<{ folders?: LibraryFolder[]; clips?: LibraryClip[]; stills?: LibraryStill[]; drafts?: RetryDraft[] }>('/api/library')
|
||||
const [data, queued] = await Promise.all([
|
||||
$fetch<{ folders?: LibraryFolder[]; clips?: LibraryClip[]; stills?: LibraryStill[]; drafts?: RetryDraft[] }>('/api/library'),
|
||||
$fetch<{ pendingCount?: number }>('/api/queues').catch(() => ({ pendingCount: 0 }))
|
||||
])
|
||||
applyLibrary(data)
|
||||
queuePendingCount.value = queued.pendingCount || 0
|
||||
}
|
||||
|
||||
function applyComfyHealth(health: {
|
||||
@@ -3113,6 +3139,7 @@ async function generate() {
|
||||
if (activeDraftId) body.append('draftId', activeDraftId)
|
||||
if (queued.length) {
|
||||
body.append('extensions', JSON.stringify(queued))
|
||||
body.append('queueAutoRun', String(queueAutoRun.value))
|
||||
}
|
||||
videoDownloadName.value = clipFileName(clipName.value.trim() || initialPrompt.split(/[.!?\n]/)[0] || 'aigen')
|
||||
const started = await $fetch<{ jobId: string; seed: number; width: number; height: number; steps: number; turbo: boolean; hideThumbnail?: boolean; folderLocked?: boolean }>('/api/generate', {
|
||||
@@ -3295,7 +3322,8 @@ function applyVideoEvent(payload: Record<string, any>, hidden: boolean, folderLo
|
||||
if (complete && (payload.clipId || payload.filename)) {
|
||||
const total = Math.max(1, Number(payload.chainTotal || 0) || 0, Number(chainTotal.value || 0) || 0)
|
||||
const step = Math.max(1, Number(payload.chainStep || 0) || 0, Number(chainStep.value || 0) || 0)
|
||||
if (total > 1 && step < total) {
|
||||
const remaining = Number(payload.queuedRemaining || 0)
|
||||
if (remaining > 0 || (total > 1 && step < total)) {
|
||||
completedChainStep.value = Math.max(completedChainStep.value, step)
|
||||
loadLibrary().catch(() => null)
|
||||
const dest = folders.value.find(folder => folder.id === folderId.value)
|
||||
@@ -3307,9 +3335,11 @@ function applyVideoEvent(payload: Record<string, any>, hidden: boolean, folderLo
|
||||
videoUrl.value = clipVideoUrl(payload.clipId)
|
||||
concealOutput.value = hidden || payload.hideThumbnail === true
|
||||
}
|
||||
statusMessage.value = `Shot ${step} of ${total} saved, but the remaining shots did not start.`
|
||||
progress.value = Math.min(Number(payload.progress || progress.value || 99), 99)
|
||||
toast(`Shot ${step} of ${total} saved. Remaining shots did not run — extend this clip from the library.`)
|
||||
const left = remaining || Math.max(0, total - step)
|
||||
statusMessage.value = `Shot ${step} of ${total} saved. ${left} waiting on Queue.`
|
||||
progress.value = Number(payload.progress || progress.value || 100)
|
||||
toast(`Shot ${step} of ${total} saved. ${left} shot${left === 1 ? '' : 's'} waiting on Queue — edit prompts or process next.`)
|
||||
queuePendingCount.value = left
|
||||
videoBusy.value = false
|
||||
statusBusy.value = false
|
||||
stopTimer('video')
|
||||
|
||||
Reference in New Issue
Block a user