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:
+25
-10
@@ -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
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ function stillDisplayLoraStack(settings: StillGenerationSettings) {
|
||||
export function normalizeStillSettings(raw: unknown): StillGenerationSettings | undefined {
|
||||
if (!raw || typeof raw !== 'object') return undefined
|
||||
const value = raw as Record<string, unknown>
|
||||
const mode = value.mode === 'compose' || value.mode === 'refine' || value.mode === 'generate' || value.mode === 'edit'
|
||||
const mode = value.mode === 'compose' || value.mode === 'refine' || value.mode === 'generate' || value.mode === 'edit' || value.mode === 'iterate'
|
||||
? value.mode
|
||||
: undefined
|
||||
const engine = value.engine === 'krea' || value.engine === 'flux' ? value.engine : undefined
|
||||
@@ -155,11 +155,13 @@ export function stillModeLabel(still: { filename?: string; settings?: StillGener
|
||||
if (settings?.kind === 'imagev2' || settings?.mode) {
|
||||
const mode = settings.mode === 'generate'
|
||||
? 'Generate'
|
||||
: settings.mode === 'compose'
|
||||
? 'Compose'
|
||||
: settings.mode === 'refine'
|
||||
? 'Refine'
|
||||
: 'Edit'
|
||||
: settings.mode === 'iterate'
|
||||
? 'Iterate'
|
||||
: settings.mode === 'compose'
|
||||
? 'Compose'
|
||||
: settings.mode === 'refine'
|
||||
? 'Refine'
|
||||
: 'Edit'
|
||||
const task = settings.mode === 'compose' ? composeTaskLabel(settings.task) : ''
|
||||
const engine = settings.engine === 'krea' ? 'Krea' : settings.engine === 'flux' ? 'Flux' : ''
|
||||
return [engine ? `Image v2 · ${mode} · ${engine}` : `Image v2 · ${mode}`, task].filter(Boolean).join(' · ')
|
||||
|
||||
Reference in New Issue
Block a user