Add exact KV memory with persona overlays and param priority.

Canonical defaults live in exact.json (UI fills empties without an LLM call); chat session overrides beat file defaults until clear/persona change.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-21 23:04:34 +03:00
co-authored by Cursor
parent fe4d8d3a3a
commit 1b9cc1ad78
15 changed files with 418 additions and 121 deletions
+275 -28
View File
@@ -1,6 +1,6 @@
/**
* Swarm Assistent — Krea 2 collaborative chat (Ollama via Swarm API).
* v0.7.0: Config presets, persona folders, vector memory, chat|memory model roles.
* v0.7.0: Config presets, persona folders, exact KV + vector memory, chat|memory model roles.
*/
(function () {
const LS_BASE = 'swarm_assistent_base_url';
@@ -99,6 +99,9 @@
history: [],
packsLoaded: false,
config: null,
exact: null,
sessionExact: {},
lastUserParamIntent: false,
enabledSkills: [],
kreaProfiles: { turbo: { steps: 8, cfg: 1, sigma_shift: 1.15 }, raw: { steps: 28, cfg: 4.5 } },
preferredEmbed: null,
@@ -424,6 +427,158 @@
el.dispatchEvent(new Event('change', { bubbles: true }));
}
function isEmptyParamField(raw, { treatZeroEmpty = false } = {}) {
if (raw == null) {
return true;
}
const s = String(raw).trim();
if (s === '') {
return true;
}
if (treatZeroEmpty && (s === '0' || Number(s) === 0)) {
return true;
}
return false;
}
function userTextMentionsParams(text) {
return /\b(steps?|cfg|seed|sigma|размер|aspect|ширин|высот|resolution|batch|турбо|turbo|raw)\b/i.test(String(text || ''));
}
function applyAspectTableFrom(obj) {
if (!obj || typeof obj !== 'object') {
return false;
}
const next = {};
for (const [k, v] of Object.entries(obj)) {
if (Array.isArray(v) && v.length >= 2) {
next[k] = [Number(v[0]), Number(v[1])];
}
}
if (!Object.keys(next).length) {
return false;
}
ASPECT_TABLE = next;
return true;
}
function resolveExactBundle() {
const exact = state.exact || state.config?.exact || {};
const profiles = exact.profiles || state.kreaProfiles || {};
return { exact, profiles };
}
function detectKreaProfileName() {
try {
const model = resolveCurrentCheckpoint();
const blob = `${model?.name || ''} ${model?.title || ''}`.toLowerCase();
const hasRaw = /\braw\b/.test(blob);
const hasTurbo = /\bturbo\b/.test(blob);
return hasRaw && !hasTurbo ? 'raw' : 'turbo';
} catch (e) {
return (state.exact?.generation?.profile) || 'turbo';
}
}
function mergedGenerationDefaults(profileName) {
const { exact, profiles } = resolveExactBundle();
const gen = exact.generation && typeof exact.generation === 'object' ? { ...exact.generation } : {};
const profile = profileName || gen.profile || detectKreaProfileName();
const fromProfile = profiles[profile] && typeof profiles[profile] === 'object' ? { ...profiles[profile] } : {};
const session = state.sessionExact && typeof state.sessionExact === 'object' ? { ...state.sessionExact } : {};
// Profile (turbo/raw) overrides generation defaults; session overrides both.
return { ...gen, ...fromProfile, profile, ...session };
}
function exactDefaultFor(key, profileName) {
const { exact, profiles } = resolveExactBundle();
const profile = profileName || exact.generation?.profile || detectKreaProfileName();
const fromProfile = profiles[profile]?.[key];
if (fromProfile != null) {
return fromProfile;
}
return exact.generation?.[key];
}
function rememberSessionExact(partial) {
if (!partial || typeof partial !== 'object') {
return;
}
const keys = ['steps', 'cfg', 'sigma_shift', 'aspect', 'width', 'height', 'images', 'batch', 'seed', 'sampler', 'scheduler'];
for (const k of keys) {
if (partial[k] != null) {
state.sessionExact[k] = partial[k];
}
}
if (partial.images == null && partial.batch != null) {
state.sessionExact.images = partial.batch;
}
}
function fillEmptyParamsFromExact() {
const defaults = mergedGenerationDefaults();
if (isEmptyParamField(val('input_steps'), { treatZeroEmpty: true }) && defaults.steps != null) {
setVal('input_steps', String(defaults.steps));
}
const cfgRaw = val('input_cfgscale') || val('input_cfg');
if (isEmptyParamField(cfgRaw, { treatZeroEmpty: true }) && defaults.cfg != null) {
if (document.getElementById('input_cfgscale')) {
setVal('input_cfgscale', String(defaults.cfg));
} else if (document.getElementById('input_cfg')) {
setVal('input_cfg', String(defaults.cfg));
}
}
if (isEmptyParamField(val('input_sigmashift')) && defaults.sigma_shift != null) {
setVal('input_sigmashift', String(defaults.sigma_shift));
}
const wEmpty = isEmptyParamField(val('input_width'), { treatZeroEmpty: true });
const hEmpty = isEmptyParamField(val('input_height'), { treatZeroEmpty: true });
if ((wEmpty || hEmpty) && defaults.aspect) {
const size = sizeFromAspect(defaults.aspect);
if (size) {
if (wEmpty) {
setVal('input_width', String(size[0]));
}
if (hEmpty) {
setVal('input_height', String(size[1]));
}
}
} else {
if (wEmpty && defaults.width != null) {
setVal('input_width', String(defaults.width));
}
if (hEmpty && defaults.height != null) {
setVal('input_height', String(defaults.height));
}
}
const batchId = document.getElementById('input_images') ? 'input_images' : (document.getElementById('input_batchsize') ? 'input_batchsize' : null);
if (batchId && isEmptyParamField(val(batchId), { treatZeroEmpty: true })) {
const batch = defaults.images != null ? defaults.images : defaults.batch;
if (batch != null) {
setVal(batchId, String(batch));
}
}
}
function shouldSkipSessionRollback(key, patchValue) {
if (state.lastUserParamIntent) {
return false;
}
if (state.sessionExact[key] == null) {
return false;
}
const sessionVal = state.sessionExact[key];
if (String(sessionVal) === String(patchValue)) {
return false;
}
const exactVal = exactDefaultFor(key);
if (exactVal == null) {
return false;
}
// Model trying to restore file exact while session override differs — keep session.
return String(patchValue) === String(exactVal);
}
function openAssistentTab() {
const tab = document.getElementById(TAB_BUTTON_ID);
if (tab) {
@@ -1143,6 +1298,8 @@
function onPersonaChanged() {
const id = $('sa_persona')?.value || 'neutral';
state.sessionExact = {};
state.lastUserParamIntent = false;
saveSettings();
loadConfig(id, (data) => {
const title = data?.personas?.find((p) => p.id === id)?.title
@@ -1159,6 +1316,7 @@
$('sa_pack').value = packId;
}
}
fillEmptyParamsFromExact();
});
}
@@ -1480,15 +1638,32 @@
const blob = `${model.name || ''} ${model.title || ''}`.toLowerCase();
const hasRaw = /\braw\b/.test(blob);
const hasTurbo = /\bturbo\b/.test(blob);
ctx.krea_profile = hasRaw && !hasTurbo ? 'raw' : 'turbo';
ctx.recommended_params = ctx.krea_profile === 'raw'
? { steps: 28, cfg: 4.5 }
: { steps: 8, cfg: 1, sigma_shift: 1.15 };
const profile = hasRaw && !hasTurbo ? 'raw' : 'turbo';
ctx.krea_profile = profile;
const defaults = mergedGenerationDefaults(profile);
ctx.recommended_params = {
steps: defaults.steps ?? 8,
cfg: defaults.cfg ?? 1,
sigma_shift: defaults.sigma_shift ?? 1.15,
};
if (defaults.aspect) {
ctx.recommended_params.aspect = defaults.aspect;
}
} catch (e) {
ctx.krea_profile = 'turbo';
ctx.recommended_params = { steps: 8, cfg: 1, sigma_shift: 1.15 };
const defaults = mergedGenerationDefaults('turbo');
ctx.recommended_params = {
steps: defaults.steps ?? 8,
cfg: defaults.cfg ?? 1,
sigma_shift: defaults.sigma_shift ?? 1.15,
};
}
ctx.exact = state.exact || state.config?.exact || null;
ctx.session_exact = state.sessionExact && Object.keys(state.sessionExact).length
? { ...state.sessionExact }
: {};
return ctx;
}
@@ -1901,7 +2076,7 @@
if (/\b(inpaint|замажь|закрась|руки|лицо|маск|mask|img2img|init\s*image)\b/i.test(t)) {
return 'inpaint_edit';
}
if (/\b(steps?|cfg|seed|sigma|размер|aspect|ширин|высот|resolution|batch)\b/i.test(t)) {
if (userTextMentionsParams(t)) {
return 'fix_params';
}
if (/\b(сцен|moodboard|атмосфер|compose|scene|мизансцен)\b/i.test(t)) {
@@ -2010,27 +2185,68 @@
}
if (doParams) {
const defaults = mergedGenerationDefaults();
const capture = state.lastUserParamIntent;
const aspectSize = sizeFromAspect(patch.aspect);
if (patch.aspect != null && !shouldSkipSessionRollback('aspect', patch.aspect)) {
if (aspectSize) {
setVal('input_width', String(aspectSize[0]));
setVal('input_height', String(aspectSize[1]));
}
if (capture) {
rememberSessionExact({ aspect: patch.aspect });
}
} else if (patch.aspect == null && isEmptyParamField(val('input_width'), { treatZeroEmpty: true })
&& isEmptyParamField(val('input_height'), { treatZeroEmpty: true }) && defaults.aspect) {
const fill = sizeFromAspect(defaults.aspect);
if (fill) {
setVal('input_width', String(fill[0]));
setVal('input_height', String(fill[1]));
}
} else {
if (patch.width != null) {
if (patch.width != null && !shouldSkipSessionRollback('width', patch.width)) {
setVal('input_width', String(patch.width));
if (capture) {
rememberSessionExact({ width: patch.width });
}
if (patch.height != null) {
} else if (patch.width == null && isEmptyParamField(val('input_width'), { treatZeroEmpty: true }) && defaults.width != null) {
setVal('input_width', String(defaults.width));
}
if (patch.height != null && !shouldSkipSessionRollback('height', patch.height)) {
setVal('input_height', String(patch.height));
if (capture) {
rememberSessionExact({ height: patch.height });
}
} else if (patch.height == null && isEmptyParamField(val('input_height'), { treatZeroEmpty: true }) && defaults.height != null) {
setVal('input_height', String(defaults.height));
}
}
if (patch.steps != null) {
if (patch.steps != null && !shouldSkipSessionRollback('steps', patch.steps)) {
setVal('input_steps', String(patch.steps));
if (capture) {
rememberSessionExact({ steps: patch.steps });
}
if (patch.cfg != null) {
} else if (patch.steps == null && isEmptyParamField(val('input_steps'), { treatZeroEmpty: true }) && defaults.steps != null) {
setVal('input_steps', String(defaults.steps));
}
if (patch.cfg != null && !shouldSkipSessionRollback('cfg', patch.cfg)) {
if (document.getElementById('input_cfgscale')) {
setVal('input_cfgscale', String(patch.cfg));
} else {
setVal('input_cfg', String(patch.cfg));
}
if (capture) {
rememberSessionExact({ cfg: patch.cfg });
}
} else if (patch.cfg == null) {
const cfgRaw = val('input_cfgscale') || val('input_cfg');
if (isEmptyParamField(cfgRaw, { treatZeroEmpty: true }) && defaults.cfg != null) {
if (document.getElementById('input_cfgscale')) {
setVal('input_cfgscale', String(defaults.cfg));
} else if (document.getElementById('input_cfg')) {
setVal('input_cfg', String(defaults.cfg));
}
}
}
if (patch.vary === true) {
setVal('input_seed', '-1');
@@ -2039,27 +2255,50 @@
if (cur && String(cur) !== '-1') {
setVal('input_seed', cur);
}
} else if (patch.seed != null) {
} else if (patch.seed != null && !shouldSkipSessionRollback('seed', patch.seed)) {
setVal('input_seed', String(patch.seed));
if (capture) {
rememberSessionExact({ seed: patch.seed });
}
if (patch.sigma_shift != null) {
}
if (patch.sigma_shift != null && !shouldSkipSessionRollback('sigma_shift', patch.sigma_shift)) {
setVal('input_sigmashift', String(patch.sigma_shift));
if (capture) {
rememberSessionExact({ sigma_shift: patch.sigma_shift });
}
} else if (patch.sigma_shift == null && isEmptyParamField(val('input_sigmashift')) && defaults.sigma_shift != null) {
setVal('input_sigmashift', String(defaults.sigma_shift));
}
if (patch.sampler != null) {
if (document.getElementById('input_sampler')) {
setVal('input_sampler', String(patch.sampler));
}
if (capture) {
rememberSessionExact({ sampler: patch.sampler });
}
}
if (patch.scheduler != null && document.getElementById('input_scheduler')) {
setVal('input_scheduler', String(patch.scheduler));
if (capture) {
rememberSessionExact({ scheduler: patch.scheduler });
}
}
const batch = patch.images != null ? patch.images : patch.batch;
if (batch != null) {
if (batch != null && !shouldSkipSessionRollback('images', batch)) {
if (document.getElementById('input_images')) {
setVal('input_images', String(batch));
} else if (document.getElementById('input_batchsize')) {
setVal('input_batchsize', String(batch));
}
if (capture) {
rememberSessionExact({ images: batch });
}
} else if (batch == null) {
const batchId = document.getElementById('input_images') ? 'input_images' : (document.getElementById('input_batchsize') ? 'input_batchsize' : null);
const defBatch = defaults.images != null ? defaults.images : defaults.batch;
if (batchId && isEmptyParamField(val(batchId), { treatZeroEmpty: true }) && defBatch != null) {
setVal(batchId, String(defBatch));
}
}
}
@@ -2835,19 +3074,16 @@
return;
}
state.config = data;
if (data.model?.aspect_table && typeof data.model.aspect_table === 'object') {
const next = {};
for (const [k, v] of Object.entries(data.model.aspect_table)) {
if (Array.isArray(v) && v.length >= 2) {
next[k] = [Number(v[0]), Number(v[1])];
if (data.exact && typeof data.exact === 'object') {
state.exact = data.exact;
}
const aspectSource = data.exact?.aspect_table || data.model?.aspect_table;
if (aspectSource && typeof aspectSource === 'object') {
applyAspectTableFrom(aspectSource);
}
if (Object.keys(next).length) {
ASPECT_TABLE = next;
}
}
if (data.model?.profiles) {
state.kreaProfiles = data.model.profiles;
const profileSource = data.exact?.profiles || data.model?.profiles;
if (profileSource && typeof profileSource === 'object') {
state.kreaProfiles = profileSource;
}
if (data.ui?.pack_aliases) {
PACK_ALIASES = { ...PACK_ALIASES, ...data.ui.pack_aliases };
@@ -2879,6 +3115,9 @@
if (data.assistant?.embed_model && !state.preferredEmbed) {
state.preferredEmbed = data.assistant.embed_model;
}
if (applyDefaults || data.exact) {
fillEmptyParamsFromExact();
}
}
function renderPersonaOptions(personas, selected) {
@@ -3958,7 +4197,10 @@
if (!Array.isArray(withActions.actions) && patchHasGenTrigger(withActions)) {
withActions.actions = ['generate'];
}
const prevIntent = state.lastUserParamIntent;
state.lastUserParamIntent = true;
await applyPatch(withActions, 'all');
state.lastUserParamIntent = prevIntent;
setStatus(note || 'Applied');
if ($('sa_auto_generate')?.checked) {
await runGenerateFromPatch(withActions);
@@ -4209,6 +4451,9 @@
if (!text) {
return;
}
if (!opts.fromAutoCritique && !opts.fromDownload && !opts.fromVisionHop) {
state.lastUserParamIntent = userTextMentionsParams(text);
}
if (!opts.fromAutoCritique && !opts.fromDownload && !opts.fromVisionHop && !opts.skipSlash) {
if (rawInput.startsWith('/')) {
@@ -4745,6 +4990,8 @@
state.visionHopUsed = false;
state.packUserTouched = false;
state.pendingPersonaNote = null;
state.sessionExact = {};
state.lastUserParamIntent = false;
clearPersistedHistory();
const box = $('sa_messages');
if (box) {
@@ -4786,11 +5033,11 @@
} else if (vary) {
await applyQuickPatch({ vary: true, seed: -1, actions: ['generate'] }, 'Vary');
} else if (profile === 'turbo') {
const p = state.kreaProfiles?.turbo || { steps: 8, cfg: 1, sigma_shift: 1.15 };
const p = state.kreaProfiles?.turbo || mergedGenerationDefaults('turbo');
await applyQuickPatch({ steps: p.steps ?? 8, cfg: p.cfg ?? 1, sigma_shift: p.sigma_shift ?? 1.15, actions: ['generate'] }, 'Turbo');
} else if (profile === 'raw') {
const p = state.kreaProfiles?.raw || { steps: 28, cfg: 4.5 };
await applyQuickPatch({ steps: p.steps ?? 28, cfg: p.cfg ?? 4.5, actions: ['generate'] }, 'RAW');
const p = state.kreaProfiles?.raw || mergedGenerationDefaults('raw');
await applyQuickPatch({ steps: p.steps ?? 28, cfg: p.cfg ?? 4.5, sigma_shift: p.sigma_shift, actions: ['generate'] }, 'RAW');
}
renderLoraChips();
});
+5
View File
@@ -319,6 +319,9 @@ public sealed class AssistentConfig
public JObject LoadUi(string personaId) => MergeJsonLayers("ui.json", LayerRoots(personaId));
/// <summary>Exact (KV) memory: bundled base → persona overlays → disk overlays. Persona keys overwrite base.</summary>
public JObject LoadExact(string personaId) => MergeJsonLayers("exact.json", LayerRoots(personaId));
public JObject LoadModelProfile(string personaId)
{
JObject assistant = LoadAssistant(personaId);
@@ -712,6 +715,7 @@ public sealed class AssistentConfig
JObject assistant = LoadAssistant(id);
JObject ui = LoadUi(id);
JObject model = LoadModelProfile(id);
JObject exact = LoadExact(id);
var packs = ListPacks(id);
var skills = ListSkills(id);
var personas = ListPersonaCatalog();
@@ -724,6 +728,7 @@ public sealed class AssistentConfig
["assistant"] = assistant,
["ui"] = ui,
["model"] = model,
["exact"] = exact,
["packs"] = new JArray(packs.Select(p => new JObject
{
["id"] = p.id,
+1 -1
View File
@@ -10,7 +10,7 @@
"default_persona": "neutral",
"embed_model": "nomic-embed-text",
"memory_top_k": 10,
"seed_version": 1,
"seed_version": 2,
"gate": {
"architecture": "krea2",
"keywords": ["krea"]
+17 -1
View File
@@ -2,13 +2,28 @@
You are **Swarm Assistent**, a collaborative art director for image generation inside SwarmUI.
## Priority (mandatory)
When instructions conflict, apply this order (highest wins):
1. **This core contract** — output format, never invent LoRA/checkpoint names, never use CFG 0.
2. **Current user message** — explicit “use steps 20 / aspect 16:9 now” wins for that turn.
3. **Live `session_exact`** — prior user overrides this chat (until persona change / clear chat).
4. **Exact memory** (`## Exact memory` JSON) — canonical defaults (steps/CFG/aspect/facts). Persona overlays are already merged into it.
5. **Filled live SwarmUI fields** — respect what is already set unless the user or pack asks to change.
6. **`memory_hits` (vector RAG)** — notes, pitfalls, LoRA blurbs. Never override exact numbers or the users param request.
7. Guesses — last resort only.
Exact = encyclopedia of defaults. RAG = soft notes. Do **not** re-emit `steps` / `cfg` / `sigma_shift` / `aspect` when they already match exact (or session_exact) and the user did not ask to change them.
## Live context
A JSON block named "Live SwarmUI context" is attached. Treat it as ground truth — refreshed every chat turn:
- Use only LoRAs listed in `available_loras` / `enabled_loras` (exact `name`), or Civitai search candidates.
- Prefer listed `trigger_phrase` / `triggers`**never invent** trigger words.
- `memory_hits` are retrieved facts (model knowledge, LoRA notes, pitfalls). Trust them over guesses.
- `exact` / `session_exact` / `recommended_params` — generation defaults; see Priority.
- `memory_hits` are retrieved notes (LoRA tips, pitfalls). Trust them over guesses, but **not** over exact or the user.
- `model_cards` for **enabled** models beat generic blurbs — follow `when` / `avoid` / `prompt_hint` / `triggers`.
- `taste_profile` is the user's remembered preferences — bias toward it unless they override.
- Prefer `krea_likely` / Krea architecture entries; ignore FLUX/SDXL LoRAs.
@@ -67,6 +82,7 @@ A JSON block named "Live SwarmUI context" is attached. Treat it as ground truth
### Patch rules
- Omit keys you are not changing.
- Prefer omitting `steps` / `cfg` / `sigma_shift` / `aspect` when they already match Exact memory (or `session_exact`) and the user did not request a change — the UI fills empties from exact.
- `loras` replaces the intended LoRA set for Apply (list all that should be on).
- Prefer `aspect` over raw width/height when framing changes.
- `vary: true` — new random seed. `lock_seed: true` — reuse current seed.
+38
View File
@@ -0,0 +1,38 @@
{
"generation": {
"profile": "turbo",
"steps": 8,
"cfg": 1,
"sigma_shift": 1.15,
"images": 1
},
"profiles": {
"turbo": {
"steps": 8,
"cfg": 1,
"sigma_shift": 1.15
},
"raw": {
"steps": 28,
"cfg": 4.5,
"sigma_shift": 1.15
}
},
"aspect_table": {
"1:1": [1024, 1024],
"4:3": [1184, 896],
"3:2": [1248, 832],
"16:9": [1376, 768],
"2.35:1": [1568, 672],
"4:5": [928, 1152],
"2:3": [832, 1248],
"9:16": [768, 1376]
},
"facts": {
"architecture": "Krea 2 is a 12B DiT architecture. Not FLUX, not SDXL, not FLUX.1-Krea. Text encoder: Qwen3-VL 4B. VAE: Qwen Image VAE. Use only Krea2-trained LoRAs — never suggest FLUX/SDXL LoRAs.",
"negatives": "Negative prompts are nearly useless with Qwen3-VL. Prefer positives (sharp focus, empty street) over no blur / no people. Built-in NSFW text-refiner may strip risque words; LoRAs/finetunes may restore — stay practical.",
"prompt_images": "Prompt Images (refs in the prompt box) often overpower text — use sparingly and warn. Init Image = structure (img2img). Mask = local fix. They are not interchangeable. Cloud-only features (moodboards, Generative Sliders) are not in Swarm — emulate with prompt language + board refs.",
"turbo": "Krea 2 Turbo defaults live in exact.generation / exact.profiles.turbo. Never use CFG 0 — broken output. Side ~1024 (1284096 OK).",
"raw": "Krea 2 RAW/Base: prefer exact.profiles.raw when checkpoint name/title looks like RAW (not Turbo). If a turbo LoRA exists, weight ~0.6 for photoreal (1.0 ≈ full turbo). Swarm Generate cannot run dual-sampler Comfy graphs — only suggest LoRA weight + steps/CFG the UI can set."
}
}
+2 -44
View File
@@ -1,50 +1,8 @@
[
{
"kind": "aspect",
"key": "1:1",
"key": "table",
"tags": ["aspect", "1k"],
"text": "Aspect 1:1 maps to 1024×1024 on the official Krea 1K table. Prefer patch field aspect over raw width/height."
},
{
"kind": "aspect",
"key": "4:3",
"tags": ["aspect", "1k"],
"text": "Aspect 4:3 maps to 1184×896."
},
{
"kind": "aspect",
"key": "3:2",
"tags": ["aspect", "1k"],
"text": "Aspect 3:2 maps to 1248×832."
},
{
"kind": "aspect",
"key": "16:9",
"tags": ["aspect", "1k", "widescreen"],
"text": "Aspect 16:9 maps to 1376×768."
},
{
"kind": "aspect",
"key": "2.35:1",
"tags": ["aspect", "1k", "cinematic"],
"text": "Aspect 2.35:1 (cinematic ultrawide) maps to 1568×672."
},
{
"kind": "aspect",
"key": "4:5",
"tags": ["aspect", "1k", "portrait"],
"text": "Aspect 4:5 maps to 928×1152 — good for portrait."
},
{
"kind": "aspect",
"key": "2:3",
"tags": ["aspect", "1k", "portrait"],
"text": "Aspect 2:3 maps to 832×1248."
},
{
"kind": "aspect",
"key": "9:16",
"tags": ["aspect", "1k", "stories"],
"text": "Aspect 9:16 maps to 768×1376 — vertical / stories."
"text": "Official Krea 1K aspect → width/height table lives in Exact memory aspect_table. Prefer patch field aspect over raw width/height. UI fills empties from Exact."
}
]
+5 -5
View File
@@ -3,30 +3,30 @@
"kind": "model",
"key": "krea2_architecture",
"tags": ["krea", "architecture"],
"text": "Krea 2 is a 12B DiT architecture. Not FLUX, not SDXL, not FLUX.1-Krea. Text encoder: Qwen3-VL 4B. VAE: Qwen Image VAE. Use only Krea2-trained LoRAs — never suggest FLUX/SDXL LoRAs."
"text": "Krea 2 architecture facts live in Exact memory (exact.facts.architecture). Prefer Exact KV over this note. Never suggest FLUX/SDXL LoRAs."
},
{
"kind": "model",
"key": "krea2_turbo",
"tags": ["krea", "turbo", "params"],
"text": "Krea 2 Turbo defaults: steps 8 (min 4), CFG 1 (never CFG 0 — broken output), sigma shift 1.15, side ~1024 (1284096 OK)."
"text": "Turbo numeric defaults (steps/CFG/sigma) live in Exact memory profiles.turbo / generation — do not invent numbers; never use CFG 0."
},
{
"kind": "model",
"key": "krea2_raw",
"tags": ["krea", "raw", "params"],
"text": "Krea 2 RAW/Base: steps ~2052, CFG ~44.5. If checkpoint name/title looks like RAW (not Turbo), prefer RAW settings. If a turbo LoRA exists, weight ~0.6 for photoreal (1.0 ≈ full turbo). Swarm Generate cannot run dual-sampler Comfy graphs — only suggest LoRA weight + steps/CFG the UI can set."
"text": "RAW numeric defaults live in Exact memory profiles.raw. If checkpoint looks like RAW (not Turbo), use that profile. Turbo LoRA weight ~0.6 for photoreal when needed."
},
{
"kind": "model",
"key": "krea2_negatives",
"tags": ["krea", "prompting"],
"text": "Negative prompts are nearly useless with Qwen3-VL. Prefer positives (sharp focus, empty street) over no blur / no people. Built-in NSFW text-refiner may strip risque words; LoRAs/finetunes may restore — stay practical."
"text": "See Exact memory facts.negatives: negatives are nearly useless with Qwen3-VL — prefer positive phrasing."
},
{
"kind": "model",
"key": "krea2_prompt_images",
"tags": ["krea", "board"],
"text": "Prompt Images (refs in the prompt box) often overpower text — use sparingly and warn. Init Image = structure (img2img). Mask = local fix. They are not interchangeable. Cloud-only features (moodboards, Generative Sliders) are not in Swarm — emulate with prompt language + board refs."
"text": "See Exact memory facts.prompt_images: Prompt Images overpower text; Init ≠ Mask ≠ Prompt Images."
}
]
+1 -23
View File
@@ -1,26 +1,4 @@
{
"id": "krea2",
"gate_keywords": ["krea"],
"profiles": {
"turbo": {
"steps": 8,
"cfg": 1,
"sigma_shift": 1.15
},
"raw": {
"steps": 28,
"cfg": 4.5,
"sigma_shift": 1.15
}
},
"aspect_table": {
"1:1": [1024, 1024],
"4:3": [1184, 896],
"3:2": [1248, 832],
"16:9": [1376, 768],
"2.35:1": [1568, 672],
"4:5": [928, 1152],
"2:3": [832, 1248],
"9:16": [768, 1376]
}
"gate_keywords": ["krea"]
}
+4 -4
View File
@@ -4,16 +4,16 @@ Goal: adjust **generation parameters** for Krea 2 Turbo (or RAW if context says
## Guidelines
- **Turbo:** steps 412 (default **8**), CFG **1** (never 0), sigma shift ~**1.15**.
- **RAW/base:** steps 2052, CFG ~44.5 — only if checkpoint/context indicates Raw. If a turbo-distill LoRA is available, weight **0.6** is the usual photoreal compromise (UI LoRA only — no dual-sampler).
- Prefer live context field `krea_profile` (`turbo` | `raw`) and `recommended_params` when present.
- **Aspect:** prefer patch field `aspect` (`1:1`, `4:5`, `2:3`, `16:9`, `9:16`, `4:3`, `3:2`, `2.35:1`) — UI maps to official 1K sizes. Else set width/height near 1024.
- Prefer **Exact memory** (`profiles.turbo` / `profiles.raw`), live `recommended_params`, and `session_exact` over invented numbers. Never CFG 0.
- Prefer live context field `krea_profile` (`turbo` | `raw`) when present.
- **Aspect:** prefer patch field `aspect` from Exact `aspect_table` — UI maps to official 1K sizes. Else set width/height near 1024.
- **Batch:** `images` or `batch` (14 typical).
- **Seed:** `lock_seed: true` to reuse current; `vary: true` or `seed: -1` for a new roll; set numeric `seed` for exact reproducibility.
- **Sampler/scheduler:** leave alone unless the user asks (Swarm default is fine; community Turbo often Euler + Simple).
- **Init creativity** only when `has_init_image` or enabling img2img — see `inpaint_edit`.
- Do not change the prompt unless needed for the new framing.
- Keep LoRAs unless asked to drop them.
- Users requested params for this turn beat Exact; do not silently roll them back to Exact defaults.
## Deliverable
+1 -1
View File
@@ -15,7 +15,7 @@ Structure as flowing prose (not tag soup):
- Put **LoRA trigger phrases** near the subject they affect.
- Short user ideas → expand. User already wrote a polished paragraph → keep it; only fix tags/weights/negatives-as-positives.
- Keep Turbo defaults unless asked (steps 8, cfg 1). Prefer `aspect` for framing.
- Keep Exact Turbo defaults unless asked (see Exact memory / `recommended_params`). Prefer `aspect` for framing. Omit steps/cfg/sigma/aspect from the patch when they already match Exact and the user did not ask to change them.
- Missing style LoRA → `actions: ["search_civitai"]` + clear `search_query` (Krea-compatible).
- Optional `creativity` / intensity/complexity/movement: expand or restrain wording accordingly; bake slider intent into the prose.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"id": "memory",
"title": "Vector memory",
"title": "Exact + vector memory",
"default": true,
"prompt_file": "memory.md"
}
+10 -2
View File
@@ -1,8 +1,15 @@
# Skill: memory
You have a persistent vector memory (`memory_hits` in live context).
You have two memory layers:
## When to write
1. **Exact memory** (`## Exact memory` + live `exact` / `session_exact`) — canonical KV defaults (generation params, aspect table, architecture facts). Always prefer Exact over RAG for numbers and defaults.
2. **Vector memory** (`memory_hits`) — soft notes from retrieve (LoRA tips, pitfalls, paths).
## Priority
User (this turn) > `session_exact` > Exact KV > filled live fields > `memory_hits` > guesses. Never let a vector hit override Exact steps/CFG/aspect or an explicit user param request.
## When to write (vector only)
- Durable facts about a LoRA/checkpoint (when it works, what it breaks, good weight).
- Bad paths / pitfalls you discovered this session.
@@ -10,6 +17,7 @@ You have a persistent vector memory (`memory_hits` in live context).
## When not to write
- Do not dump Exact defaults into vector memory — they already live in `exact.json`.
- Do not dump the full inventory — retrieve already surfaces relevant blurbs.
- Do not store the user's taste profile (that is `taste_profile` / taste.json).
- Do not upsert trivia that is already in `memory_hits` with the same meaning.
+5
View File
@@ -0,0 +1,5 @@
{
"generation": {
"aspect": "2.35:1"
}
}
+14 -5
View File
@@ -2,7 +2,7 @@
SwarmUI extension for **collaborative Krea 2** prompting via **Ollama**: chat + board (Generate | Refs tabs), LoRA chips, **persona presets** (`Config/personas/`), **vector memory**, model cards with Civitai fetch, img2img/inpaint, slash commands, auto Generate.
**Version 0.7.0** — Config/_base + persona folders, skills, memory-seed → SQLite, Ollama `use: chat|memory`, slim inventory via retrieve.
**Version 0.7.0** — Config/_base + persona folders, skills, **exact KV memory** + vector memory-seed → SQLite, Ollama `use: chat|memory`, slim inventory via retrieve.
## Layout
@@ -15,20 +15,29 @@ SwarmUI extension for **collaborative Krea 2** prompting via **Ollama**: chat +
```
Config/
_base/ # defaults (assistant, ui, models/krea2, core, packs, skills, memory-seed, identity)
personas/<id>/ # sparse preset: persona/voice/likes/dislikes/rules + optional overrides
_base/ # defaults (assistant, ui, models/krea2, exact.json, core, packs, skills, memory-seed, identity)
personas/<id>/ # sparse preset: persona/voice/likes/dislikes/rules + optional exact.json / overrides
```
Disk overlay (wins over bundled): `/mnt/swarm_data/Assistent/` — same layout, plus `settings.json`, `taste.json`, `personas.json` (legacy prompt overlay), `ollama-roles.json`, `memory/assistent.sqlite`.
Copy `personas/cinema/``noir/`, edit only differing JSON files.
## Exact memory (KV)
- `Config/_base/exact.json` — canonical generation defaults, profiles (turbo/raw), aspect table, short facts
- Persona / disk overlays merge via DeepMerge (matching keys overwrite)
- Always injected into the system prompt; UI fills **empty** SwarmUI fields from Exact (no LLM call)
- Chat-session overrides (`session_exact`) last until persona change or clear chat — not written to disk
- Priority: core contract → current user → session_exact → exact (+ persona) → live fields → vector `memory_hits`
## Vector memory
- SQLite + Ollama `/api/embed` (default `nomic-embed-text`, pick in ⚙)
- First chat seeds `Config/_base/memory-seed/` (Krea facts, aspect, pitfalls)
- First chat seeds `Config/_base/memory-seed/` (pointers + pitfalls; numbers live in Exact)
- Agents upsert via patch `memory_upsert` / `memory_forget`
- Cards ingest on save; retrieve → `memory_hits` in live context (inventory slimmed)
- Soft notes only — Exact and the user beat RAG for params
## UX
@@ -76,7 +85,7 @@ Restart / rebuild SwarmUI after clone. gpu-rent: `seed-extensions` + restart.
**Packs** (one active): `write_prompt`, `critique_image`, `compose_scene`, `fix_params`, `inpaint_edit`, `describe_ref`, `catalog_card`.
**Skills** (checkboxes): `prompting`, `creativity_sliders`, `memory` — procedures, not model encyclopedia (facts live in memory-seed).
**Skills** (checkboxes): `prompting`, `creativity_sliders`, `memory` — procedures; encyclopedia numbers live in Exact, soft notes in memory-seed / RAG.
**Personas:** `neutral`, `lewd`, `aggressive`, `cinema`, `terse` under `Config/personas/`.
+35 -2
View File
@@ -1347,6 +1347,16 @@ public class SwarmAssistentExtension : Extension
}
}
JObject exact = Config.LoadExact(pid);
if (exact is not null && exact.Count > 0)
{
system.AppendLine();
system.AppendLine("## Exact memory (canonical KV — always trust over RAG guesses)");
system.AppendLine("```json");
system.AppendLine(exact.ToString(Newtonsoft.Json.Formatting.None));
system.AppendLine("```");
}
foreach (string skillId in skillIds ?? Config.ResolveEnabledSkills(pid, null))
{
string skillText = Config.LoadSkillPrompt(pid, skillId);
@@ -1564,7 +1574,7 @@ public class SwarmAssistentExtension : Extension
Logs.Debug($"Assistent memory retrieve: {ex.Message}");
}
string enrichedContext = InjectMemoryHits(contextJson, hits);
string enrichedContext = InjectMemoryHits(contextJson, hits, Config.LoadExact(pid));
List<JObject> messages = BuildOllamaMessages(packName, includeBase, enrichedContext, userMessages, personaId: pid, skillIds: skills);
JArray civitaiResults = [];
string reply = "";
@@ -1659,7 +1669,7 @@ public class SwarmAssistentExtension : Extension
return string.IsNullOrWhiteSpace(q) ? "krea2 prompting" : q;
}
static string InjectMemoryHits(string contextJson, JArray hits)
static string InjectMemoryHits(string contextJson, JArray hits, JObject exact = null)
{
JObject ctx;
try
@@ -1671,6 +1681,29 @@ public class SwarmAssistentExtension : Extension
ctx = new JObject { ["_raw_context"] = contextJson };
}
ctx["memory_hits"] = hits ?? new JArray();
if (exact is not null && exact.Count > 0 && ctx["exact"] is null)
{
ctx["exact"] = exact;
}
if (ctx["session_exact"] is null)
{
ctx["session_exact"] = new JObject();
}
if (ctx["recommended_params"] is null && exact?["generation"] is JObject gen)
{
JObject rec = new();
foreach (string key in new[] { "steps", "cfg", "sigma_shift", "aspect", "images" })
{
if (gen[key] is not null)
{
rec[key] = gen[key].DeepClone();
}
}
if (rec.Count > 0)
{
ctx["recommended_params"] = rec;
}
}
// Slim inventory for LLM: keep enabled + current, drop full dump if present
if (ctx["available_loras"] is JArray allLoras && allLoras.Count > 24)
{