Do not mark a waiting I2V complete when the image job finishes.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+28
-1
@@ -3127,6 +3127,13 @@ function applyGenerationPreset(preset: GenerationPreset) {
|
|||||||
toast(skipped
|
toast(skipped
|
||||||
? `Loaded “${preset.name}”. Skipped ${skipped} xAIGen-only LoRA${skipped === 1 ? '' : 's'}.`
|
? `Loaded “${preset.name}”. Skipped ${skipped} xAIGen-only LoRA${skipped === 1 ? '' : 's'}.`
|
||||||
: `Loaded “${preset.name}”.`)
|
: `Loaded “${preset.name}”.`)
|
||||||
|
console.info('[aigen preset apply]', {
|
||||||
|
kind: preset.kind,
|
||||||
|
name: preset.name,
|
||||||
|
hasFile: Boolean(file.value),
|
||||||
|
inputStillId: inputStillId.value,
|
||||||
|
hasPreview: Boolean(preview.value)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadGenerationPreset(id: string) {
|
function loadGenerationPreset(id: string) {
|
||||||
@@ -4895,6 +4902,19 @@ async function generate() {
|
|||||||
toast('LTX Pink Cherry is not available on this instance')
|
toast('LTX Pink Cherry is not available on this instance')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
console.info('[aigen generate click]', {
|
||||||
|
hasFile: Boolean(file.value),
|
||||||
|
fileName: file.value?.name || '',
|
||||||
|
fileSize: file.value?.size || 0,
|
||||||
|
fileType: file.value?.type || '',
|
||||||
|
inputStillId: inputStillId.value,
|
||||||
|
hasPreview: Boolean(preview.value),
|
||||||
|
hasInputStill: hasInputStill.value,
|
||||||
|
mode: studioMode.value,
|
||||||
|
workflow: videoWorkflow.value,
|
||||||
|
block: videoBlockReason.value || '',
|
||||||
|
promptChars: prompt.value.trim().length
|
||||||
|
})
|
||||||
if (videoBlockReason.value) {
|
if (videoBlockReason.value) {
|
||||||
toast(videoBlockReason.value)
|
toast(videoBlockReason.value)
|
||||||
return
|
return
|
||||||
@@ -4970,6 +4990,13 @@ async function generate() {
|
|||||||
}
|
}
|
||||||
const downloadName = clipFileName(clipName.value.trim() || initialPrompt.split(/[.!?\n]/)[0] || 'aigen')
|
const downloadName = clipFileName(clipName.value.trim() || initialPrompt.split(/[.!?\n]/)[0] || 'aigen')
|
||||||
const chainPlan = plannedChain.value.map(part => ({ ...part }))
|
const chainPlan = plannedChain.value.map(part => ({ ...part }))
|
||||||
|
console.info('[aigen generate payload]', {
|
||||||
|
keys: [...body.keys()],
|
||||||
|
hasImage: body.has('image'),
|
||||||
|
stillId: inputStillId.value,
|
||||||
|
promptChars: initialPrompt.length,
|
||||||
|
workflow: videoWorkflow.value
|
||||||
|
})
|
||||||
const started = await $fetch<{
|
const started = await $fetch<{
|
||||||
jobId: string
|
jobId: string
|
||||||
studioJobId?: string
|
studioJobId?: string
|
||||||
@@ -5142,6 +5169,7 @@ function applyEditEvent(payload: Record<string, any>, hidden: boolean, folderLoc
|
|||||||
clearActiveJob('edit')
|
clearActiveJob('edit')
|
||||||
scheduleLibraryRefresh()
|
scheduleLibraryRefresh()
|
||||||
void pollComfyHealth()
|
void pollComfyHealth()
|
||||||
|
void refreshStudioQueue()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5174,7 +5202,6 @@ function applyVideoEvent(payload: Record<string, any>, hidden: boolean, folderLo
|
|||||||
const complete = payload.type === 'complete' || payload.status === 'complete'
|
const complete = payload.type === 'complete' || payload.status === 'complete'
|
||||||
const deferred = payload.type === 'deferred' || payload.status === 'deferred'
|
const deferred = payload.type === 'deferred' || payload.status === 'deferred'
|
||||||
if (videoSettledUi) return
|
if (videoSettledUi) return
|
||||||
if (payload.stillId) return
|
|
||||||
|
|
||||||
if (payload.type === 'checkpoint' && payload.clipId) {
|
if (payload.type === 'checkpoint' && payload.clipId) {
|
||||||
completedChainStep.value = Math.max(completedChainStep.value, Number(payload.chainStep || chainStep.value) || 0)
|
completedChainStep.value = Math.max(completedChainStep.value, Number(payload.chainStep || chainStep.value) || 0)
|
||||||
|
|||||||
@@ -201,6 +201,19 @@ export default defineEventHandler(async (event) => {
|
|||||||
hideInput
|
hideInput
|
||||||
})
|
})
|
||||||
: null)
|
: null)
|
||||||
|
if (!textToVideo && !still?.id) {
|
||||||
|
throw createError({ statusCode: 400, statusMessage: 'An initial image is required' })
|
||||||
|
}
|
||||||
|
console.info('[aigen generate]', {
|
||||||
|
hasImage: Boolean(image?.data?.length),
|
||||||
|
imageBytes: image?.data?.length || 0,
|
||||||
|
imageName: image?.filename || '',
|
||||||
|
stillId: still?.id || '',
|
||||||
|
postedStillId,
|
||||||
|
workflow,
|
||||||
|
promptChars: promptRaw.length,
|
||||||
|
textToVideo
|
||||||
|
})
|
||||||
const referenceStillIds: Array<string | null> = [null, null, null, null]
|
const referenceStillIds: Array<string | null> = [null, null, null, null]
|
||||||
if (useIdentityRefs) {
|
if (useIdentityRefs) {
|
||||||
for (const [index, ref] of referenceImages.entries()) {
|
for (const [index, ref] of referenceImages.entries()) {
|
||||||
|
|||||||
@@ -5,7 +5,12 @@ export default defineEventHandler((event) => {
|
|||||||
const { owner } = assertLibraryOwner(event)
|
const { owner } = assertLibraryOwner(event)
|
||||||
const full = String(getQuery(event).full || '') === '1'
|
const full = String(getQuery(event).full || '') === '1'
|
||||||
const active = listStudioJobs(owner)
|
const active = listStudioJobs(owner)
|
||||||
.filter(job => job.status === 'waiting' || job.status === 'running' || job.status === 'held')
|
.filter(job => (
|
||||||
|
job.status === 'waiting'
|
||||||
|
|| job.status === 'running'
|
||||||
|
|| job.status === 'held'
|
||||||
|
|| (job.status === 'error' && Date.now() - job.updatedAt < 60 * 60 * 1000)
|
||||||
|
))
|
||||||
const claimed = new Set(active.map(job => job.shotQueueId).filter(Boolean) as string[])
|
const claimed = new Set(active.map(job => job.shotQueueId).filter(Boolean) as string[])
|
||||||
const queues = listShotQueues(owner)
|
const queues = listShotQueues(owner)
|
||||||
const byId = new Map(queues.map(queue => [queue.id, queue]))
|
const byId = new Map(queues.map(queue => [queue.id, queue]))
|
||||||
|
|||||||
+19
-21
@@ -4,7 +4,7 @@ import { getJob, listJobs, emitJob, type Job } from '~/server/utils/jobs'
|
|||||||
import { listPendingJobs, patchPendingJob, readPendingJob, deletePendingJob } from '~/server/utils/pending'
|
import { listPendingJobs, patchPendingJob, readPendingJob, deletePendingJob } from '~/server/utils/pending'
|
||||||
import { getShotQueue } from '~/server/utils/shotQueue'
|
import { getShotQueue } from '~/server/utils/shotQueue'
|
||||||
import { fetchLiveQueue } from '~/server/utils/comfy'
|
import { fetchLiveQueue } from '~/server/utils/comfy'
|
||||||
import { isLtxWorkflow, LTX_DISABLED_MESSAGE, ltxWorkflowEnabled, parseVideoWorkflow, type VideoWorkflowId } from '~/utils/videoModels'
|
import { isLtxWorkflow, isTextToVideo, LTX_DISABLED_MESSAGE, ltxWorkflowEnabled, parseVideoWorkflow, type VideoWorkflowId } from '~/utils/videoModels'
|
||||||
import { persistLoraFields } from '~/server/utils/loras'
|
import { persistLoraFields } from '~/server/utils/loras'
|
||||||
import { resolveLoraStack } from '~/utils/loras'
|
import { resolveLoraStack } from '~/utils/loras'
|
||||||
|
|
||||||
@@ -484,7 +484,7 @@ export async function markStudioHeld(owner: string, id: string) {
|
|||||||
|
|
||||||
export async function markStudioSettled(owner: string, liveJobId: string, status: 'complete' | 'error' | 'cancelled', error?: string) {
|
export async function markStudioSettled(owner: string, liveJobId: string, status: 'complete' | 'error' | 'cancelled', error?: string) {
|
||||||
return mutate(owner, (jobs) => {
|
return mutate(owner, (jobs) => {
|
||||||
const job = jobs.find(item => item.liveJobId === liveJobId) || jobs.find(item => item.status === 'running')
|
const job = jobs.find(item => item.liveJobId === liveJobId)
|
||||||
if (!job) return null
|
if (!job) return null
|
||||||
if (job.holdForCutIn && status === 'complete') {
|
if (job.holdForCutIn && status === 'complete') {
|
||||||
job.status = 'held'
|
job.status = 'held'
|
||||||
@@ -750,6 +750,16 @@ export async function startStudioJob(item: StudioJob) {
|
|||||||
payload.globalLocks,
|
payload.globalLocks,
|
||||||
payload.shotPermanenceRefs
|
payload.shotPermanenceRefs
|
||||||
)
|
)
|
||||||
|
const stillFile = payload.stillId && existsSync(stillPath(item.ownerKey, payload.stillId))
|
||||||
|
? {
|
||||||
|
filename: payload.stillFilename || 'still.png',
|
||||||
|
data: readFileSync(stillPath(item.ownerKey, payload.stillId)),
|
||||||
|
type: 'image/png'
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
if (!isTextToVideo(workflow) && !stillFile) {
|
||||||
|
throw new Error('The input still is missing from the library')
|
||||||
|
}
|
||||||
const job = createJob()
|
const job = createJob()
|
||||||
live = job
|
live = job
|
||||||
job.kind = 'video'
|
job.kind = 'video'
|
||||||
@@ -840,13 +850,6 @@ export async function startStudioJob(item: StudioJob) {
|
|||||||
|
|
||||||
await markStudioLive(item.ownerKey, item.id, job.id, job.library.queueId)
|
await markStudioLive(item.ownerKey, item.id, job.id, job.library.queueId)
|
||||||
|
|
||||||
const stillFile = payload.stillId && existsSync(stillPath(item.ownerKey, payload.stillId))
|
|
||||||
? {
|
|
||||||
filename: payload.stillFilename || 'still.png',
|
|
||||||
data: readFileSync(stillPath(item.ownerKey, payload.stillId)),
|
|
||||||
type: 'image/png'
|
|
||||||
}
|
|
||||||
: null
|
|
||||||
const referenceImages: Array<{ filename: string; data: Buffer; type?: string } | null> = [null, null, null, null]
|
const referenceImages: Array<{ filename: string; data: Buffer; type?: string } | null> = [null, null, null, null]
|
||||||
for (const [index, stillId] of (payload.referenceStillIds || []).entries()) {
|
for (const [index, stillId] of (payload.referenceStillIds || []).entries()) {
|
||||||
if (!stillId || index >= 4) continue
|
if (!stillId || index >= 4) continue
|
||||||
@@ -914,17 +917,12 @@ export async function onLiveVideoSettled(job: Job) {
|
|||||||
const failed = job.status === 'error' || job.status === 'cancelled'
|
const failed = job.status === 'error' || job.status === 'cancelled'
|
||||||
const snapshot = readStore(owner)
|
const snapshot = readStore(owner)
|
||||||
const rowNow = snapshot.jobs.find(item => item.liveJobId === job.id)
|
const rowNow = snapshot.jobs.find(item => item.liveJobId === job.id)
|
||||||
|| snapshot.jobs.find(item => (
|
|| (job.library?.queueId
|
||||||
|
? snapshot.jobs.find(item => (
|
||||||
item.status === 'running'
|
item.status === 'running'
|
||||||
&& Boolean(item.shotQueueId)
|
|
||||||
&& item.shotQueueId === job.library?.queueId
|
&& item.shotQueueId === job.library?.queueId
|
||||||
))
|
))
|
||||||
|| snapshot.jobs.find(item => (
|
: undefined)
|
||||||
item.status === 'running'
|
|
||||||
&& !item.shotQueueId
|
|
||||||
&& !job.library?.queueId
|
|
||||||
&& (!item.liveJobId || item.liveJobId === job.id)
|
|
||||||
))
|
|
||||||
const userPause = !failed && rowNow?.holdForCutIn !== true && (
|
const userPause = !failed && rowNow?.holdForCutIn !== true && (
|
||||||
rowNow?.pauseAfterCurrent === true
|
rowNow?.pauseAfterCurrent === true
|
||||||
|| rowNow?.pausedByUser === true
|
|| rowNow?.pausedByUser === true
|
||||||
@@ -937,7 +935,7 @@ export async function onLiveVideoSettled(job: Job) {
|
|||||||
}
|
}
|
||||||
if (userPause && remaining > 0) {
|
if (userPause && remaining > 0) {
|
||||||
await mutateStore(owner, (store) => {
|
await mutateStore(owner, (store) => {
|
||||||
const row = store.jobs.find(item => item.liveJobId === job.id) || store.jobs.find(item => item.status === 'running')
|
const row = store.jobs.find(item => item.liveJobId === job.id)
|
||||||
if (!row) {
|
if (!row) {
|
||||||
syncPausedFlag(store)
|
syncPausedFlag(store)
|
||||||
return
|
return
|
||||||
@@ -953,7 +951,7 @@ export async function onLiveVideoSettled(job: Job) {
|
|||||||
})
|
})
|
||||||
} else if (cutInHold) {
|
} else if (cutInHold) {
|
||||||
await mutate(owner, (jobs) => {
|
await mutate(owner, (jobs) => {
|
||||||
const row = jobs.find(item => item.liveJobId === job.id) || jobs.find(item => item.status === 'running')
|
const row = jobs.find(item => item.liveJobId === job.id)
|
||||||
if (!row) return
|
if (!row) return
|
||||||
row.status = 'held'
|
row.status = 'held'
|
||||||
row.liveJobId = undefined
|
row.liveJobId = undefined
|
||||||
@@ -963,7 +961,7 @@ export async function onLiveVideoSettled(job: Job) {
|
|||||||
})
|
})
|
||||||
} else if (userPause) {
|
} else if (userPause) {
|
||||||
await mutateStore(owner, (store) => {
|
await mutateStore(owner, (store) => {
|
||||||
const row = store.jobs.find(item => item.liveJobId === job.id) || store.jobs.find(item => item.status === 'running')
|
const row = store.jobs.find(item => item.liveJobId === job.id)
|
||||||
if (!row) {
|
if (!row) {
|
||||||
syncPausedFlag(store)
|
syncPausedFlag(store)
|
||||||
return
|
return
|
||||||
@@ -990,7 +988,7 @@ export async function onLiveVideoSettled(job: Job) {
|
|||||||
: 'error'
|
: 'error'
|
||||||
await markStudioSettled(owner, job.id, status, job.error)
|
await markStudioSettled(owner, job.id, status, job.error)
|
||||||
}
|
}
|
||||||
kickStudioQueue()
|
await kickStudioQueue()
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyLiveJobPause(live: Job | undefined, pause: boolean, restoreAutoRun?: boolean) {
|
function applyLiveJobPause(live: Job | undefined, pause: boolean, restoreAutoRun?: boolean) {
|
||||||
|
|||||||
Reference in New Issue
Block a user