Add Image v2 as a sibling of Image edit, with separate Klein graphs.

Keep the v1 Image tab on its dual-branch template. The host agent follows Desktop's live port so Coolify stays on :8198.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-08-28 21:22:37 -05:00
co-authored by Cursor
parent 55f8cb1def
commit 21fe2412f9
18 changed files with 1678 additions and 67 deletions
+39 -2
View File
@@ -14,6 +14,18 @@ import {
type ImagePresetSettings,
type VideoPresetSettings
} from '~/utils/generationPresets'
import {
IMAGE_V2_CFG_DEFAULT,
IMAGE_V2_CONSISTENCY_CLIP,
IMAGE_V2_CONSISTENCY_MODEL,
IMAGE_V2_SNOFS_CLIP,
IMAGE_V2_SNOFS_MODEL,
IMAGE_V2_STEPS_DEFAULT,
clampImageV2Strength,
parseImageV2Mode,
parseImageV2Task,
type ImageV2PresetSettings
} from '~/utils/imageV2'
import { coerceVideoWorkflow, isXaigenStudio, parseVideoWorkflow } from '~/utils/videoModels'
const writeChains = new Map<string, Promise<unknown>>()
@@ -96,6 +108,31 @@ function sanitizeImageSettings(raw: unknown): ImagePresetSettings {
}
}
function sanitizeImageV2Settings(raw: unknown): ImageV2PresetSettings {
const rec = raw && typeof raw === 'object' ? raw as Record<string, unknown> : {}
const mode = parseImageV2Mode(rec.mode) || 'edit'
const turbo = rec.turbo === true
return {
mode,
task: mode === 'compose' ? parseImageV2Task(rec.task, 'scene') : 'scene',
negative: String(rec.negative || '').slice(0, 2000),
snofsModel: clampImageV2Strength(rec.snofsModel ?? rec.snofs_model, IMAGE_V2_SNOFS_MODEL),
snofsClip: clampImageV2Strength(rec.snofsClip ?? rec.snofs_clip, IMAGE_V2_SNOFS_CLIP),
consistencyModel: clampImageV2Strength(rec.consistencyModel ?? rec.consistency_model, IMAGE_V2_CONSISTENCY_MODEL),
consistencyClip: clampImageV2Strength(rec.consistencyClip ?? rec.consistency_clip, IMAGE_V2_CONSISTENCY_CLIP),
steps: turbo ? 8 : clampImageSteps(rec.steps, IMAGE_V2_STEPS_DEFAULT),
cfg: turbo ? 1 : clampImageCfg(rec.cfg, IMAGE_V2_CFG_DEFAULT),
megapixels: clampImageScaleMegapixels(rec.megapixels ?? rec.scaleMegapixels, 1),
turbo
}
}
function sanitizeSettings(kind: GenerationPresetKind, raw: unknown) {
if (kind === 'image') return sanitizeImageSettings(raw)
if (kind === 'imagev2') return sanitizeImageV2Settings(raw)
return sanitizeVideoSettings(raw)
}
function normalizeStoredPreset(raw: unknown): GenerationPreset | null {
if (!raw || typeof raw !== 'object') return null
const rec = raw as Record<string, unknown>
@@ -111,7 +148,7 @@ function normalizeStoredPreset(raw: unknown): GenerationPreset | null {
createdAt,
updatedAt: Number(rec.updatedAt) || createdAt,
loraStack: normalizeLoraStack(rec.loraStack),
settings: kind === 'image' ? sanitizeImageSettings(rec.settings) : sanitizeVideoSettings(rec.settings)
settings: sanitizeSettings(kind, rec.settings)
}
}
@@ -147,7 +184,7 @@ export async function saveGenerationPreset(owner: string, input: {
const kind = parseGenerationPresetKind(input.kind)
const xaigen = isXaigenStudio()
const loraStack = filterLoraStackForStudio(input.loraStack, xaigen)
const settings = kind === 'image' ? sanitizeImageSettings(input.settings) : sanitizeVideoSettings(input.settings)
const settings = sanitizeSettings(kind, input.settings)
const now = Date.now()
return mutate(owner, (presets) => {
const existing = presets.find(item => item.kind === kind && item.name.toLowerCase() === name.toLowerCase())