Overhaul Studio controls and add image batch from one still.
Video and Image pickers, thumbwheels, aspect icons, and a Scale to MP paddle; extra prompts default to batch so each run starts from the original image. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -37,6 +37,7 @@ export type EditV2RunParams = {
|
||||
referenceStillId?: string
|
||||
loraStack?: import('~/utils/loras').LoraStackItem[]
|
||||
passes?: { prompt: string }[]
|
||||
passMode?: 'batch' | 'chain'
|
||||
}
|
||||
|
||||
export async function runEditV2(job: Job, params: EditV2RunParams) {
|
||||
@@ -61,6 +62,8 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
||||
: []
|
||||
const prompts = [params.prompt, ...extraPasses.map(item => item.prompt)]
|
||||
const chainTotal = prompts.length
|
||||
const batch = params.passMode !== 'chain'
|
||||
const unit = batch ? 'Batch' : 'Pass'
|
||||
library.chainTotal = chainTotal
|
||||
library.familyId = chainTotal > 1 ? (library.familyId || crypto.randomUUID()) : library.familyId
|
||||
|
||||
@@ -83,13 +86,14 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
||||
const { applyImageFill } = await import('~/server/utils/imagePad')
|
||||
const { composeFillPrompt } = await import('~/utils/imageAspect')
|
||||
let current = params.image || null
|
||||
let startPadded = false
|
||||
let mask = params.mask
|
||||
let parentStillId: string | undefined
|
||||
let uploadedRef: { name: string } | null = null
|
||||
let uploadedMask: { name: string } | null = null
|
||||
|
||||
await ensureComfyLoraNames('image')
|
||||
if (!generate) await assertImageScaleToTotalPixelsNode()
|
||||
if (!generate && params.megapixels > 0) await assertImageScaleToTotalPixelsNode()
|
||||
const krea = engine === 'krea' ? await resolveKreaGenerateAssets() : null
|
||||
|
||||
if (!generate && params.mode === 'compose' && params.reference) {
|
||||
@@ -108,7 +112,7 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
||||
library.seed = seed
|
||||
library.chainIndex = index
|
||||
library.chainStep = index + 1
|
||||
library.chainLabel = chainTotal > 1 ? `Pass ${index + 1}` : undefined
|
||||
library.chainLabel = chainTotal > 1 ? `${unit} ${index + 1}` : undefined
|
||||
const passName = stillChainName(library.name || '', index)
|
||||
let prompt = prompts[index]
|
||||
let image = current
|
||||
@@ -151,6 +155,7 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
||||
}, job.id)
|
||||
}
|
||||
padded = filled.padded
|
||||
startPadded = filled.padded
|
||||
prompt = composeFillPrompt(prompt, padded)
|
||||
if (padded) {
|
||||
emitChainJob(job, {
|
||||
@@ -159,6 +164,8 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
||||
progress: 7
|
||||
})
|
||||
}
|
||||
} else if (batch && startPadded) {
|
||||
prompt = composeFillPrompt(prompt, true)
|
||||
}
|
||||
|
||||
if (index === 0 && params.mode === 'refine' && mask && !uploadedMask) {
|
||||
@@ -258,7 +265,7 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
||||
name: passName || undefined,
|
||||
prompt,
|
||||
familyId: library.familyId,
|
||||
parentStillId,
|
||||
parentStillId: batch ? (params.sourceStillId || library.stillId) : parentStillId,
|
||||
chainIndex: index,
|
||||
settings: {
|
||||
kind: 'imagev2',
|
||||
@@ -272,7 +279,8 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
||||
consistencyClip: params.consistencyClip,
|
||||
steps: params.steps,
|
||||
cfg: params.cfg,
|
||||
megapixels: params.mode === 'generate' ? undefined : params.megapixels,
|
||||
megapixels: params.mode === 'generate' || !(params.megapixels > 0) ? undefined : params.megapixels,
|
||||
scaleToTotalPixels: params.mode === 'generate' ? undefined : params.megapixels > 0,
|
||||
turbo: params.turbo === true,
|
||||
strength: params.mode === 'generate' ? undefined : params.strength,
|
||||
width: params.mode === 'generate' ? params.width : undefined,
|
||||
@@ -285,7 +293,7 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
||||
}
|
||||
})
|
||||
job.stillId = still?.id
|
||||
parentStillId = still?.id
|
||||
if (!batch) parentStillId = still?.id
|
||||
await purgeComfyArtifacts({
|
||||
video: { filename: output.filename, subfolder: output.subfolder, type: output.type },
|
||||
imageName: uploaded?.name,
|
||||
@@ -299,16 +307,18 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
||||
if (!last) {
|
||||
emitChainJob(job, {
|
||||
type: 'checkpoint',
|
||||
message: `Pass ${index + 1} saved`,
|
||||
message: `${unit} ${index + 1} of ${chainTotal} saved`,
|
||||
progress: 100,
|
||||
stillId: still?.id,
|
||||
hideThumbnail: job.hideThumbnail,
|
||||
folderLocked: library.folderLocked
|
||||
})
|
||||
current = {
|
||||
filename: still?.filename || `pass_${index + 1}.png`,
|
||||
data: buffer,
|
||||
type: 'image/png'
|
||||
if (!batch) {
|
||||
current = {
|
||||
filename: still?.filename || `pass_${index + 1}.png`,
|
||||
data: buffer,
|
||||
type: 'image/png'
|
||||
}
|
||||
}
|
||||
if (library.stopAfterCurrent === true) break
|
||||
continue
|
||||
@@ -318,7 +328,7 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
||||
emitChainJob(job, {
|
||||
type: 'complete',
|
||||
message: chainTotal > 1
|
||||
? `${engineLabel} v2 chain finished on Beast`
|
||||
? `${engineLabel} v2 ${batch ? 'batch' : 'chain'} finished on Beast`
|
||||
: `${engineLabel} v2 finished on Beast`,
|
||||
progress: 100,
|
||||
stillId: still?.id,
|
||||
|
||||
Reference in New Issue
Block a user