Ship Assistent 0.11.9: esbuild bundle and patch-keys manifest.

Replace split Assets JS with a built bundle and aligned C#/config so SwarmUI loads one script and patch apply stays consistent; drop legacy terse persona and memory seed.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-22 13:58:11 +03:00
co-authored by Cursor
parent a5e96f7430
commit e8bb012885
42 changed files with 10652 additions and 1281 deletions
-27
View File
@@ -1,27 +0,0 @@
/**
* Swarm Assistent — promise wrapper around SwarmUI's genericRequest.
* Loaded before assistent.js.
*/
window.SA = window.SA || {};
SA.request = function (name, body) {
return new Promise((resolve, reject) => {
if (typeof genericRequest !== 'function') {
reject(new Error('genericRequest unavailable'));
return;
}
genericRequest(
name,
body || {},
(data) => {
if (data && data.error) {
reject(new Error(String(data.error)));
} else {
resolve(data);
}
},
0,
(err) => reject(err instanceof Error ? err : new Error(String(err || 'request failed'))),
);
});
};
File diff suppressed because it is too large Load Diff
+8
View File
@@ -95,6 +95,10 @@
outline: none;
}
.sa-board:focus-visible {
box-shadow: 0 0 0 2px color-mix(in srgb, currentColor 55%, transparent);
}
.sa-board.sa-board-gen-only {
grid-template-columns: 1fr;
grid-auto-rows: 1fr;
@@ -138,6 +142,10 @@
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.sa-slot:focus-visible {
box-shadow: 0 0 0 2px color-mix(in srgb, currentColor 55%, transparent);
}
.sa-slot.sa-has-image {
border-style: solid;
}
-9644
View File
File diff suppressed because it is too large Load Diff
-145
View File
@@ -1,145 +0,0 @@
/**
* Swarm Assistent — patch detection / extraction / alias normalization.
* Loaded before assistent.js; mirrors AssistentPatch.cs on the server side.
*/
window.SA = window.SA || {};
(function () {
const PATCH_KEYS = [
'prompt', 'negative', 'loras', 'width', 'height', 'steps', 'cfg', 'seed', 'sigma_shift', 'sampler',
'actions', 'search_query', 'civitai_query',
'use_init_image', 'clear_init_image', 'init_creativity', 'denoise',
'use_mask_image', 'clear_mask_image', 'mask_blur', 'mask_grow',
'look_at', 'vision_from', 'vision_slots', 'slot_to_init', 'slot_to_mask',
'snapshot_generate', 'select_slot', 'aspect', 'images', 'batch', 'vary', 'lock_seed',
'creativity', 'intensity', 'complexity', 'movement',
'clear_prompt_images', 'slot_to_prompt_image', 'pack',
'memories', 'memory_query', 'memory_kind', 'tag_query', 'user_prefs',
'controls', 'persona_clone', 'persona_shelves', 'persona', 'notes',
'variants',
];
const FENCE_RE = /```(?:json)?\s*([\s\S]*?)```/gi;
function has(obj, key) {
return obj[key] !== undefined && obj[key] !== null;
}
/** Model-card JSON (catalog_card) — must not be treated as a Generate patch. */
function isCardObject(obj) {
if (!obj || typeof obj !== 'object') {
return false;
}
const cardish = !!(obj.kind || obj.triggers || obj.when || obj.prompt_hint);
const genish = !!(obj.prompt != null || obj.negative != null || obj.loras || obj.actions
|| obj.width || obj.height || obj.steps || obj.cfg || obj.aspect || obj.seed != null
|| obj.search_query || obj.civitai_query || obj.look_at || obj.controls);
if (cardish && !genish && (obj.name || obj.triggers || obj.when)) {
return true;
}
return !!(obj.kind && obj.name && (obj.triggers || obj.when || obj.prompt_hint || obj.notes != null));
}
/** True when the object looks like a generation patch rather than a catalog card / arbitrary JSON. */
function isPatchObject(obj) {
if (!obj || typeof obj !== 'object') {
return false;
}
if (isCardObject(obj)) {
return false;
}
return PATCH_KEYS.some((k) => has(obj, k));
}
/** Maps alias fields onto canonical names, keeping the aliases in place. */
function normalizePatch(patch) {
if (!patch || typeof patch !== 'object') {
return patch;
}
if (!has(patch, 'search_query') && has(patch, 'civitai_query')) {
patch.search_query = patch.civitai_query;
}
if (!has(patch, 'init_creativity') && has(patch, 'denoise')) {
patch.init_creativity = patch.denoise;
}
if (!has(patch, 'look_at')) {
if (has(patch, 'vision_from')) {
patch.look_at = patch.vision_from;
} else if (has(patch, 'vision_slots')) {
patch.look_at = patch.vision_slots;
}
}
return patch;
}
/** Splits a reply into prose and the last fenced patch object found in it. */
function extractPatch(text) {
if (!text) {
return { prose: text || '', patch: null };
}
const re = new RegExp(FENCE_RE.source, 'gi');
let match;
let lastPatch = null;
let prose = text;
while ((match = re.exec(text)) !== null) {
try {
const obj = JSON.parse(match[1].trim());
if (isPatchObject(obj)) {
lastPatch = normalizePatch(obj);
prose = (text.slice(0, match.index) + text.slice(match.index + match[0].length)).trim();
}
} catch (e) { /* not json */ }
}
return { prose, patch: lastPatch };
}
/**
* Closed fence worth freezing the stream / stopping Ollama early.
* Weak fences (pack / creativity / empty) must NOT stop — model often continues with the real patch.
*/
function isTerminalStreamPatch(obj) {
if (!obj || typeof obj !== 'object') {
return false;
}
if (isCardObject(obj)) {
return true;
}
if (Array.isArray(obj.variants) && obj.variants.length) {
return true;
}
if (obj.look_at != null || obj.vision_from != null || obj.vision_slots != null) {
return true;
}
if (obj.search_query != null || obj.civitai_query != null
|| obj.memory_query != null || obj.tag_query != null || obj.inventory_query != null) {
return true;
}
const acts = Array.isArray(obj.actions) ? obj.actions.map(String) : [];
const hopOrGen = [
'skill_load', 'persona_read', 'memory_get', 'memory_search', 'lookup_tags',
'list_inventory', 'search_civitai', 'interrupt', 'generate',
'memory_upsert', 'user_pref_upsert',
];
if (acts.some((a) => hopOrGen.includes(a))) {
return true;
}
if (String(obj.prompt || '').trim().length >= 48) {
return true;
}
if (obj.loras != null || obj.aspect != null || obj.steps != null
|| obj.width != null || obj.height != null || obj.cfg != null
|| obj.seed != null || obj.controls != null
|| obj.memories != null || obj.user_prefs != null) {
return true;
}
return false;
}
SA.PATCH_KEYS = PATCH_KEYS;
SA.isCardObject = isCardObject;
SA.isPatchObject = isPatchObject;
SA.isTerminalStreamPatch = isTerminalStreamPatch;
SA.normalizePatch = normalizePatch;
SA.extractPatch = extractPatch;
})();
-202
View File
@@ -1,202 +0,0 @@
/**
* Swarm Assistent — sqlite persistence for chats + UI state (Assistent/memory/assistent.sqlite).
* Loaded after assistent.api.js and before assistent.js.
*/
window.SA = window.SA || {};
(function () {
const LS_CHATS = 'swarm_assistent_chats_v1';
const LS_MIGRATED = 'swarm_assistent_chats_on_disk_v1';
const SAVE_DEBOUNCE_MS = 700;
const timers = { chats: new Map(), ui: null };
function request(name, body) {
if (typeof SA.request === 'function') {
return SA.request(name, body);
}
return new Promise((resolve, reject) => {
if (typeof genericRequest !== 'function') {
reject(new Error('genericRequest unavailable'));
return;
}
genericRequest(
name,
body || {},
(data) => (data && data.error ? reject(new Error(String(data.error))) : resolve(data)),
0,
(err) => reject(err instanceof Error ? err : new Error(String(err || 'request failed'))),
);
});
}
function normalizeChat(raw) {
if (!raw || !raw.id) {
return null;
}
return {
id: String(raw.id),
title: String(raw.title || 'Новый чат'),
createdAt: Number(raw.createdAt) || Date.now(),
updatedAt: Number(raw.updatedAt) || Number(raw.createdAt) || Date.now(),
messages: Array.isArray(raw.messages) ? raw.messages : [],
messages_count: Number(raw.messages_count) || (Array.isArray(raw.messages) ? raw.messages.length : 0),
params: raw.params && typeof raw.params === 'object' ? raw.params : null,
};
}
function readLocalChats() {
try {
const parsed = JSON.parse(localStorage.getItem(LS_CHATS) || 'null');
if (Array.isArray(parsed?.chats)) {
return parsed.chats.map(normalizeChat).filter(Boolean);
}
} catch (e) { /* ignore */ }
return [];
}
/** One-shot lift of the browser-only history onto the data volume. */
async function migrateLocalChatsToDisk() {
if (localStorage.getItem(LS_MIGRATED) === '1') {
return [];
}
const local = readLocalChats().filter((c) => (c.messages || []).length > 0);
localStorage.setItem(LS_MIGRATED, '1');
if (!local.length) {
return [];
}
for (const chat of local) {
try {
await saveChat(chat, { immediate: true });
} catch (e) {
console.warn('Assistent: chat migration failed', chat.id, e);
}
}
return local;
}
/** Sqlite chats, newest first. Falls back to a localStorage migration when the store is empty. */
async function loadChats() {
let chats = [];
try {
const data = await request('AssistentListChats', { with_messages: true });
chats = (data?.chats || []).map(normalizeChat).filter(Boolean);
} catch (e) {
console.warn('Assistent: disk chats unavailable', e);
return null;
}
if (!chats.length) {
const migrated = await migrateLocalChatsToDisk();
if (migrated.length) {
chats = migrated;
}
} else {
localStorage.setItem(LS_MIGRATED, '1');
}
return chats.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
}
async function getChat(id) {
if (!id) {
return null;
}
const data = await request('AssistentGetChat', { id });
return normalizeChat(data?.chat);
}
async function searchChats(q) {
const query = String(q || '').trim();
if (query.length < 2) {
return [];
}
const data = await request('AssistentListChats', { q: query, with_messages: false, limit: 40 });
return (data?.chats || []).map(normalizeChat).filter(Boolean);
}
function saveChat(chat, { immediate = false } = {}) {
const clean = normalizeChat(chat);
if (!clean) {
return Promise.resolve(null);
}
const send = () => {
timers.chats.delete(clean.id);
return request('AssistentSaveChat', {
id: clean.id,
title: clean.title,
messages: clean.messages,
params: clean.params,
createdAt: clean.createdAt,
updatedAt: clean.updatedAt,
});
};
if (immediate) {
const pending = timers.chats.get(clean.id);
if (pending) {
clearTimeout(pending);
}
return send();
}
const pending = timers.chats.get(clean.id);
if (pending) {
clearTimeout(pending);
}
timers.chats.set(clean.id, setTimeout(() => {
send().catch((e) => console.warn('Assistent: chat save failed', clean.id, e));
}, SAVE_DEBOUNCE_MS));
return Promise.resolve(null);
}
function deleteChat(id) {
if (!id) {
return Promise.resolve(null);
}
const pending = timers.chats.get(id);
if (pending) {
clearTimeout(pending);
timers.chats.delete(id);
}
return request('AssistentDeleteChat', { id });
}
async function loadUiState() {
try {
const data = await request('AssistentGetUiState', {});
const ui = data?.ui_state;
return ui && typeof ui === 'object' ? ui : null;
} catch (e) {
return null;
}
}
function saveUiState(uiState, { immediate = false } = {}) {
if (!uiState || typeof uiState !== 'object') {
return Promise.resolve(null);
}
const send = () => {
timers.ui = null;
return request('AssistentSaveUiState', { ui_state: uiState });
};
if (timers.ui) {
clearTimeout(timers.ui);
timers.ui = null;
}
if (immediate) {
return send();
}
timers.ui = setTimeout(() => {
send().catch((e) => console.warn('Assistent: ui-state save failed', e));
}, SAVE_DEBOUNCE_MS);
return Promise.resolve(null);
}
SA.persist = {
LS_CHATS,
loadChats,
getChat,
searchChats,
saveChat,
deleteChat,
loadUiState,
saveUiState,
};
})();