Add Image Iterate: nested multi-prompt still families.

Iterate runs a series of prompts as generate (no still), edit (still A), or compose (A+B), saves each output under one library collection like video extensions, and defaults to iterating from the original still.
This commit is contained in:
2026-09-04 22:46:19 -05:00
parent 279ac72049
commit 2435167a41
6 changed files with 261 additions and 130 deletions
+25 -10
View File
@@ -6,13 +6,26 @@ import {
type LoraStackItem
} from '~/utils/loras'
export const IMAGE_V2_MODES = ['edit', 'compose', 'refine', 'generate'] as const
export const IMAGE_V2_MODES = ['edit', 'compose', 'refine', 'generate', 'iterate'] as const
export const IMAGE_V2_TASKS = ['scene', 'identity', 'outfit', 'face_lock', 'refine', 't2i'] as const
export const IMAGE_V2_ENGINES = ['flux', 'krea'] as const
export type ImageV2Mode = (typeof IMAGE_V2_MODES)[number]
export type ImageV2Task = (typeof IMAGE_V2_TASKS)[number]
export type ImageV2Engine = (typeof IMAGE_V2_ENGINES)[number]
export type ImageV2GraphMode = Exclude<ImageV2Mode, 'iterate'>
/** Iterate picks a real graph from the attached stills: none → generate, A → edit, A+B → compose. */
export function iterateGraphMode(opts: { hasImage?: boolean; hasReference?: boolean }): Exclude<ImageV2GraphMode, 'refine'> {
if (!opts.hasImage) return 'generate'
if (opts.hasReference) return 'compose'
return 'edit'
}
export function imageV2GraphMode(mode: ImageV2Mode, opts: { hasImage?: boolean; hasReference?: boolean } = {}): ImageV2GraphMode {
if (mode === 'iterate') return iterateGraphMode(opts)
return mode
}
export const IMAGE_V2_STEPS_DEFAULT = 24
export const IMAGE_V2_CFG_DEFAULT = 4
@@ -97,16 +110,17 @@ export function parseImageV2Engine(raw: unknown, fallback: ImageV2Engine = 'flux
return IMAGE_V2_ENGINES.includes(value as ImageV2Engine) ? value as ImageV2Engine : fallback
}
export function imageV2WorkflowFile(mode: ImageV2Mode, engine: ImageV2Engine = 'flux') {
export function imageV2WorkflowFile(mode: ImageV2Mode, engine: ImageV2Engine = 'flux', opts: { hasImage?: boolean; hasReference?: boolean } = {}) {
const graph = imageV2GraphMode(mode, opts)
if (engine === 'krea') {
if (mode === 'generate') return 'krea_v2_generate.json'
if (mode === 'refine') return 'krea_v2_refine.json'
if (mode === 'compose') return 'krea_v2_compose.json'
if (graph === 'generate') return 'krea_v2_generate.json'
if (graph === 'refine') return 'krea_v2_refine.json'
if (graph === 'compose') return 'krea_v2_compose.json'
return 'krea_v2_edit.json'
}
if (mode === 'generate') return 'klein_v2_generate.json'
if (mode === 'refine') return 'klein_v2_refine.json'
if (mode === 'compose') return 'klein_v2_compose.json'
if (graph === 'generate') return 'klein_v2_generate.json'
if (graph === 'refine') return 'klein_v2_refine.json'
if (graph === 'compose') return 'klein_v2_compose.json'
return 'klein_v2_edit.json'
}
@@ -150,9 +164,10 @@ export function clampImageV2Size(width: unknown, height: unknown, aspect?: unkno
}
}
export function composeImageV2Prompt(mode: ImageV2Mode, task: ImageV2Task, prompt: string) {
export function composeImageV2Prompt(mode: ImageV2Mode, task: ImageV2Task, prompt: string, opts: { hasImage?: boolean; hasReference?: boolean } = {}) {
const body = String(prompt || '').trim()
if (mode === 'generate' || mode === 'refine' || mode !== 'compose' || task === 'scene' || task === 't2i') return body
const graph = imageV2GraphMode(mode, opts)
if (graph === 'generate' || graph === 'refine' || graph !== 'compose' || task === 'scene' || task === 't2i') return body
const header = IMAGE_V2_ROLE_HEADERS[task as Exclude<ImageV2Task, 'scene' | 'refine' | 't2i'>]
return header ? `${header}\n\n${body}` : body
}