Add an xAIGen SNOFS switch for Flux and Krea.

It is the same LoRA card as the rest of the chain; the toggle only adds or removes that card so it no longer comes back after you turn it off.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-08-30 13:27:30 -05:00
co-authored by Cursor
parent 9f82377b52
commit bfe94e4bb1
5 changed files with 88 additions and 18 deletions
+66 -7
View File
@@ -757,6 +757,23 @@
</div>
<p class="mt-2 text-xs text-zinc-500">Flux is Klein 9B. Krea is Krea 2 Turbo. Video stays MiniMax or LTX.</p>
<div class="max-w-md">
<label
v-if="v2ConceptLoraEnabled && imageV2ConceptFile"
class="mb-2 flex cursor-pointer items-center gap-2 text-xs text-zinc-400"
>
<span class="relative inline-flex h-5 w-9 shrink-0 items-center">
<input
type="checkbox"
class="peer sr-only"
role="switch"
:checked="imageV2SnofsOn"
@change="onImageV2SnofsToggle"
>
<span class="absolute inset-0 rounded-full bg-zinc-700 transition peer-checked:bg-amber-400" />
<span class="absolute left-0.5 top-0.5 h-4 w-4 rounded-full bg-white transition peer-checked:translate-x-4" />
</span>
SNOFS
</label>
<LoraSelect
v-model="imageV2LoraStack"
:names="imageV2LoraOptions"
@@ -2008,15 +2025,19 @@ import {
IMAGE_V2_REFINE_FACE_PROMPT,
IMAGE_V2_REFINE_HAND,
IMAGE_V2_REFINE_HEAVY,
IMAGE_V2_SNOFS_CLIP,
IMAGE_V2_SNOFS_LORA,
IMAGE_V2_SNOFS_MODEL,
IMAGE_V2_STEPS_DEFAULT,
clampImageV2Denoise,
clampImageV2Size,
defaultImageV2LoraStack,
imageV2ConceptLoraName,
isImageV2ConceptLora,
isImageV2ConsistencyLora,
mergeLegacyImageV2Loras,
parseImageV2Engine,
removeImageV2ConceptLora,
upsertImageV2Lora,
type ImageV2Engine,
type ImageV2GenerateAspect,
@@ -2365,8 +2386,18 @@ function imageV2EngineLoras(stack?: unknown) {
)
}
function imageV2ConceptName() {
return imageV2ConceptLoraName(
v2Engine.value,
String(runtimeConfig.public.kreaConceptLora || IMAGE_V2_KREA_CONCEPT_LORA || '').trim()
)
}
function seedImageV2LorasIfEmpty() {
if (imageV2LoraStack.value.length) return
try {
if (localStorage.getItem(imageV2LoraStoreKey(v2Engine.value)) != null) return
} catch { /* ignore */ }
imageV2LoraStack.value = defaultImageV2LoraStack({
engine: v2Engine.value,
mode: v2Mode.value,
@@ -2439,9 +2470,14 @@ const videoLoraOptions = computed(() => mergeLoraOptions(videoLoras.value, [
...extensionQueue.value.flatMap(item => (item.loraStack || []).map(entry => entry.name))
]))
const imageLoraOptions = computed(() => mergeLoraOptions(imageLoras.value, imageLoraStack.value.map(item => item.name)))
const imageV2ConceptFile = computed(() => imageV2ConceptName())
const imageV2SnofsOn = computed(() => imageV2LoraStack.value.some(item => isImageV2ConceptLora(item.name, v2Engine.value)))
const imageV2LoraOptions = computed(() => mergeLoraOptions(
filterLorasForImageEngine(imageLoras.value, v2Engine.value),
filterLorasForImageEngine(imageV2LoraStack.value.map(item => item.name), v2Engine.value)
filterLorasForImageEngine([
...imageV2LoraStack.value.map(item => item.name),
...(v2ConceptLoraEnabled.value && imageV2ConceptFile.value ? [imageV2ConceptFile.value] : [])
], v2Engine.value)
))
const identityRefs = ref<(File | null)[]>([null, null, null, null])
const identityPreviews = ref(['', '', '', ''])
@@ -3578,14 +3614,37 @@ function persistImageV2LoraStack(stack: LoraStackItem[], engine: ImageV2Engine)
} catch { /* ignore */ }
}
function readStoredLoraStackIfPresent(key: string) {
try {
if (localStorage.getItem(key) == null) return null
return readLoraStack(localStorage.getItem(key) || '')
} catch {
return null
}
}
function readImageV2LoraStack(engine: ImageV2Engine) {
const keyed = readStoredLoraStack(imageV2LoraStoreKey(engine))
if (keyed.length) return filterLoraStackForImageEngine(keyed, engine)
const legacy = readStoredLoraStack('aigen-image-v2-lora')
if (!legacy.length) return [] as LoraStackItem[]
const keyed = readStoredLoraStackIfPresent(imageV2LoraStoreKey(engine))
if (keyed) return filterLoraStackForImageEngine(keyed, engine)
const legacy = readStoredLoraStackIfPresent('aigen-image-v2-lora')
if (!legacy) return [] as LoraStackItem[]
return filterLoraStackForImageEngine(legacy, engine)
}
function setImageV2Snofs(on: boolean) {
const name = imageV2ConceptName()
if (!name) return
if (on) {
imageV2LoraStack.value = upsertImageV2Lora(imageV2LoraStack.value, name, IMAGE_V2_SNOFS_MODEL, IMAGE_V2_SNOFS_CLIP)
return
}
imageV2LoraStack.value = removeImageV2ConceptLora(imageV2LoraStack.value, v2Engine.value)
}
function onImageV2SnofsToggle(event: Event) {
setImageV2Snofs((event.target as HTMLInputElement).checked)
}
function mergeLoraOptions(listed: string[], extra: string[]) {
const names = [...listed]
for (const name of extra) {
@@ -5724,7 +5783,7 @@ function applyRefinePreset(kind: 'face' | 'hand' | 'heavy') {
if (kind === 'face') {
v2Denoise.value = IMAGE_V2_REFINE_FACE.strength
if (v2Engine.value === 'flux') {
if (v2ConceptLoraEnabled.value) {
if (v2ConceptLoraEnabled.value && imageV2SnofsOn.value) {
imageV2LoraStack.value = upsertImageV2Lora(imageV2LoraStack.value, IMAGE_V2_SNOFS_LORA, IMAGE_V2_REFINE_FACE.snofsModel, IMAGE_V2_REFINE_FACE.snofsClip)
}
imageV2LoraStack.value = upsertImageV2Lora(imageV2LoraStack.value, IMAGE_V2_CONSISTENCY_LORA, IMAGE_V2_REFINE_FACE.consistencyModel, IMAGE_V2_REFINE_FACE.consistencyClip)
@@ -5735,7 +5794,7 @@ function applyRefinePreset(kind: 'face' | 'hand' | 'heavy') {
if (kind === 'hand') {
v2Denoise.value = IMAGE_V2_REFINE_HAND.strength
if (v2Engine.value === 'flux') {
if (v2ConceptLoraEnabled.value) {
if (v2ConceptLoraEnabled.value && imageV2SnofsOn.value) {
imageV2LoraStack.value = upsertImageV2Lora(imageV2LoraStack.value, IMAGE_V2_SNOFS_LORA, IMAGE_V2_REFINE_HAND.snofsModel, IMAGE_V2_REFINE_HAND.snofsClip)
}
imageV2LoraStack.value = upsertImageV2Lora(imageV2LoraStack.value, IMAGE_V2_CONSISTENCY_LORA, IMAGE_V2_REFINE_HAND.consistencyModel, IMAGE_V2_REFINE_HAND.consistencyClip)