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:
+28
@@ -0,0 +1,28 @@
|
||||
/** Promise wrapper around SwarmUI genericRequest. */
|
||||
export function createRequest() {
|
||||
return function request(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'))),
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function attachApi(SA) {
|
||||
SA.request = createRequest();
|
||||
}
|
||||
+9426
File diff suppressed because it is too large
Load Diff
+191
@@ -0,0 +1,191 @@
|
||||
/** Turn intent heuristics — pure functions testable with node --test. */
|
||||
|
||||
export function cyrTokenRe(alts) {
|
||||
const boundary = '(^|[^0-9A-Za-z_А-Яа-яЁё])';
|
||||
const end = '(?=$|[^0-9A-Za-z_А-Яа-яЁё])';
|
||||
return new RegExp(`${boundary}(?:${alts})${end}`, 'i');
|
||||
}
|
||||
|
||||
export function userAsksGenerate(text) {
|
||||
const t = String(text || '').trim();
|
||||
if (!t) {
|
||||
return false;
|
||||
}
|
||||
if (/^(gen|generate|go|рисуй|нарисуй)([!.…\s]|$)/i.test(t)) {
|
||||
return true;
|
||||
}
|
||||
if (/^(ещё|еще)(\s+раз)?([!.…\s]|$)/i.test(t)) {
|
||||
return true;
|
||||
}
|
||||
const letter = '[0-9A-Za-z_А-Яа-яЁё]';
|
||||
const stem = `${letter}*`;
|
||||
return cyrTokenRe(
|
||||
'сгенерируй|сгенерировать|генерируй|generate|нарисуй|перегенерируй|перерисуй|'
|
||||
+ `сделай\\s+(картинк${stem}|изображен${stem}|фото${stem})|`
|
||||
+ `хочу\\s+(картинк${stem}|изображен${stem}|фото${stem})|`
|
||||
+ 'run\\s+generat|/gen',
|
||||
).test(t);
|
||||
}
|
||||
|
||||
export function userAsksContinue(text) {
|
||||
const t = String(text || '').trim();
|
||||
if (!t) {
|
||||
return false;
|
||||
}
|
||||
if (/^(давай\s+дальше|продолжай|продолжим|go\s+on|continue|keep\s+going|next(\s+one)?|next\s+frame)([!.…\s]|$)/i.test(t)) {
|
||||
return true;
|
||||
}
|
||||
return cyrTokenRe(
|
||||
'давай\\s+дальше|следующ(ий|ая|ее|ую)\\s+кадр|ещё\\s+кадр|еще\\s+кадр|'
|
||||
+ 'кадр\\s*№?\\s*\\d+|сделай\\s+следующ',
|
||||
).test(t);
|
||||
}
|
||||
|
||||
export function isSameButAspectRequest(text) {
|
||||
const t = String(text || '').trim();
|
||||
if (!t) {
|
||||
return false;
|
||||
}
|
||||
return cyrTokenRe(
|
||||
'тот\\s+же\\s+(кадр|сцена|промпт|prompt)|так\\s+же\\s+но\\s+(друг|иной)\\s+(формат|размер|aspect|соотношен)|'
|
||||
+ 'same\\s+but\\s+(wider|taller|16:9|4:3|portrait|landscape)',
|
||||
).test(t);
|
||||
}
|
||||
|
||||
export function userAsksNoGenerate(text) {
|
||||
const t = String(text || '').trim();
|
||||
if (!t || userAsksGenerate(t)) {
|
||||
return false;
|
||||
}
|
||||
if (/\b(remember|save\s+(this\s+)?(as\s+)?(the\s+)?(base\s+)?(prompt|template)|don'?t\s+generat|do\s+not\s+generat|no\s+generat|without\s+generat)\b/i.test(t)) {
|
||||
return true;
|
||||
}
|
||||
return cyrTokenRe(
|
||||
'запомн|запомни|запомним|сохрани|сохраним|шаблон|'
|
||||
+ 'базов(ый|ого|ому|ым|ая|ую|ое)?\\s+промпт|'
|
||||
+ 'не\\s+генерир|без\\s+генерац|не\\s+надо\\s+генер|только\\s+запомн|пока\\s+запомн|'
|
||||
+ 'не\\s+рисуй|не\\s+запускай\\s+генер',
|
||||
).test(t);
|
||||
}
|
||||
|
||||
export function userCommandsGenerate(text) {
|
||||
const t = String(text || '').trim();
|
||||
if (!t || userAsksNoGenerate(t)) {
|
||||
return false;
|
||||
}
|
||||
return userAsksGenerate(t) || userAsksContinue(t) || isSameButAspectRequest(t);
|
||||
}
|
||||
|
||||
export function userAsksLook(text) {
|
||||
const t = String(text || '').trim();
|
||||
if (!t) {
|
||||
return false;
|
||||
}
|
||||
if (/\b(look\s+at|critique|criticize|describe\s+(this|the|ref|image)|what\s+do\s+you\s+see)\b/i.test(t)) {
|
||||
return true;
|
||||
}
|
||||
if (cyrTokenRe('критик[а-яё]*|что\\s+не\\s+так|разбери').test(t)) {
|
||||
return true;
|
||||
}
|
||||
if (cyrTokenRe('опиши\\s+(это|эту|реф|изображ[а-яё]*|картинк[а-яё]*|кадр|результат|референс)').test(t)) {
|
||||
return true;
|
||||
}
|
||||
return /(?:^|[^а-яёa-z0-9_])(посмотри|смотри|глянь)\s+(на\s+)?(результат|картинк[а-яё]*|изображен[а-яё]*|кадр|ген|реф)/i.test(t);
|
||||
}
|
||||
|
||||
export function userIsChatNotFrame(text) {
|
||||
const t = String(text || '').trim();
|
||||
if (!t) {
|
||||
return false;
|
||||
}
|
||||
if (/^(ок|окей|ok|okay|ладно|хорошо|понял|ясно|спасибо|thanks)([!.…\s]*)$/i.test(t)) {
|
||||
return true;
|
||||
}
|
||||
if (cyrTokenRe(
|
||||
'что\\s+такое|как\\s+работает|зачем\\s+|какие\\s+(лор|модел|чекпоинт)|'
|
||||
+ 'список\\s+лор|где\\s+настрой|что\\s+значит|'
|
||||
+ 'нравит|спасибо|благодар|почему\\s+так|что\\s+ты\\s+(сделал|изменил)|'
|
||||
+ 'только\\s+(ответь|скажи|объясни)|без\\s+(кадр|генерац)|не\\s+надо\\s+кадр',
|
||||
).test(t) && !userAsksGenerate(t) && !userAsksContinue(t)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function userImpliesGenerate(text) {
|
||||
const t = String(text || '').trim();
|
||||
if (!t || userAsksNoGenerate(t) || userIsChatNotFrame(t)) {
|
||||
return false;
|
||||
}
|
||||
if (userCommandsGenerate(t)) {
|
||||
return true;
|
||||
}
|
||||
if (t.length < 8) {
|
||||
return false;
|
||||
}
|
||||
const wantsLook = userAsksLook(t);
|
||||
const wantsRedraw = cyrTokenRe('поправь|исправь|перегенерир|перерисуй|улучши|переделай').test(t)
|
||||
|| /\b(fix|redo|redraw|improve)\b/i.test(t);
|
||||
if (wantsLook && !wantsRedraw) {
|
||||
return false;
|
||||
}
|
||||
if (cyrTokenRe(
|
||||
'нарису|сгенер|перерису|'
|
||||
+ 'сделай\\s+(картинк|изображен|фото|кадр)|'
|
||||
+ 'хочу\\s+(картинк|изображен|фото|увидеть|видеть)|'
|
||||
+ 'покажи\\s+как\\s+(она|он|это)|'
|
||||
+ 'сделай\\s+(её|ее|его|мне)\\s|'
|
||||
+ 'пусть\\s+будет|'
|
||||
+ 'другой\\s+(ракурс|свет|наряд|поза)|'
|
||||
+ 'поменяй\\s+(позу|свет|одежд|фон)|добавь\\s+(свет|детал)|'
|
||||
+ 'ещё\\s+одн|еще\\s+одн',
|
||||
).test(t)) {
|
||||
return true;
|
||||
}
|
||||
if (/\b(draw|paint|render|make her|make him|another one|new frame)\b/i.test(t)) {
|
||||
return true;
|
||||
}
|
||||
const isQuestion = /[??]\s*$/.test(t);
|
||||
if (isQuestion) {
|
||||
return cyrTokenRe('нарису|сгенер|можешь\\s+(сделать|нарисовать)|можно\\s+(картинк|сгенер)').test(t);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function packBlocksAutoGenerate(pack) {
|
||||
const p = String(pack || '');
|
||||
return p === 'describe_ref' || p === 'catalog_card' || p === 'author_persona' || p === 'debug_explain';
|
||||
}
|
||||
|
||||
export function packWantsVision(pack) {
|
||||
const p = String(pack || '');
|
||||
return p === 'critique_image' || p === 'describe_ref' || p === 'compose_scene' || p === 'inpaint_edit';
|
||||
}
|
||||
|
||||
export function resolveTurnIntent(patch, userText, opts = {}, packId = '') {
|
||||
const machine = !!opts.machineTurn;
|
||||
const vetoed = !machine && userAsksNoGenerate(userText);
|
||||
const commanded = !!opts.userWantsGenerate || (!machine && userCommandsGenerate(userText));
|
||||
const implied = !machine && userImpliesGenerate(userText);
|
||||
const modelAsked = Array.isArray(patch?.actions) && patch.actions.map(String).includes('generate');
|
||||
|
||||
let generate;
|
||||
if (vetoed || opts.fromAutoCritique) {
|
||||
generate = false;
|
||||
} else if (commanded) {
|
||||
generate = true;
|
||||
} else if (packBlocksAutoGenerate(packId)) {
|
||||
generate = false;
|
||||
} else {
|
||||
generate = modelAsked || implied;
|
||||
}
|
||||
|
||||
const hasLook = !!patch
|
||||
&& (patch.look_at != null || patch.vision_from != null || patch.vision_slots != null);
|
||||
const honorLook = opts.fromAutoCritique || opts.fromVisionHop
|
||||
|| (!machine && userAsksLook(userText))
|
||||
|| packWantsVision(packId);
|
||||
const look = !!(hasLook && !vetoed && !generate && honorLook);
|
||||
|
||||
return { generate, look, vetoed };
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { attachApi } from './api.js';
|
||||
import { attachPatch, setPatchKeys } from './patch.js';
|
||||
import { attachPersist } from './persist.js';
|
||||
|
||||
window.SA = window.SA || {};
|
||||
attachApi(window.SA);
|
||||
attachPatch(window.SA);
|
||||
attachPersist(window.SA);
|
||||
|
||||
/** Called from app after AssistentGetConfig — single source: Config/_base/patch-keys.json */
|
||||
window.SA.applyConfigPatchKeys = function (config) {
|
||||
const keys = config?.patch_keys;
|
||||
if (Array.isArray(keys) && keys.length) {
|
||||
setPatchKeys(keys);
|
||||
window.SA.PATCH_KEYS = keys;
|
||||
}
|
||||
};
|
||||
|
||||
import './app.js';
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
/** Patch detection / extraction — mirrors AssistentPatch.cs. */
|
||||
|
||||
const DEFAULT_PATCH_KEYS = [
|
||||
'prompt', 'negative', 'loras', 'width', 'height', 'steps', 'cfg', 'seed', 'sigma_shift', 'sampler', 'scheduler',
|
||||
'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',
|
||||
'memory_query', 'memory_kind', 'tag_query', 'user_prefs',
|
||||
'inventory_query', 'skills', 'persona_shelves', 'persona_clone', 'persona', 'controls',
|
||||
'variants',
|
||||
];
|
||||
|
||||
let PATCH_KEYS = DEFAULT_PATCH_KEYS.slice();
|
||||
|
||||
export function setPatchKeys(keys) {
|
||||
if (Array.isArray(keys) && keys.length) {
|
||||
PATCH_KEYS = keys.map(String);
|
||||
}
|
||||
}
|
||||
|
||||
export function getPatchKeys() {
|
||||
return PATCH_KEYS.slice();
|
||||
}
|
||||
|
||||
const FENCE_RE = /```(?:json)?\s*([\s\S]*?)```/gi;
|
||||
|
||||
function has(obj, key) {
|
||||
return obj[key] !== undefined && obj[key] !== null;
|
||||
}
|
||||
|
||||
export 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 != null || obj.cfg != null || 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));
|
||||
}
|
||||
|
||||
export function isPatchObject(obj) {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return false;
|
||||
}
|
||||
if (isCardObject(obj)) {
|
||||
return false;
|
||||
}
|
||||
return PATCH_KEYS.some((k) => has(obj, k));
|
||||
}
|
||||
|
||||
export 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;
|
||||
}
|
||||
|
||||
export 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 };
|
||||
}
|
||||
|
||||
export 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;
|
||||
}
|
||||
|
||||
export function attachPatch(SA) {
|
||||
SA.PATCH_KEYS = PATCH_KEYS;
|
||||
SA.setPatchKeys = setPatchKeys;
|
||||
SA.isCardObject = isCardObject;
|
||||
SA.isPatchObject = isPatchObject;
|
||||
SA.isTerminalStreamPatch = isTerminalStreamPatch;
|
||||
SA.normalizePatch = normalizePatch;
|
||||
SA.extractPatch = extractPatch;
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
/** Sqlite persistence for chats + UI state. */
|
||||
|
||||
const LS_CHATS = 'swarm_assistent_chats_v1';
|
||||
const SAVE_DEBOUNCE_MS = 700;
|
||||
|
||||
const timers = { chats: new Map(), ui: null };
|
||||
|
||||
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) || 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,
|
||||
};
|
||||
}
|
||||
|
||||
export function attachPersist(SA, request = SA.request) {
|
||||
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;
|
||||
}
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user