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
+279 -32
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 (aspectSize) {
setVal('input_width', String(aspectSize[0]));
setVal('input_height', String(aspectSize[1]));
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 });
}
} else if (patch.width == null && isEmptyParamField(val('input_width'), { treatZeroEmpty: true }) && defaults.width != null) {
setVal('input_width', String(defaults.width));
}
if (patch.height != null) {
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 });
}
} else if (patch.steps == null && isEmptyParamField(val('input_steps'), { treatZeroEmpty: true }) && defaults.steps != null) {
setVal('input_steps', String(defaults.steps));
}
if (patch.cfg != null) {
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 (Object.keys(next).length) {
ASPECT_TABLE = next;
}
if (data.exact && typeof data.exact === 'object') {
state.exact = data.exact;
}
if (data.model?.profiles) {
state.kreaProfiles = data.model.profiles;
const aspectSource = data.exact?.aspect_table || data.model?.aspect_table;
if (aspectSource && typeof aspectSource === 'object') {
applyAspectTableFrom(aspectSource);
}
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();
});