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:
+279
-32
@@ -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();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user