diff --git a/pages/index.vue b/pages/index.vue
index f9b29f2..0aeeb0b 100644
--- a/pages/index.vue
+++ b/pages/index.vue
@@ -757,6 +757,23 @@
Flux is Klein 9B. Krea is Krea 2 Turbo. Video stays MiniMax or LTX.
+
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)
diff --git a/server/api/v2/generate.post.ts b/server/api/v2/generate.post.ts
index 19be2c9..6268c08 100644
--- a/server/api/v2/generate.post.ts
+++ b/server/api/v2/generate.post.ts
@@ -6,8 +6,6 @@ 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,
IMAGE_V2_TURBO_CFG,
IMAGE_V2_TURBO_STEPS,
@@ -221,8 +219,8 @@ export default defineEventHandler(async (event) => {
mode === 'generate' || engine === 'krea' ? 0 : IMAGE_V2_CONSISTENCY_CLIP
)
const conceptAllowed = isXaigenStudio()
- const generateSnofsFallback = !conceptAllowed || engine === 'krea' ? 0 : IMAGE_V2_SNOFS_MODEL
- const generateSnofsClipFallback = !conceptAllowed || engine === 'krea' ? 0 : IMAGE_V2_SNOFS_CLIP
+ const generateSnofsFallback = 0
+ const generateSnofsClipFallback = 0
await listStudioLoras()
const loraStack = assertImageV2LoraStack(parsePostedLoraStack(fields.loraStack || fields.lora, 'image'), engine)
diff --git a/server/utils/studioQueue.ts b/server/utils/studioQueue.ts
index 3ea18ae..6b4799c 100644
--- a/server/utils/studioQueue.ts
+++ b/server/utils/studioQueue.ts
@@ -797,8 +797,8 @@ async function startStudioEditJob(item: StudioJob) {
steps: payload.steps,
seed: live.library?.seed || payload.seed,
cfg: payload.cfg,
- snofsModel: payload.snofsModel ?? (isXaigenStudio() ? 0.65 : 0),
- snofsClip: payload.snofsClip ?? (isXaigenStudio() ? 0.35 : 0),
+ snofsModel: payload.snofsModel ?? 0,
+ snofsClip: payload.snofsClip ?? 0,
consistencyModel: payload.consistencyModel ?? (mode === 'generate' || payload.engine === 'krea' ? 0 : 0.7),
consistencyClip: payload.consistencyClip ?? (mode === 'generate' || payload.engine === 'krea' ? 0 : 0.7),
megapixels: payload.scaleMegapixels ?? 1,
diff --git a/utils/imageV2.ts b/utils/imageV2.ts
index 7cd28e8..3c7e3d5 100644
--- a/utils/imageV2.ts
+++ b/utils/imageV2.ts
@@ -74,7 +74,7 @@ export const IMAGE_V2_KREA_UNET_CANDIDATES = [
'krea2_turbo_nvfp4.safetensors',
'krea2_turbo_fp8_scaled.safetensors'
] as const
-export const IMAGE_V2_KREA_CONCEPT_LORA = ''
+export const IMAGE_V2_KREA_CONCEPT_LORA = 'xaigen-snofs_krea_v1_3D.safetensors'
export const IMAGE_V2_ROLE_HEADERS: Record, 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.',
@@ -175,9 +175,18 @@ export type ImageV2PresetSettings = {
height?: number
}
-export function isImageV2ConceptLora(name: string) {
+export function imageV2ConceptLoraName(engine: ImageV2Engine, kreaConcept?: string) {
+ if (engine === 'krea') return String(kreaConcept || IMAGE_V2_KREA_CONCEPT_LORA || '').trim()
+ return IMAGE_V2_SNOFS_LORA
+}
+
+export function isImageV2ConceptLora(name: string, engine?: ImageV2Engine) {
const id = loraIdentityKey(name)
- return id === loraIdentityKey(IMAGE_V2_SNOFS_LORA) || /klein_snofs/i.test(id)
+ const flux = id === loraIdentityKey(IMAGE_V2_SNOFS_LORA) || /klein_snofs/i.test(id)
+ const krea = id === loraIdentityKey(IMAGE_V2_KREA_CONCEPT_LORA) || /snofs_krea/i.test(id)
+ if (engine === 'krea') return krea
+ if (engine === 'flux') return flux
+ return flux || krea
}
export function isImageV2ConsistencyLora(name: string) {
@@ -203,6 +212,10 @@ export function upsertImageV2Lora(stack: unknown, name: string, model: number, c
return [item, ...current]
}
+export function removeImageV2ConceptLora(stack: unknown, engine?: ImageV2Engine) {
+ return normalizeLoraStack(stack).filter(item => !isImageV2ConceptLora(item.name, engine))
+}
+
export function defaultImageV2LoraStack(opts: {
engine: ImageV2Engine
mode: ImageV2Mode
@@ -242,7 +255,7 @@ export function mergeLegacyImageV2Loras(stack: unknown, opts: {
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))) {
+ if (name && (model > 0 || clip > 0) && !items.some(item => isImageV2ConceptLora(item.name, 'krea'))) {
return upsertImageV2Lora(items, name, model, clip)
}
return items
diff --git a/utils/loras.ts b/utils/loras.ts
index c33126b..2116ab8 100644
--- a/utils/loras.ts
+++ b/utils/loras.ts
@@ -31,7 +31,7 @@ export function loraIdentityKey(name: string) {
export function isXaigenOnlyLora(name: string) {
const base = loraBasename(name)
- return /^xaigen/i.test(base) || /klein_snofs/i.test(base)
+ return /^xaigen/i.test(base) || /klein_snofs|snofs_krea/i.test(base)
}
/** Pick the real Comfy filename: exact path, then same basename ignoring `xaigen-`, then suffix/contains. */