Show Flux and Krea as Image v2 engine cards, and run Krea on edit, compose, and refine.

Krea image-to-image uses encode plus denoise graphs, not Klein reference latents, and still fails closed if the Krea models are missing.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-08-29 07:29:31 -05:00
co-authored by Cursor
parent ab5477c999
commit 5fe91e6bae
12 changed files with 608 additions and 119 deletions
+61 -53
View File
@@ -711,29 +711,29 @@
</div>
<div v-else-if="studioMode === 'editv2'" class="space-y-3">
<p class="text-xs text-zinc-500">Edit, Compose, Refine, and Generate are separate graphs. Shares the desktop GPU with video.</p>
<p class="mb-2 text-xs uppercase tracking-wider text-zinc-500">Image engine</p>
<div class="grid grid-cols-2 gap-2">
<button
type="button"
class="rounded-xl border px-3 py-2 text-left text-sm"
:class="v2Engine === 'flux' ? 'border-amber-300 bg-amber-400/10 text-amber-100' : 'border-white/10 hover:border-white/20'"
@click="setV2Engine('flux')"
>
<span class="block font-semibold">Flux</span>
<span class="text-xs text-zinc-400">Klein 9B. Edit, compose, refine, and generate.</span>
</button>
<button
type="button"
class="rounded-xl border px-3 py-2 text-left text-sm"
:class="v2Engine === 'krea' ? 'border-amber-300 bg-amber-400/10 text-amber-100' : 'border-white/10 hover:border-white/20'"
@click="setV2Engine('krea')"
>
<span class="block font-semibold">Krea</span>
<span class="text-xs text-zinc-400">Krea 2 Turbo. Photograph engine for stills.</span>
</button>
</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 v-if="v2Mode === 'generate'" class="space-y-3 rounded-2xl border border-white/10 bg-zinc-950/40 p-3">
<div>
<p class="text-xs text-zinc-400">Engine</p>
<div class="mt-2 flex flex-wrap gap-2">
<button
type="button"
class="rounded-full border px-2.5 py-1 text-xs"
:class="v2Engine === 'flux' ? 'border-amber-300/70 text-amber-100' : 'border-white/10 text-zinc-400'"
@click="setV2Engine('flux')"
>
Flux
</button>
<button
type="button"
class="rounded-full border px-2.5 py-1 text-xs"
:class="v2Engine === 'krea' ? 'border-amber-300/70 text-amber-100' : 'border-white/10 text-zinc-400'"
@click="setV2Engine('krea')"
>
Krea
</button>
</div>
<p class="mt-2 text-xs text-zinc-500">Flux is the current edit and compose engine. Krea is a separate photograph engine.</p>
</div>
<div>
<p class="text-xs text-zinc-400">Size · near 1 MP</p>
<div class="mt-2 flex flex-wrap gap-2">
@@ -750,7 +750,7 @@
</div>
</div>
</div>
<div v-if="v2Mode === 'refine'" class="space-y-2 rounded-2xl border border-white/10 bg-zinc-950/40 p-3">
<div v-if="v2Mode === 'refine' || (v2Engine === 'krea' && v2Mode !== 'generate')" class="space-y-2 rounded-2xl border border-white/10 bg-zinc-950/40 p-3">
<label class="block text-sm">
<span class="mb-1 block text-zinc-300">Strength (denoise)</span>
<div class="flex items-center gap-3">
@@ -765,7 +765,7 @@
<span class="w-12 tabular-nums text-sm text-zinc-200">{{ v2Denoise }}</span>
</div>
</label>
<div class="flex flex-wrap gap-2">
<div v-if="v2Mode === 'refine'" class="flex flex-wrap gap-2">
<button
type="button"
class="rounded-full border px-2.5 py-1 text-xs"
@@ -1986,7 +1986,9 @@ import {
IMAGE_V2_GENERATE_HEIGHT,
IMAGE_V2_GENERATE_WIDTH,
IMAGE_V2_KREA_CFG,
IMAGE_V2_KREA_COMPOSE_DENOISE,
IMAGE_V2_KREA_CONCEPT_LORA,
IMAGE_V2_KREA_EDIT_DENOISE,
IMAGE_V2_KREA_STEPS,
IMAGE_V2_REFINE_FACE,
IMAGE_V2_REFINE_FACE_PROMPT,
@@ -3503,18 +3505,18 @@ function currentPresetSnapshot() {
loraStack: [],
settings: {
mode: v2Mode.value,
engine: v2Mode.value === 'generate' ? v2Engine.value : 'flux',
engine: v2Engine.value,
task: v2Mode.value === 'compose' ? v2Task.value : v2Mode.value === 'refine' ? 'refine' : v2Mode.value === 'generate' ? 't2i' : 'scene',
negative: v2Negative.value,
snofsModel: v2ConceptLoraEnabled.value ? clampImageV2Strength(v2SnofsModel.value, IMAGE_V2_SNOFS_MODEL) : 0,
snofsClip: v2ConceptLoraEnabled.value ? clampImageV2Strength(v2SnofsClip.value, IMAGE_V2_SNOFS_CLIP) : 0,
consistencyModel: v2Mode.value === 'generate' ? 0 : clampImageV2Strength(v2ConsistencyModel.value, IMAGE_V2_CONSISTENCY_MODEL),
consistencyClip: v2Mode.value === 'generate' ? 0 : clampImageV2Strength(v2ConsistencyClip.value, IMAGE_V2_CONSISTENCY_CLIP),
steps: clampImageSteps(v2Steps.value, IMAGE_V2_STEPS_DEFAULT),
cfg: clampImageCfg(v2Cfg.value, IMAGE_V2_CFG_DEFAULT),
consistencyModel: v2Mode.value === 'generate' || v2Engine.value === 'krea' ? 0 : clampImageV2Strength(v2ConsistencyModel.value, IMAGE_V2_CONSISTENCY_MODEL),
consistencyClip: v2Mode.value === 'generate' || v2Engine.value === 'krea' ? 0 : clampImageV2Strength(v2ConsistencyClip.value, IMAGE_V2_CONSISTENCY_CLIP),
steps: clampImageSteps(v2Steps.value, v2Engine.value === 'krea' ? IMAGE_V2_KREA_STEPS : IMAGE_V2_STEPS_DEFAULT),
cfg: clampImageCfg(v2Cfg.value, v2Engine.value === 'krea' ? IMAGE_V2_KREA_CFG : IMAGE_V2_CFG_DEFAULT),
megapixels: v2Mode.value === 'generate' ? undefined : clampImageScaleMegapixels(v2Megapixels.value, 1),
turbo: v2Turbo.value === true,
strength: v2Mode.value === 'refine' ? clampImageV2Denoise(v2Denoise.value) : undefined,
strength: v2Mode.value === 'generate' || (v2Mode.value !== 'refine' && v2Engine.value !== 'krea') ? undefined : clampImageV2Denoise(v2Denoise.value),
width: v2Mode.value === 'generate' ? v2Width.value : undefined,
height: v2Mode.value === 'generate' ? v2Height.value : undefined
}
@@ -3556,18 +3558,18 @@ function applyGenerationPreset(preset: GenerationPreset) {
if (preset.kind === 'imagev2') {
const settings = preset.settings as ImageV2PresetSettings
v2Mode.value = settings.mode === 'compose' ? 'compose' : settings.mode === 'refine' ? 'refine' : settings.mode === 'generate' ? 'generate' : 'edit'
v2Engine.value = v2Mode.value === 'generate' ? parseImageV2Engine(settings.engine, 'flux') : 'flux'
v2Engine.value = parseImageV2Engine(settings.engine, 'flux')
v2Task.value = v2Mode.value === 'compose' ? (settings.task || 'scene') : v2Mode.value === 'refine' ? 'refine' : v2Mode.value === 'generate' ? 't2i' : 'scene'
if (typeof settings.negative === 'string') v2Negative.value = settings.negative
v2SnofsModel.value = v2ConceptLoraEnabled.value ? clampImageV2Strength(settings.snofsModel, IMAGE_V2_SNOFS_MODEL) : 0
v2SnofsClip.value = v2ConceptLoraEnabled.value ? clampImageV2Strength(settings.snofsClip, IMAGE_V2_SNOFS_CLIP) : 0
v2ConsistencyModel.value = v2Mode.value === 'generate' ? 0 : clampImageV2Strength(settings.consistencyModel, IMAGE_V2_CONSISTENCY_MODEL)
v2ConsistencyClip.value = v2Mode.value === 'generate' ? 0 : clampImageV2Strength(settings.consistencyClip, IMAGE_V2_CONSISTENCY_CLIP)
v2Steps.value = clampImageSteps(settings.steps, IMAGE_V2_STEPS_DEFAULT)
v2Cfg.value = clampImageCfg(settings.cfg, IMAGE_V2_CFG_DEFAULT)
v2ConsistencyModel.value = v2Mode.value === 'generate' || v2Engine.value === 'krea' ? 0 : clampImageV2Strength(settings.consistencyModel, IMAGE_V2_CONSISTENCY_MODEL)
v2ConsistencyClip.value = v2Mode.value === 'generate' || v2Engine.value === 'krea' ? 0 : clampImageV2Strength(settings.consistencyClip, IMAGE_V2_CONSISTENCY_CLIP)
v2Steps.value = clampImageSteps(settings.steps, v2Engine.value === 'krea' ? IMAGE_V2_KREA_STEPS : IMAGE_V2_STEPS_DEFAULT)
v2Cfg.value = clampImageCfg(settings.cfg, v2Engine.value === 'krea' ? IMAGE_V2_KREA_CFG : IMAGE_V2_CFG_DEFAULT)
v2Megapixels.value = clampImageScaleMegapixels(settings.megapixels, 1)
v2Turbo.value = settings.turbo === true
if (v2Mode.value === 'refine') v2Denoise.value = clampImageV2Denoise(settings.strength)
if (v2Mode.value === 'refine' || (v2Engine.value === 'krea' && v2Mode.value !== 'generate')) v2Denoise.value = clampImageV2Denoise(settings.strength)
if (v2Mode.value === 'generate') {
const size = clampImageV2Size(settings.width, settings.height)
v2Width.value = size.width
@@ -4839,7 +4841,7 @@ function applyStillSettings(still: LibraryStill) {
? settings.mode
: 'edit'
setV2Mode(mode)
v2Engine.value = mode === 'generate' ? parseImageV2Engine(settings.engine, 'flux') : 'flux'
v2Engine.value = parseImageV2Engine(settings?.engine, 'flux')
if (!settings) return
if (typeof settings.negative === 'string') v2Negative.value = settings.negative
if (v2ConceptLoraEnabled.value) {
@@ -4849,7 +4851,7 @@ function applyStillSettings(still: LibraryStill) {
v2SnofsModel.value = 0
v2SnofsClip.value = 0
}
if (mode !== 'generate') {
if (mode !== 'generate' && v2Engine.value !== 'krea') {
if (settings.consistencyModel != null) v2ConsistencyModel.value = clampImageV2Strength(settings.consistencyModel, IMAGE_V2_CONSISTENCY_MODEL)
if (settings.consistencyClip != null) v2ConsistencyClip.value = clampImageV2Strength(settings.consistencyClip, IMAGE_V2_CONSISTENCY_CLIP)
}
@@ -4857,7 +4859,7 @@ function applyStillSettings(still: LibraryStill) {
if (settings.cfg != null) v2Cfg.value = clampImageCfg(settings.cfg, IMAGE_V2_CFG_DEFAULT)
if (settings.megapixels != null) v2Megapixels.value = clampImageScaleMegapixels(settings.megapixels, 1)
v2Turbo.value = settings.turbo === true
if (mode === 'refine' && settings.strength != null) v2Denoise.value = clampImageV2Denoise(settings.strength)
if ((mode === 'refine' || (v2Engine.value === 'krea' && mode !== 'generate')) && settings.strength != null) v2Denoise.value = clampImageV2Denoise(settings.strength)
if (mode === 'generate') {
const size = clampImageV2Size(settings.width, settings.height)
v2Width.value = size.width
@@ -5466,17 +5468,15 @@ function setV2Mode(mode: ImageV2Mode) {
v2Task.value = 't2i'
v2ConsistencyModel.value = 0
v2ConsistencyClip.value = 0
} else {
v2Engine.value = 'flux'
if (previous === 'generate' && v2ConsistencyModel.value === 0 && v2ConsistencyClip.value === 0) {
v2ConsistencyModel.value = IMAGE_V2_CONSISTENCY_MODEL
v2ConsistencyClip.value = IMAGE_V2_CONSISTENCY_CLIP
}
} else if (previous === 'generate' && v2Engine.value === 'flux' && v2ConsistencyModel.value === 0 && v2ConsistencyClip.value === 0) {
v2ConsistencyModel.value = IMAGE_V2_CONSISTENCY_MODEL
v2ConsistencyClip.value = IMAGE_V2_CONSISTENCY_CLIP
}
if (v2Engine.value === 'krea' && mode === 'edit') v2Denoise.value = IMAGE_V2_KREA_EDIT_DENOISE
if (v2Engine.value === 'krea' && mode === 'compose') v2Denoise.value = IMAGE_V2_KREA_COMPOSE_DENOISE
}
function setV2Engine(engine: ImageV2Engine) {
if (engine === 'krea' && v2Mode.value !== 'generate') return
const previous = v2Engine.value
v2Engine.value = engine
if (engine === 'krea') {
@@ -5487,6 +5487,8 @@ function setV2Engine(engine: ImageV2Engine) {
v2ConsistencyModel.value = 0
v2ConsistencyClip.value = 0
v2Turbo.value = false
if (v2Mode.value === 'edit') v2Denoise.value = IMAGE_V2_KREA_EDIT_DENOISE
if (v2Mode.value === 'compose') v2Denoise.value = IMAGE_V2_KREA_COMPOSE_DENOISE
return
}
if (previous === 'krea') {
@@ -5494,6 +5496,10 @@ function setV2Engine(engine: ImageV2Engine) {
v2Cfg.value = IMAGE_V2_CFG_DEFAULT
v2SnofsModel.value = v2ConceptLoraEnabled.value ? IMAGE_V2_SNOFS_MODEL : 0
v2SnofsClip.value = v2ConceptLoraEnabled.value ? IMAGE_V2_SNOFS_CLIP : 0
if (v2Mode.value !== 'generate') {
v2ConsistencyModel.value = IMAGE_V2_CONSISTENCY_MODEL
v2ConsistencyClip.value = IMAGE_V2_CONSISTENCY_CLIP
}
}
}
@@ -5587,21 +5593,23 @@ async function editImageV2() {
body.append('task', v2Mode.value === 'generate' ? 't2i' : v2Mode.value === 'refine' ? 'refine' : v2Mode.value === 'compose' ? v2Task.value : 'scene')
if (v2Mode.value !== 'generate' && file.value) body.append('image_a', file.value)
if (v2Mode.value === 'compose' && editRefFile.value) body.append('image_b', editRefFile.value)
if (v2Mode.value === 'refine') {
const maskBlob = await refinePainter.value?.exportPng()
if (!maskBlob) {
toast('Paint a mask on Still A first. Refine will not fall back to Edit.')
return
if (v2Mode.value === 'refine' || (v2Engine.value === 'krea' && v2Mode.value !== 'generate')) {
if (v2Mode.value === 'refine') {
const maskBlob = await refinePainter.value?.exportPng()
if (!maskBlob) {
toast('Paint a mask on Still A first. Refine will not fall back to Edit.')
return
}
body.append('mask', new File([maskBlob], 'refine-mask.png', { type: 'image/png' }))
}
body.append('mask', new File([maskBlob], 'refine-mask.png', { type: 'image/png' }))
body.append('strength', String(clampImageV2Denoise(v2Denoise.value)))
}
if (v2Mode.value === 'generate') {
const size = clampImageV2Size(v2Width.value, v2Height.value)
body.append('width', String(size.width))
body.append('height', String(size.height))
body.append('engine', v2Engine.value)
}
body.append('engine', v2Engine.value)
body.append('prompt', prompt.value.trim())
body.append('negative', v2Negative.value)
body.append('snofs_model', String(v2ConceptLoraEnabled.value ? clampImageV2Strength(v2SnofsModel.value, v2Engine.value === 'krea' ? 0 : IMAGE_V2_SNOFS_MODEL) : 0))