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:
+66
-7
@@ -757,6 +757,23 @@
|
|||||||
</div>
|
</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>
|
<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">
|
<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
|
<LoraSelect
|
||||||
v-model="imageV2LoraStack"
|
v-model="imageV2LoraStack"
|
||||||
:names="imageV2LoraOptions"
|
:names="imageV2LoraOptions"
|
||||||
@@ -2008,15 +2025,19 @@ import {
|
|||||||
IMAGE_V2_REFINE_FACE_PROMPT,
|
IMAGE_V2_REFINE_FACE_PROMPT,
|
||||||
IMAGE_V2_REFINE_HAND,
|
IMAGE_V2_REFINE_HAND,
|
||||||
IMAGE_V2_REFINE_HEAVY,
|
IMAGE_V2_REFINE_HEAVY,
|
||||||
|
IMAGE_V2_SNOFS_CLIP,
|
||||||
IMAGE_V2_SNOFS_LORA,
|
IMAGE_V2_SNOFS_LORA,
|
||||||
|
IMAGE_V2_SNOFS_MODEL,
|
||||||
IMAGE_V2_STEPS_DEFAULT,
|
IMAGE_V2_STEPS_DEFAULT,
|
||||||
clampImageV2Denoise,
|
clampImageV2Denoise,
|
||||||
clampImageV2Size,
|
clampImageV2Size,
|
||||||
defaultImageV2LoraStack,
|
defaultImageV2LoraStack,
|
||||||
|
imageV2ConceptLoraName,
|
||||||
isImageV2ConceptLora,
|
isImageV2ConceptLora,
|
||||||
isImageV2ConsistencyLora,
|
isImageV2ConsistencyLora,
|
||||||
mergeLegacyImageV2Loras,
|
mergeLegacyImageV2Loras,
|
||||||
parseImageV2Engine,
|
parseImageV2Engine,
|
||||||
|
removeImageV2ConceptLora,
|
||||||
upsertImageV2Lora,
|
upsertImageV2Lora,
|
||||||
type ImageV2Engine,
|
type ImageV2Engine,
|
||||||
type ImageV2GenerateAspect,
|
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() {
|
function seedImageV2LorasIfEmpty() {
|
||||||
if (imageV2LoraStack.value.length) return
|
if (imageV2LoraStack.value.length) return
|
||||||
|
try {
|
||||||
|
if (localStorage.getItem(imageV2LoraStoreKey(v2Engine.value)) != null) return
|
||||||
|
} catch { /* ignore */ }
|
||||||
imageV2LoraStack.value = defaultImageV2LoraStack({
|
imageV2LoraStack.value = defaultImageV2LoraStack({
|
||||||
engine: v2Engine.value,
|
engine: v2Engine.value,
|
||||||
mode: v2Mode.value,
|
mode: v2Mode.value,
|
||||||
@@ -2439,9 +2470,14 @@ const videoLoraOptions = computed(() => mergeLoraOptions(videoLoras.value, [
|
|||||||
...extensionQueue.value.flatMap(item => (item.loraStack || []).map(entry => entry.name))
|
...extensionQueue.value.flatMap(item => (item.loraStack || []).map(entry => entry.name))
|
||||||
]))
|
]))
|
||||||
const imageLoraOptions = computed(() => mergeLoraOptions(imageLoras.value, imageLoraStack.value.map(item => item.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(
|
const imageV2LoraOptions = computed(() => mergeLoraOptions(
|
||||||
filterLorasForImageEngine(imageLoras.value, v2Engine.value),
|
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 identityRefs = ref<(File | null)[]>([null, null, null, null])
|
||||||
const identityPreviews = ref(['', '', '', ''])
|
const identityPreviews = ref(['', '', '', ''])
|
||||||
@@ -3578,14 +3614,37 @@ function persistImageV2LoraStack(stack: LoraStackItem[], engine: ImageV2Engine)
|
|||||||
} catch { /* ignore */ }
|
} 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) {
|
function readImageV2LoraStack(engine: ImageV2Engine) {
|
||||||
const keyed = readStoredLoraStack(imageV2LoraStoreKey(engine))
|
const keyed = readStoredLoraStackIfPresent(imageV2LoraStoreKey(engine))
|
||||||
if (keyed.length) return filterLoraStackForImageEngine(keyed, engine)
|
if (keyed) return filterLoraStackForImageEngine(keyed, engine)
|
||||||
const legacy = readStoredLoraStack('aigen-image-v2-lora')
|
const legacy = readStoredLoraStackIfPresent('aigen-image-v2-lora')
|
||||||
if (!legacy.length) return [] as LoraStackItem[]
|
if (!legacy) return [] as LoraStackItem[]
|
||||||
return filterLoraStackForImageEngine(legacy, engine)
|
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[]) {
|
function mergeLoraOptions(listed: string[], extra: string[]) {
|
||||||
const names = [...listed]
|
const names = [...listed]
|
||||||
for (const name of extra) {
|
for (const name of extra) {
|
||||||
@@ -5724,7 +5783,7 @@ function applyRefinePreset(kind: 'face' | 'hand' | 'heavy') {
|
|||||||
if (kind === 'face') {
|
if (kind === 'face') {
|
||||||
v2Denoise.value = IMAGE_V2_REFINE_FACE.strength
|
v2Denoise.value = IMAGE_V2_REFINE_FACE.strength
|
||||||
if (v2Engine.value === 'flux') {
|
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_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)
|
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') {
|
if (kind === 'hand') {
|
||||||
v2Denoise.value = IMAGE_V2_REFINE_HAND.strength
|
v2Denoise.value = IMAGE_V2_REFINE_HAND.strength
|
||||||
if (v2Engine.value === 'flux') {
|
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_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)
|
imageV2LoraStack.value = upsertImageV2Lora(imageV2LoraStack.value, IMAGE_V2_CONSISTENCY_LORA, IMAGE_V2_REFINE_HAND.consistencyModel, IMAGE_V2_REFINE_HAND.consistencyClip)
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import {
|
|||||||
IMAGE_V2_CFG_DEFAULT,
|
IMAGE_V2_CFG_DEFAULT,
|
||||||
IMAGE_V2_CONSISTENCY_CLIP,
|
IMAGE_V2_CONSISTENCY_CLIP,
|
||||||
IMAGE_V2_CONSISTENCY_MODEL,
|
IMAGE_V2_CONSISTENCY_MODEL,
|
||||||
IMAGE_V2_SNOFS_CLIP,
|
|
||||||
IMAGE_V2_SNOFS_MODEL,
|
|
||||||
IMAGE_V2_STEPS_DEFAULT,
|
IMAGE_V2_STEPS_DEFAULT,
|
||||||
IMAGE_V2_TURBO_CFG,
|
IMAGE_V2_TURBO_CFG,
|
||||||
IMAGE_V2_TURBO_STEPS,
|
IMAGE_V2_TURBO_STEPS,
|
||||||
@@ -221,8 +219,8 @@ export default defineEventHandler(async (event) => {
|
|||||||
mode === 'generate' || engine === 'krea' ? 0 : IMAGE_V2_CONSISTENCY_CLIP
|
mode === 'generate' || engine === 'krea' ? 0 : IMAGE_V2_CONSISTENCY_CLIP
|
||||||
)
|
)
|
||||||
const conceptAllowed = isXaigenStudio()
|
const conceptAllowed = isXaigenStudio()
|
||||||
const generateSnofsFallback = !conceptAllowed || engine === 'krea' ? 0 : IMAGE_V2_SNOFS_MODEL
|
const generateSnofsFallback = 0
|
||||||
const generateSnofsClipFallback = !conceptAllowed || engine === 'krea' ? 0 : IMAGE_V2_SNOFS_CLIP
|
const generateSnofsClipFallback = 0
|
||||||
await listStudioLoras()
|
await listStudioLoras()
|
||||||
const loraStack = assertImageV2LoraStack(parsePostedLoraStack(fields.loraStack || fields.lora, 'image'), engine)
|
const loraStack = assertImageV2LoraStack(parsePostedLoraStack(fields.loraStack || fields.lora, 'image'), engine)
|
||||||
|
|
||||||
|
|||||||
@@ -797,8 +797,8 @@ async function startStudioEditJob(item: StudioJob) {
|
|||||||
steps: payload.steps,
|
steps: payload.steps,
|
||||||
seed: live.library?.seed || payload.seed,
|
seed: live.library?.seed || payload.seed,
|
||||||
cfg: payload.cfg,
|
cfg: payload.cfg,
|
||||||
snofsModel: payload.snofsModel ?? (isXaigenStudio() ? 0.65 : 0),
|
snofsModel: payload.snofsModel ?? 0,
|
||||||
snofsClip: payload.snofsClip ?? (isXaigenStudio() ? 0.35 : 0),
|
snofsClip: payload.snofsClip ?? 0,
|
||||||
consistencyModel: payload.consistencyModel ?? (mode === 'generate' || payload.engine === 'krea' ? 0 : 0.7),
|
consistencyModel: payload.consistencyModel ?? (mode === 'generate' || payload.engine === 'krea' ? 0 : 0.7),
|
||||||
consistencyClip: payload.consistencyClip ?? (mode === 'generate' || payload.engine === 'krea' ? 0 : 0.7),
|
consistencyClip: payload.consistencyClip ?? (mode === 'generate' || payload.engine === 'krea' ? 0 : 0.7),
|
||||||
megapixels: payload.scaleMegapixels ?? 1,
|
megapixels: payload.scaleMegapixels ?? 1,
|
||||||
|
|||||||
+17
-4
@@ -74,7 +74,7 @@ export const IMAGE_V2_KREA_UNET_CANDIDATES = [
|
|||||||
'krea2_turbo_nvfp4.safetensors',
|
'krea2_turbo_nvfp4.safetensors',
|
||||||
'krea2_turbo_fp8_scaled.safetensors'
|
'krea2_turbo_fp8_scaled.safetensors'
|
||||||
] as const
|
] 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<Exclude<ImageV2Task, 'scene' | 'refine' | 't2i'>, string> = {
|
export const IMAGE_V2_ROLE_HEADERS: Record<Exclude<ImageV2Task, 'scene' | 'refine' | 't2i'>, 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.',
|
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
|
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)
|
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) {
|
export function isImageV2ConsistencyLora(name: string) {
|
||||||
@@ -203,6 +212,10 @@ export function upsertImageV2Lora(stack: unknown, name: string, model: number, c
|
|||||||
return [item, ...current]
|
return [item, ...current]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function removeImageV2ConceptLora(stack: unknown, engine?: ImageV2Engine) {
|
||||||
|
return normalizeLoraStack(stack).filter(item => !isImageV2ConceptLora(item.name, engine))
|
||||||
|
}
|
||||||
|
|
||||||
export function defaultImageV2LoraStack(opts: {
|
export function defaultImageV2LoraStack(opts: {
|
||||||
engine: ImageV2Engine
|
engine: ImageV2Engine
|
||||||
mode: ImageV2Mode
|
mode: ImageV2Mode
|
||||||
@@ -242,7 +255,7 @@ export function mergeLegacyImageV2Loras(stack: unknown, opts: {
|
|||||||
const name = String(opts.kreaConcept || '').trim()
|
const name = String(opts.kreaConcept || '').trim()
|
||||||
const model = Number(opts.snofsModel) || 0
|
const model = Number(opts.snofsModel) || 0
|
||||||
const clip = Number(opts.snofsClip) || 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 upsertImageV2Lora(items, name, model, clip)
|
||||||
}
|
}
|
||||||
return items
|
return items
|
||||||
|
|||||||
+1
-1
@@ -31,7 +31,7 @@ export function loraIdentityKey(name: string) {
|
|||||||
|
|
||||||
export function isXaigenOnlyLora(name: string) {
|
export function isXaigenOnlyLora(name: string) {
|
||||||
const base = loraBasename(name)
|
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. */
|
/** Pick the real Comfy filename: exact path, then same basename ignoring `xaigen-`, then suffix/contains. */
|
||||||
|
|||||||
Reference in New Issue
Block a user