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
+3 -1
View File
@@ -3,7 +3,7 @@ import { parsePostedLoraStack, listStudioLoras, persistLoraFields } from '~/serv
import { assertImageScaleToTotalPixelsNode, comfyConfigured } from '~/server/utils/comfy'
import { imageDimensions } from '~/server/utils/resolution'
import { parseImageAspect } from '~/utils/imageAspect'
import { clampImageCfg, clampImageScaleMegapixels, clampImageSteps, parseImageScaleToTotalPixels } from '~/utils/generationPresets'
import { clampImageCfg, clampImageScaleMegapixels, clampImageSteps, parseImagePassMode, parseImageScaleToTotalPixels } from '~/utils/generationPresets'
function parsePasses(raw: string | undefined) {
if (!raw) return [] as { prompt: string }[]
@@ -35,6 +35,7 @@ export default defineEventHandler(async (event) => {
const prompt = (fields.prompt || '').trim()
const extraPasses = parsePasses(fields.passes)
const passMode = parseImagePassMode(fields.passMode)
const aspect = parseImageAspect(fields.aspect)
if (!image) {
throw createError({ statusCode: 400, statusMessage: 'An input image is required' })
@@ -152,6 +153,7 @@ export default defineEventHandler(async (event) => {
queueAutoRun: false,
negative: (fields.negative || '').trim(),
passes,
passMode,
referenceStillId: savedRef?.id,
referenceStillFilename: savedRef?.filename,
scaleToTotalPixels,
+11 -3
View File
@@ -2,7 +2,7 @@ import { addStudioJob, kickStudioQueue, listStudioJobs, videoJobsBusy } from '~/
import { comfyConfigured } from '~/server/utils/comfy'
import { parseImageAspect, generateSizeForAspect } from '~/utils/imageAspect'
import { imageDimensions } from '~/server/utils/resolution'
import { clampImageCfg, clampImageScaleMegapixels, clampImageSteps } from '~/utils/generationPresets'
import { clampImageCfg, clampImageScaleMegapixels, clampImageSteps, parseImagePassMode } from '~/utils/generationPresets'
import {
IMAGE_V2_CFG_DEFAULT,
IMAGE_V2_CONSISTENCY_CLIP,
@@ -137,6 +137,7 @@ export default defineEventHandler(async (event) => {
)
const prompt = String(fields.prompt || '').trim()
const extraPasses = parsePasses(fields.passes)
const passMode = parseImagePassMode(fields.passMode)
const aspect = parseImageAspect(fields.aspect)
if (!prompt && (mode === 'generate' || aspect === 'auto')) {
throw createError({ statusCode: 400, statusMessage: 'A prompt is required' })
@@ -213,7 +214,13 @@ export default defineEventHandler(async (event) => {
const cfg = engine === 'krea'
? clampImageCfg(fields.cfg, IMAGE_V2_KREA_CFG)
: turbo ? IMAGE_V2_TURBO_CFG : clampImageCfg(fields.cfg, IMAGE_V2_CFG_DEFAULT)
const megapixels = mode === 'generate' ? 0 : clampImageScaleMegapixels(fields.megapixels ?? fields.scaleMegapixels, 1)
const megapixels = mode === 'generate'
? 0
: fields.scaleToTotalPixels == null
? clampImageScaleMegapixels(fields.megapixels ?? fields.scaleMegapixels, 1)
: parseBool(fields.scaleToTotalPixels)
? clampImageScaleMegapixels(fields.megapixels ?? fields.scaleMegapixels, 1)
: 0
const seed = fields.seed && String(fields.seed) !== 'random'
? Number(fields.seed)
: Math.floor(Math.random() * 2_147_483_647)
@@ -324,9 +331,10 @@ export default defineEventHandler(async (event) => {
queueAutoRun: false,
negative: String(fields.negative || '').trim(),
passes: extraPasses,
passMode,
referenceStillId: savedRef?.id,
referenceStillFilename: savedRef?.filename,
scaleToTotalPixels: mode !== 'generate',
scaleToTotalPixels: mode !== 'generate' && megapixels > 0,
scaleMegapixels: megapixels,
imagePipeline: 'v2',
v2Mode,