Files
aigen/utils/imageV2.ts
T
TowstyandCursor 21fe2412f9 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>
2026-08-28 21:22:37 -05:00

65 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export const IMAGE_V2_MODES = ['edit', 'compose'] as const
export const IMAGE_V2_TASKS = ['scene', 'identity', 'outfit', 'face_lock'] as const
export type ImageV2Mode = (typeof IMAGE_V2_MODES)[number]
export type ImageV2Task = (typeof IMAGE_V2_TASKS)[number]
export const IMAGE_V2_STEPS_DEFAULT = 24
export const IMAGE_V2_CFG_DEFAULT = 4
export const IMAGE_V2_TURBO_STEPS = 8
export const IMAGE_V2_TURBO_CFG = 1
export const IMAGE_V2_SNOFS_MODEL = 0.65
export const IMAGE_V2_SNOFS_CLIP = 0.35
export const IMAGE_V2_CONSISTENCY_MODEL = 0.7
export const IMAGE_V2_CONSISTENCY_CLIP = 0.7
export const IMAGE_V2_STRENGTH_MIN = 0
export const IMAGE_V2_STRENGTH_MAX = 2
export const IMAGE_V2_STRENGTH_STEP = 0.05
export const IMAGE_V2_SNOFS_LORA = 'klein_snofs_v1_4.safetensors'
export const IMAGE_V2_CONSISTENCY_LORA = 'Flux2-Klein-9B-consistency-V2.safetensors'
export const IMAGE_V2_ROLE_HEADERS: Record<Exclude<ImageV2Task, 'scene'>, string> = {
outfit: 'Person, face, body, pose, and background from image 1. Clothing only from image 2. Fit the outfit from image 2 to the body in image 1. Do not copy image 2’s face, body shape, or pose.',
face_lock: 'Body, pose, and scene from image 1. Exact face from image 2.',
identity: 'Same person as image 1. Use image 2 only to reinforce the face. Follow the user’s pose/scene prompt.'
}
export function parseImageV2Mode(raw: unknown): ImageV2Mode | null {
const value = String(raw || '').trim().toLowerCase()
return IMAGE_V2_MODES.includes(value as ImageV2Mode) ? value as ImageV2Mode : null
}
export function parseImageV2Task(raw: unknown, fallback: ImageV2Task = 'scene'): ImageV2Task {
const value = String(raw || '').trim().toLowerCase()
return IMAGE_V2_TASKS.includes(value as ImageV2Task) ? value as ImageV2Task : fallback
}
export function clampImageV2Strength(raw: unknown, fallback: number) {
const value = Number(raw)
if (!Number.isFinite(value)) return fallback
const snapped = Math.round(value / IMAGE_V2_STRENGTH_STEP) * IMAGE_V2_STRENGTH_STEP
return Math.min(IMAGE_V2_STRENGTH_MAX, Math.max(IMAGE_V2_STRENGTH_MIN, Math.round(snapped * 100) / 100))
}
export function composeImageV2Prompt(mode: ImageV2Mode, task: ImageV2Task, prompt: string) {
const body = String(prompt || '').trim()
if (mode !== 'compose' || task === 'scene') return body
const header = IMAGE_V2_ROLE_HEADERS[task]
return header ? `${header}\n\n${body}` : body
}
export type ImageV2PresetSettings = {
mode?: ImageV2Mode
task?: ImageV2Task
negative?: string
snofsModel?: number
snofsClip?: number
consistencyModel?: number
consistencyClip?: number
steps?: number
cfg?: number
megapixels?: number
turbo?: boolean
}