Move Concept and Consistency into the Image v2 LoRA chain.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-08-29 19:45:34 -05:00
co-authored by Cursor
parent e296bb5b96
commit 253e47ed81
3 changed files with 191 additions and 119 deletions
+93
View File
@@ -1,3 +1,11 @@
import {
filterLoraStackForImageEngine,
loraIdentityKey,
makeLoraStackItem,
normalizeLoraStack,
type LoraStackItem
} from '~/utils/loras'
export const IMAGE_V2_MODES = ['edit', 'compose', 'refine', 'generate'] 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
@@ -166,3 +174,88 @@ export type ImageV2PresetSettings = {
width?: number
height?: number
}
export function isImageV2ConceptLora(name: string) {
const id = loraIdentityKey(name)
return id === loraIdentityKey(IMAGE_V2_SNOFS_LORA) || /klein_snofs/i.test(id)
}
export function isImageV2ConsistencyLora(name: string) {
const id = loraIdentityKey(name)
return id === loraIdentityKey(IMAGE_V2_CONSISTENCY_LORA) || /flux2-klein-9b-consistency/i.test(id)
}
export function upsertImageV2Lora(stack: unknown, name: string, model: number, clip: number): LoraStackItem[] {
const item = makeLoraStackItem(name, model, clip)
const current = normalizeLoraStack(stack)
if (!item) return current
const key = loraIdentityKey(name)
const index = current.findIndex(entry => loraIdentityKey(entry.name) === key)
if (index >= 0) {
const next = [...current]
next[index] = { ...next[index], strengthModel: item.strengthModel, strengthClip: item.strengthClip }
return next
}
if (isImageV2ConsistencyLora(name)) {
const conceptAt = current.findIndex(entry => isImageV2ConceptLora(entry.name))
if (conceptAt >= 0) return [...current.slice(0, conceptAt + 1), item, ...current.slice(conceptAt + 1)]
}
return [item, ...current]
}
export function defaultImageV2LoraStack(opts: {
engine: ImageV2Engine
mode: ImageV2Mode
xaigen: boolean
kreaConcept?: string
}): LoraStackItem[] {
if (opts.engine === 'krea') {
const name = String(opts.kreaConcept || '').trim()
if (!opts.xaigen || !name) return []
const item = makeLoraStackItem(name, IMAGE_V2_SNOFS_MODEL, IMAGE_V2_SNOFS_CLIP)
return item ? [item] : []
}
const out: LoraStackItem[] = []
if (opts.xaigen) {
const concept = makeLoraStackItem(IMAGE_V2_SNOFS_LORA, IMAGE_V2_SNOFS_MODEL, IMAGE_V2_SNOFS_CLIP)
if (concept) out.push(concept)
}
if (opts.mode !== 'generate') {
const consistency = makeLoraStackItem(IMAGE_V2_CONSISTENCY_LORA, IMAGE_V2_CONSISTENCY_MODEL, IMAGE_V2_CONSISTENCY_CLIP)
if (consistency) out.push(consistency)
}
return out
}
export function mergeLegacyImageV2Loras(stack: unknown, opts: {
engine: ImageV2Engine
mode: ImageV2Mode
xaigen: boolean
snofsModel?: number
snofsClip?: number
consistencyModel?: number
consistencyClip?: number
kreaConcept?: string
}): LoraStackItem[] {
let items = filterLoraStackForImageEngine(stack, opts.engine)
if (opts.engine === 'krea') {
const name = String(opts.kreaConcept || '').trim()
const model = Number(opts.snofsModel) || 0
const clip = Number(opts.snofsClip) || 0
if (name && (model > 0 || clip > 0) && !items.some(item => loraIdentityKey(item.name) === loraIdentityKey(name))) {
return upsertImageV2Lora(items, name, model, clip)
}
return items
}
const conceptModel = opts.xaigen ? Number(opts.snofsModel) || 0 : 0
const conceptClip = opts.xaigen ? Number(opts.snofsClip) || 0 : 0
if ((conceptModel > 0 || conceptClip > 0) && !items.some(item => isImageV2ConceptLora(item.name))) {
items = upsertImageV2Lora(items, IMAGE_V2_SNOFS_LORA, conceptModel, conceptClip)
}
const consistencyModel = opts.mode === 'generate' ? 0 : Number(opts.consistencyModel) || 0
const consistencyClip = opts.mode === 'generate' ? 0 : Number(opts.consistencyClip) || 0
if ((consistencyModel > 0 || consistencyClip > 0) && !items.some(item => isImageV2ConsistencyLora(item.name))) {
items = upsertImageV2Lora(items, IMAGE_V2_CONSISTENCY_LORA, consistencyModel, consistencyClip)
}
return items
}