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:
Towsty
2026-08-30 23:16:19 -05:00
co-authored by Cursor
parent 8df265cbf3
commit 755aaeede8
17 changed files with 791 additions and 233 deletions
+32 -4
View File
@@ -110,6 +110,34 @@ function patchScaleMegapixels(graph: WorkflowGraph, megapixels: number) {
}
}
function linkSource(value: unknown): string | null {
return Array.isArray(value) && typeof value[0] === 'string' ? value[0] : null
}
function stripScaleNodes(graph: WorkflowGraph) {
for (const [id, node] of Object.entries(graph)) {
if (node.class_type !== IMAGE_SCALE_TO_TOTAL_PIXELS) continue
const src = Array.isArray(node.inputs.image) ? node.inputs.image : null
for (const [otherId, other] of Object.entries(graph)) {
if (otherId === id) continue
for (const [key, value] of Object.entries(other.inputs)) {
if (linkSource(value) !== id || !Array.isArray(value)) continue
other.inputs[key] = src ? [src[0], value[1] === 0 ? src[1] : value[1]] : value
}
}
delete graph[id]
}
}
function applyScaleMegapixels(graph: WorkflowGraph, megapixels?: number) {
const mp = Number(megapixels)
if (Number.isFinite(mp) && mp > 0) {
patchScaleMegapixels(graph, mp)
return
}
stripScaleNodes(graph)
}
function graphHasMaskInput(graph: WorkflowGraph) {
return Object.values(graph).some((node) => {
const mask = node.inputs?.mask
@@ -401,7 +429,7 @@ function buildKreaItiWorkflow(params: ImageV2BuildParams) {
setInput(graph, '15', 'scheduler', 'simple')
setInput(graph, '15', 'denoise', denoise)
setInput(graph, SAVE, 'filename_prefix', params.filenamePrefix || `v2-krea-${params.mode}`)
patchScaleMegapixels(graph, params.megapixels ?? 1)
applyScaleMegapixels(graph, params.megapixels)
const userLoras = applyImageV2UserLoras(graph, imageV2UserStack(params, 'krea'), 'krea')
assertImageV2Graph(graph, params.mode, compose ? params.imageBName : undefined, 'krea')
@@ -420,7 +448,7 @@ function buildKreaItiWorkflow(params: ImageV2BuildParams) {
steps,
cfg,
seed: params.seed,
megapixels: clampImageScaleMegapixels(params.megapixels ?? 1)
megapixels: Number(params.megapixels) > 0 ? clampImageScaleMegapixels(params.megapixels) : 0
}))
return { graph, workflowFile, loaders, prompt, strength: denoise }
}
@@ -513,7 +541,7 @@ export function buildImageV2Workflow(params: ImageV2BuildParams) {
setInput(graph, SCHEDULER, 'steps', steps)
setInput(graph, CFG, 'cfg', cfg)
setInput(graph, SAVE, 'filename_prefix', params.filenamePrefix || (generate ? 'v2-generate' : 'v2'))
if (!generate) patchScaleMegapixels(graph, params.megapixels ?? 1)
if (!generate) applyScaleMegapixels(graph, params.megapixels)
const userLoras = applyImageV2UserLoras(graph, imageV2UserStack(params, 'flux'), 'flux')
const guider = Object.values(graph).find(node => node.class_type === 'CFGGuider' || node.class_type === 'BasicGuider')
@@ -538,7 +566,7 @@ export function buildImageV2Workflow(params: ImageV2BuildParams) {
steps,
cfg,
seed: params.seed,
megapixels: generate ? undefined : clampImageScaleMegapixels(params.megapixels ?? 1)
megapixels: generate ? undefined : (Number(params.megapixels) > 0 ? clampImageScaleMegapixels(params.megapixels) : 0)
}))
return { graph, workflowFile, loaders, prompt, strength }