Files
swarm-assistent/Assets/assistent.bundle.js
T
Leonid PershinandCursor 9ac24a828b Ship Assistent 0.14.0: chat sessions, ask-only hops, and context compression.
Per-chat Generate session with sparse deltas; drop Cards/Civitai/wanted hops; rolling history summary via the same Ollama model with a budget chip and /compress.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-23 06:19:04 +03:00

10082 lines
395 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(() => {
// src/api.js
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")))
);
});
};
}
function attachApi(SA2) {
SA2.request = createRequest();
}
// src/patch.js
var DEFAULT_PATCH_KEYS = [
"prompt",
"negative",
"loras",
"width",
"height",
"steps",
"cfg",
"seed",
"sigma_shift",
"sampler",
"scheduler",
"actions",
"generate",
"ask",
"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",
"persona",
"controls",
"inventory_query",
"variants"
];
var PATCH_KEYS = DEFAULT_PATCH_KEYS.slice();
function setPatchKeys(keys) {
if (Array.isArray(keys) && keys.length) {
PATCH_KEYS = keys.map(String);
}
}
var FENCE_RE = /```(?:json)?\s*([\s\S]*?)```/gi;
function has(obj, key) {
return obj[key] !== void 0 && obj[key] !== null;
}
function isPatchObject2(obj) {
if (!obj || typeof obj !== "object") {
return false;
}
return PATCH_KEYS.some((k) => has(obj, k));
}
function normalizePatch(patch) {
if (!patch || typeof patch !== "object") {
return patch;
}
const acts = Array.isArray(patch.actions) ? patch.actions.map(String) : [];
if (patch.generate === true || acts.includes("generate")) {
patch.generate = true;
}
if (typeof patch.ask === "string") {
patch.ask = [patch.ask];
}
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;
}
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 (isPatchObject2(obj)) {
lastPatch = normalizePatch(obj);
prose = (text.slice(0, match.index) + text.slice(match.index + match[0].length)).trim();
}
} catch (e) {
}
}
return { prose, patch: lastPatch };
}
function isTerminalStreamPatch(obj) {
if (!obj || typeof obj !== "object") {
return false;
}
if (obj.generate === true) {
return true;
}
if (Array.isArray(obj.ask) && obj.ask.length) {
return true;
}
if (typeof obj.ask === "string" && obj.ask) {
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;
}
const acts = Array.isArray(obj.actions) ? obj.actions.map(String) : [];
if (acts.includes("generate")) {
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) {
return true;
}
return false;
}
function attachPatch(SA2) {
SA2.PATCH_KEYS = PATCH_KEYS;
SA2.setPatchKeys = setPatchKeys;
SA2.isPatchObject = isPatchObject2;
SA2.isTerminalStreamPatch = isTerminalStreamPatch;
SA2.normalizePatch = normalizePatch;
SA2.extractPatch = extractPatch;
}
// src/persist.js
var LS_CHATS = "swarm_assistent_chats_v1";
var SAVE_DEBOUNCE_MS = 700;
var timers = { chats: /* @__PURE__ */ new Map(), ui: null };
function normalizeChat(raw) {
if (!raw || !raw.id) {
return null;
}
return {
id: String(raw.id),
title: String(raw.title || "\u041D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442"),
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
};
}
function attachPersist(SA2, request = SA2.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 pending2 = timers.chats.get(clean.id);
if (pending2) {
clearTimeout(pending2);
}
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);
}
SA2.persist = {
LS_CHATS,
loadChats,
getChat,
searchChats,
saveChat,
deleteChat,
loadUiState,
saveUiState
};
}
// src/session.js
var MAX_DATA_URL_CHARS = 35e4;
var GEN_KEYS = [
"prompt",
"negative",
"width",
"height",
"aspect",
"steps",
"cfg",
"sigma_shift",
"seed",
"sampler",
"scheduler",
"batch",
"checkpoint",
"loras",
"controls",
"use_init_image",
"clear_init_image",
"init_creativity",
"denoise",
"use_mask_image",
"clear_mask_image",
"mask_blur",
"mask_grow"
];
function emptySession() {
return {
gen: {
prompt: "",
negative: "",
width: null,
height: null,
aspect: null,
steps: null,
cfg: null,
sigma_shift: null,
seed: null,
sampler: null,
scheduler: null,
batch: null,
checkpoint: null,
loras: [],
controls: {},
use_init_image: false,
clear_init_image: false,
init_creativity: null,
denoise: null,
use_mask_image: false,
clear_mask_image: false,
mask_blur: null,
mask_grow: null
},
board: {
slots: [],
selectedSlotId: "ref1",
genResults: [],
selectedGenResultId: null,
refSeq: 1
},
persona: "neutral",
pack: "ordinary",
context_memory: null
};
}
function normalizeDelta(raw) {
if (!raw || typeof raw !== "object") {
return null;
}
const delta = { ...raw };
const acts = Array.isArray(delta.actions) ? delta.actions.map(String) : [];
if (delta.generate === true || acts.includes("generate")) {
delta.generate = true;
}
if (typeof delta.ask === "string") {
delta.ask = [delta.ask];
}
if (!Array.isArray(delta.ask)) {
delete delta.ask;
} else {
delta.ask = delta.ask.map(String).filter(Boolean);
}
return delta;
}
function patchWantsGenerate(patch) {
if (!patch || typeof patch !== "object") {
return false;
}
if (patch.generate === true) {
return true;
}
const acts = Array.isArray(patch.actions) ? patch.actions.map(String) : [];
return acts.includes("generate");
}
function patchAskList(patch) {
const n = normalizeDelta(patch);
return Array.isArray(n?.ask) ? n.ask : [];
}
function mergeDelta(session, rawDelta) {
const base = session && typeof session === "object" ? structuredCloneSession(session) : emptySession();
const delta = normalizeDelta(rawDelta);
if (!delta) {
return base;
}
if (!base.gen) {
base.gen = emptySession().gen;
}
for (const key of GEN_KEYS) {
if (delta[key] === void 0 || delta[key] === null) {
continue;
}
if (key === "loras" && Array.isArray(delta.loras)) {
base.gen.loras = delta.loras.map((l) => ({
name: l?.name || l,
weight: l?.weight != null ? Number(l.weight) : 1,
triggers: Array.isArray(l?.triggers) ? l.triggers : void 0,
trigger_phrase: l?.trigger_phrase || void 0
})).filter((l) => l.name);
continue;
}
if (key === "controls" && typeof delta.controls === "object") {
base.gen.controls = { ...base.gen.controls || {}, ...delta.controls };
continue;
}
if (key === "checkpoint") {
base.gen.checkpoint = typeof delta.checkpoint === "object" ? { ...delta.checkpoint } : { name: String(delta.checkpoint) };
continue;
}
base.gen[key] = delta[key];
}
if (delta.images != null && delta.batch == null) {
base.gen.batch = delta.images;
}
if (delta.pack) {
base.pack = String(delta.pack);
}
if (delta.persona) {
base.persona = String(delta.persona);
}
return base;
}
function structuredCloneSession(session) {
try {
return JSON.parse(JSON.stringify(session));
} catch {
return emptySession();
}
}
function slimSrc(src) {
if (!src || typeof src !== "string") {
return null;
}
const s = src.trim();
if (!s || s.startsWith("#")) {
return null;
}
if (s.startsWith("data:") && s.length > MAX_DATA_URL_CHARS) {
return null;
}
return s;
}
function snapshotFromLive({
genFields,
board,
persona,
pack,
context_memory
}) {
const session = emptySession();
if (genFields && typeof genFields === "object") {
for (const key of GEN_KEYS) {
if (genFields[key] !== void 0) {
session.gen[key] = genFields[key];
}
}
}
session.persona = persona || "neutral";
session.pack = pack || "ordinary";
if (context_memory && typeof context_memory === "object") {
session.context_memory = context_memory;
}
if (board && typeof board === "object") {
session.board = {
slots: (board.slots || []).map((s) => ({
id: s.id,
type: s.type,
label: s.label,
src: slimSrc(s.src),
attach: !!s.attach,
note: s.note || null
})),
selectedSlotId: board.selectedSlotId || "ref1",
genResults: (board.genResults || []).map((r) => ({
id: r.id,
label: r.label,
src: slimSrc(r.src),
patch: r.patch || null
})),
selectedGenResultId: board.selectedGenResultId || null,
refSeq: board.refSeq || 1
};
}
return session;
}
function sessionFromLegacyParams(params) {
if (!params || typeof params !== "object") {
return emptySession();
}
if (params.gen && typeof params.gen === "object") {
const s2 = emptySession();
s2.gen = { ...s2.gen, ...params.gen };
if (params.board && typeof params.board === "object") {
s2.board = { ...s2.board, ...params.board };
}
s2.persona = params.persona || s2.persona;
s2.pack = params.pack || s2.pack;
if (params.context_memory && typeof params.context_memory === "object") {
s2.context_memory = params.context_memory;
}
return s2;
}
const s = emptySession();
for (const key of GEN_KEYS) {
if (params[key] !== void 0 && params[key] !== null) {
s.gen[key] = params[key];
}
}
if (Array.isArray(params.loras)) {
s.gen.loras = params.loras;
}
if (params.checkpoint) {
s.gen.checkpoint = typeof params.checkpoint === "object" ? params.checkpoint : { name: String(params.checkpoint) };
}
s.persona = params.persona || "neutral";
s.pack = params.pack || "ordinary";
if (params.context_memory && typeof params.context_memory === "object") {
s.context_memory = params.context_memory;
}
s.board.genResults = Array.isArray(params.genResults) ? params.genResults : [];
s.board.selectedGenResultId = params.selectedGenResultId || null;
if (Array.isArray(params.slots)) {
s.board.slots = params.slots;
}
if (params.selectedSlotId) {
s.board.selectedSlotId = params.selectedSlotId;
}
if (params.refSeq) {
s.board.refSeq = params.refSeq;
}
return s;
}
function toPersistParams(session) {
const s = session && typeof session === "object" ? session : emptySession();
const out = {
gen: s.gen || emptySession().gen,
board: s.board || emptySession().board,
persona: s.persona || "neutral",
pack: s.pack || "ordinary"
};
if (s.context_memory && typeof s.context_memory === "object") {
out.context_memory = s.context_memory;
}
return out;
}
function slimText(t, max) {
const s = String(t || "");
if (s.length <= max) {
return s;
}
return `${s.slice(0, max)}\u2026`;
}
function compactContext(session, extras = {}) {
const s = session && typeof session === "object" ? session : emptySession();
const g = s.gen || {};
const board = s.board || {};
const slots = board.slots || [];
const genSlot = slots.find((x) => x.type === "generate" || x.id === "generate");
const refs = slots.filter((x) => x.type === "ref" || String(x.id || "").startsWith("ref"));
return {
session: true,
prompt: slimText(g.prompt, extras.promptMax || 2e3),
negative: slimText(g.negative, 500),
aspect: g.aspect || null,
width: g.width ?? null,
height: g.height ?? null,
steps: g.steps ?? null,
cfg: g.cfg ?? null,
seed: g.seed ?? null,
sigma_shift: g.sigma_shift ?? null,
sampler: g.sampler || null,
scheduler: g.scheduler || null,
batch: g.batch ?? null,
checkpoint: g.checkpoint?.name || g.checkpoint || null,
selected_loras: (g.loras || []).map((l) => ({
name: l.name || l,
weight: l.weight != null ? l.weight : 1
})),
persona: s.persona || "neutral",
pack: s.pack || "ordinary",
board: {
has_generate: !!(genSlot?.src || (board.genResults || []).some((r) => r.src)),
refs: refs.map((r) => ({ id: r.id, has_image: !!r.src, attach: !!r.attach })),
gen_results: (board.genResults || []).map((r) => ({
id: r.id,
label: r.label,
has_image: !!r.src,
selected: r.id === board.selectedGenResultId
})),
selected_slot: board.selectedSlotId || null
},
architecture_ok: extras.architecture_ok !== false,
...extras.extra
};
}
function fullSettingsDump(session, extras = {}) {
const compact = compactContext(session, extras);
const s = session && typeof session === "object" ? session : emptySession();
return {
...compact,
detail: "settings",
gen: { ...s.gen || {} },
controls: s.gen?.controls || {},
exact: extras.exact || null,
krea_profiles: extras.kreaProfiles || null,
session_exact: extras.sessionExact || null
};
}
function resolveTurnIntent(patch, userText, { vetoFn } = {}) {
const delta = normalizeDelta(patch) || {};
const vetoed = typeof vetoFn === "function" ? !!vetoFn(userText) : false;
const generate = !vetoed && patchWantsGenerate(delta);
const hasLook = delta.look_at != null || delta.vision_from != null || delta.vision_slots != null;
const look = !!(hasLook && !generate && !vetoed);
const ask = patchAskList(delta);
return { generate, look, vetoed, ask };
}
function attachSession(SA2) {
SA2.session = {
emptySession,
normalizeDelta,
mergeDelta,
patchWantsGenerate,
patchAskList,
snapshotFromLive,
sessionFromLegacyParams,
toPersistParams,
compactContext,
fullSettingsDump,
resolveTurnIntent,
GEN_KEYS
};
}
// src/context.js
function emptyContextMemory() {
return {
summary: "",
untilCount: 0,
foldedTurns: 0,
at: 0,
uiCollapsed: false,
promptEvalCount: null
};
}
function normalizeContextMemory(raw) {
if (!raw || typeof raw !== "object") {
return emptyContextMemory();
}
const summary = String(raw.summary || "").trim();
return {
summary,
untilCount: Math.max(0, Number(raw.untilCount) || 0),
foldedTurns: Math.max(0, Number(raw.foldedTurns) || 0),
at: Number(raw.at) || 0,
uiCollapsed: !!raw.uiCollapsed && !!summary,
promptEvalCount: raw.promptEvalCount != null ? Number(raw.promptEvalCount) || null : null
};
}
function charsToTokens(chars, charsPerToken = 3.2) {
const cpt = Math.max(1.5, Number(charsPerToken) || 3.2);
const n = Math.max(0, Number(chars) || 0);
return Math.ceil(n / cpt);
}
function estimateBudget(opts = {}) {
const numCtx = Math.max(1024, Number(opts.numCtx) || 16384);
const numPredict = Math.max(256, Number(opts.numPredict) || 3072);
const charsPerToken = Math.max(1.5, Number(opts.charsPerToken) || 3.2);
const compressAt = Math.min(0.95, Math.max(0.4, Number(opts.compressAt) || 0.7));
const systemChars = Math.max(0, Number(opts.systemChars) || 0);
const historyChars = Math.max(0, Number(opts.historyChars) || 0);
const memoryChars = Math.max(0, Number(opts.memoryChars) || 0);
const inputChars = systemChars + historyChars + memoryChars;
const estimated = charsToTokens(inputChars, charsPerToken);
const used = opts.promptEvalCount != null && Number(opts.promptEvalCount) > 0 ? Number(opts.promptEvalCount) : estimated;
const headroom = Math.max(1024, numCtx - numPredict);
const threshold = Math.floor(headroom * compressAt);
const ratio = numCtx > 0 ? used / numCtx : 0;
let level = "ok";
if (ratio >= 0.85 || used >= threshold) {
level = "hot";
} else if (ratio >= 0.65 || used >= threshold * 0.85) {
level = "warn";
}
return {
numCtx,
numPredict,
headroom,
threshold,
systemChars,
historyChars,
memoryChars,
inputChars,
estimated,
used,
fromEval: opts.promptEvalCount != null && Number(opts.promptEvalCount) > 0,
ratio,
level,
charsPerToken,
compressAt
};
}
function shouldCompress(budget, memory, historyLen, opts = {}) {
const keep = Math.max(2, Number(opts.keepMessages) || 8);
const len = Math.max(0, Number(historyLen) || 0);
const until = Math.max(0, Number(memory?.untilCount) || 0);
const uncovered = Math.max(0, len - until);
if (uncovered <= keep) {
return false;
}
const used = budget?.used ?? 0;
const threshold = budget?.threshold ?? Infinity;
return used >= threshold;
}
function assembleModelMessages(history, memory, keepTurns) {
const keep = Math.max(1, Number(keepTurns) || 4) * 2;
const until = Math.max(0, Number(memory?.untilCount) || 0);
const list = (history || []).filter((m) => m && (m.role === "user" || m.role === "assistant") && !m.systemish);
const afterSummary = until > 0 ? list.slice(until) : list;
const window2 = afterSummary.length > keep ? afterSummary.slice(-keep) : afterSummary;
return window2.map((m) => ({
role: m.role,
content: String(m.content || "").slice(0, 4e3)
}));
}
function messagesToFold(history, memory, keepTurns) {
const keep = Math.max(1, Number(keepTurns) || 4) * 2;
const list = (history || []).filter((m) => m && (m.role === "user" || m.role === "assistant") && !m.systemish);
const until = Math.max(0, Number(memory?.untilCount) || 0);
const foldEnd = Math.max(until, list.length - keep);
if (foldEnd <= until) {
return [];
}
return list.slice(until, foldEnd);
}
function mergeSummary(oldSummary, incoming) {
const next = String(incoming || "").trim();
if (!next) {
return String(oldSummary || "").trim();
}
const prev = String(oldSummary || "").trim();
if (!prev) {
return next;
}
return next;
}
function formatTokenShort(n) {
const v = Math.max(0, Number(n) || 0);
if (v >= 1e4) {
return `${(v / 1e3).toFixed(1)}k`;
}
if (v >= 1e3) {
return `${(v / 1e3).toFixed(1)}k`;
}
return String(Math.round(v));
}
function conversationMemoryBlock(memory, maxChars = 2400) {
const m = normalizeContextMemory(memory);
if (!m.summary) {
return null;
}
let text = m.summary;
if (text.length > maxChars) {
text = `${text.slice(0, maxChars)}\u2026`;
}
return {
summary: text,
until_count: m.untilCount,
folded_turns: m.foldedTurns
};
}
function attachContext(SA2) {
SA2.context = {
emptyContextMemory,
normalizeContextMemory,
charsToTokens,
estimateBudget,
shouldCompress,
assembleModelMessages,
messagesToFold,
mergeSummary,
formatTokenShort,
conversationMemoryBlock
};
}
// src/activity.js
var STEP_ICONS = {
think: "\u25C7",
stream: "\u270E",
delta: "\u21E2",
ask: "?",
look: "\u25CE",
prep: "\u21BB",
generate: "\u25B7",
merge: "\u2295",
warm: "\u25B2",
park: "\u25BC",
inventory: "\u25A4",
compress: "\u25A4",
done: "\u2713",
skip: "\u2013",
error: "!"
};
function createActivityController(opts = {}) {
const {
getMessagesEl,
scrollToBottom,
hideEmpty
} = opts;
let card2 = null;
let listEl = null;
let titleEl = null;
let steps = [];
let open = true;
function ensureCard() {
const box = typeof getMessagesEl === "function" ? getMessagesEl() : null;
if (!box) {
return null;
}
if (card2 && card2.isConnected) {
return card2;
}
if (typeof hideEmpty === "function") {
hideEmpty();
}
card2 = document.createElement("div");
card2.className = "sa-activity sa-activity-live";
card2.setAttribute("role", "status");
card2.setAttribute("aria-live", "polite");
const head = document.createElement("button");
head.type = "button";
head.className = "sa-activity-head";
head.setAttribute("aria-expanded", "true");
const spin = document.createElement("span");
spin.className = "sa-activity-spin";
spin.setAttribute("aria-hidden", "true");
titleEl = document.createElement("span");
titleEl.className = "sa-activity-title";
titleEl.textContent = "Assistent";
const chev = document.createElement("span");
chev.className = "sa-activity-chev";
chev.setAttribute("aria-hidden", "true");
chev.textContent = "\u25BE";
head.appendChild(spin);
head.appendChild(titleEl);
head.appendChild(chev);
head.addEventListener("click", () => {
open = !open;
card2.classList.toggle("sa-activity-collapsed", !open);
head.setAttribute("aria-expanded", open ? "true" : "false");
});
listEl = document.createElement("div");
listEl.className = "sa-activity-steps";
card2.appendChild(head);
card2.appendChild(listEl);
box.appendChild(card2);
if (typeof scrollToBottom === "function") {
scrollToBottom();
}
return card2;
}
function renderStep(step) {
const row = document.createElement("div");
row.className = `sa-activity-step sa-activity-${step.status || "running"}`;
row.dataset.id = step.id;
const icon = document.createElement("span");
icon.className = "sa-activity-icon";
icon.setAttribute("aria-hidden", "true");
icon.textContent = STEP_ICONS[step.kind] || STEP_ICONS.think;
const body = document.createElement("div");
body.className = "sa-activity-body";
const label = document.createElement("div");
label.className = "sa-activity-label";
label.textContent = step.label || step.id;
body.appendChild(label);
if (step.detail) {
const detail = document.createElement("div");
detail.className = "sa-activity-detail";
detail.textContent = step.detail;
body.appendChild(detail);
}
row.appendChild(icon);
row.appendChild(body);
return row;
}
function paint() {
if (!ensureCard() || !listEl) {
return;
}
listEl.replaceChildren(...steps.map(renderStep));
const running = steps.find((s) => s.status === "running");
const last = steps[steps.length - 1];
if (titleEl) {
titleEl.textContent = running ? running.label : last?.label || "Assistent";
}
card2.classList.toggle("sa-activity-live", steps.some((s) => s.status === "running"));
card2.classList.toggle("sa-activity-done", steps.length > 0 && steps.every((s) => s.status === "done" || s.status === "skip"));
if (typeof scrollToBottom === "function") {
scrollToBottom();
}
}
function begin(title) {
steps = [];
card2 = null;
listEl = null;
titleEl = null;
open = true;
ensureCard();
if (titleEl && title) {
titleEl.textContent = title;
}
paint();
}
function upsert(id, patch) {
ensureCard();
let step = steps.find((s) => s.id === id);
if (!step) {
step = { id, kind: "think", label: id, status: "running", detail: "" };
steps.push(step);
}
Object.assign(step, patch);
if (!step.status) {
step.status = "running";
}
paint();
return step;
}
function done(id, patch = {}) {
return upsert(id, { ...patch, status: "done" });
}
function skip(id, patch = {}) {
return upsert(id, { ...patch, status: "skip" });
}
function fail(id, patch = {}) {
return upsert(id, { ...patch, status: "error" });
}
function finish(summary) {
steps.forEach((s) => {
if (s.status === "running") {
s.status = "done";
}
});
if (summary && titleEl) {
titleEl.textContent = summary;
}
paint();
if (card2) {
card2.classList.remove("sa-activity-live");
card2.classList.add("sa-activity-done");
}
}
function noteModelCommands(patch) {
if (!patch || typeof patch !== "object") {
return;
}
const keys = Object.keys(patch).filter((k) => patch[k] != null && !["actions", "generate", "ask", "look_at", "vision_from", "vision_slots", "notes", "variants"].includes(k));
if (keys.length) {
done("delta", {
kind: "delta",
label: "\u041E\u0431\u043D\u043E\u0432\u0438\u043B \u0441\u0435\u0441\u0441\u0438\u044E",
detail: keys.slice(0, 10).join(", ")
});
}
const ask = Array.isArray(patch.ask) ? patch.ask.map(String) : patch.ask ? [String(patch.ask)] : [];
if (ask.length) {
upsert("ask", {
kind: "ask",
label: `\u0417\u0430\u043F\u0440\u043E\u0441\u0438\u043B ${ask.join(", ")}`,
detail: "\u043F\u043E\u0434\u0433\u0440\u0443\u0436\u0430\u044E \u0434\u0435\u0442\u0430\u043B\u0438\u2026",
status: "running"
});
}
if (patch.look_at != null || patch.vision_from != null || patch.vision_slots != null) {
const slots = [].concat(patch.look_at || patch.vision_from || patch.vision_slots || []);
upsert("look", {
kind: "look",
label: "\u0421\u043C\u043E\u0442\u0440\u0438\u0442 \u043D\u0430 \u043A\u0430\u0434\u0440",
detail: slots.map(String).slice(0, 4).join(", "),
status: "running"
});
}
if (patch.generate === true || Array.isArray(patch.actions) && patch.actions.map(String).includes("generate")) {
upsert("generate", {
kind: "generate",
label: "Generate",
detail: "\u0436\u0434\u0451\u0442 \u043F\u0430\u0439\u043F\u043B\u0430\u0439\u043D\u2026",
status: "running"
});
}
if (Array.isArray(patch.variants) && patch.variants.length) {
upsert("variants", {
kind: "generate",
label: `\u0412\u0430\u0440\u0438\u0430\u043D\u0442\u044B \xD7${patch.variants.length}`,
status: "running"
});
}
}
return {
begin,
upsert,
done,
skip,
fail,
finish,
noteModelCommands,
get steps() {
return steps.slice();
}
};
}
function attachActivity(SA2) {
SA2.createActivityController = createActivityController;
}
// src/app.js
(function() {
const LS_BASE = "swarm_assistent_base_url";
const LS_MODEL = "swarm_assistent_model";
const LS_EMBED = "swarm_assistent_embed_model";
const LS_PACK = "swarm_assistent_pack";
const LS_PERSONA = "swarm_assistent_persona";
const LS_VIEW = "swarm_assistent_view";
const LS_AUTO_VISION = "swarm_assistent_auto_vision";
const LS_AUTO_APPLY = "swarm_assistent_auto_apply";
const LS_AUTO_GENERATE = "swarm_assistent_auto_generate";
const LS_AUTO_CRITIQUE = "swarm_assistent_auto_critique";
const LS_AUTO_DOWNLOAD = "swarm_assistent_auto_download";
const LS_PARK_LLM = "swarm_assistent_park_llm";
const LS_PANE_WIDTH = "swarm_assistent_pane_width";
const LS_WELCOMED = "swarm_assistent_welcomed";
const LS_CHATS2 = "swarm_assistent_chats_v1";
const LS_BOARD_TAB = "swarm_assistent_board_tab";
const LS_CHATS_DRAWER = "swarm_assistent_chats_drawer";
const MAX_CHATS = 40;
const MAX_CHAT_MSGS = 24;
const TAB_BUTTON_ID = "maintab_assistent";
const GEN_ID = "generate";
let MAX_REF_SLOTS = 4;
let MAX_GEN_VARIANTS = 4;
let CONTEXT_PROMPT_MAX = 2e3;
let HISTORY_KEEP_TURNS = 4;
let INVENTORY_PROMPT_RICH = 12;
let INVENTORY_PROMPT_NAMES = 24;
let COMPRESS_AT = 0.7;
let CHARS_PER_TOKEN = 3.2;
let COMPRESS_AUTO = true;
let 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]
};
let PACK_ALIASES = {
ordinary: "ordinary",
combine: "ordinary",
normal: "ordinary",
general: "ordinary",
default: "ordinary",
write: "write_prompt",
write_prompt: "write_prompt",
critique: "critique_image",
critique_image: "critique_image",
compose: "compose_scene",
compose_scene: "compose_scene",
params: "fix_params",
fix_params: "fix_params",
inpaint: "inpaint_edit",
inpaint_edit: "inpaint_edit",
describe: "describe_ref",
describe_ref: "describe_ref"
};
let WELCOME_HTML = `
<div class="sa-welcome-title">Assistent \xB7 Krea 2</div>
<ul>
<li><strong>Generate</strong> \u0441\u043B\u0435\u0432\u0430 \u2014 \u0436\u0438\u0432\u043E\u0439 \u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440. \u0412 \u0447\u0430\u0442 \u0441\u0430\u043C \u043D\u0435 \u0443\u0445\u043E\u0434\u0438\u0442.</li>
<li><strong>Refs</strong> \u2014 \u0440\u0435\u0444\u0435\u0440\u0435\u043D\u0441\u044B \u043D\u0430 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E\u0439 \u0432\u043A\u043B\u0430\u0434\u043A\u0435: drop / paste / \u0421\u043D\u0438\u043C\u043E\u043A gen.</li>
<li>\u0413\u0430\u043B\u043E\u0447\u043A\u0430 vision \u043D\u0430 \u043E\u043A\u043D\u0435 \u2014 \u043E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u043A\u0430\u0434\u0440 \u043C\u043E\u0434\u0435\u043B\u0438.</li>
<li>\u0427\u0438\u043F\u0441\u044B aspect / seed / Vary / Turbo\xB7RAW. \u0412 \u0447\u0430\u0442\u0435: <code>/help</code>.</li>
<li>\u041A\u043D\u043E\u043F\u043A\u0438 \u043F\u0430\u0442\u0447\u0430 \u0442\u043E\u043B\u044C\u043A\u043E \u0443 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0433\u043E \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u044F.</li>
</ul>
\u041D\u0430\u043F\u0438\u0448\u0438, \u0447\u0442\u043E \u0441\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u2014 \u0438\u043B\u0438 \u043A\u0438\u043D\u044C \u0440\u0435\u0444\u0435\u0440\u0435\u043D\u0441 \u0438 \u043F\u043E\u043F\u0440\u043E\u0441\u0438 \u043F\u0440\u0430\u0432\u043A\u0443.`;
let HELP_TEXT = `Slash-\u043A\u043E\u043C\u0430\u043D\u0434\u044B (\u0431\u0435\u0437 LLM):
/help \u2014 \u044D\u0442\u043E\u0442 \u0441\u043F\u0438\u0441\u043E\u043A
/new \u2014 \u043D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442 (\u0442\u0435\u043A\u0443\u0449\u0438\u0439 \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u0441\u044F \u0432 \u0418\u0441\u0442\u043E\u0440\u0438\u044E)
/history \u2014 \u043E\u0442\u043A\u0440\u044B\u0442\u044C \u0441\u043F\u0438\u0441\u043E\u043A \u0447\u0430\u0442\u043E\u0432
/compress \u2014 \u0441\u0436\u0430\u0442\u044C \u0441\u0442\u0430\u0440\u044B\u0435 \u0445\u043E\u0434\u044B \u0432 \u0441\u0430\u043C\u043C\u0430\u0440\u0438
/debug \u2014 \u0441\u0432\u043E\u0434\u043A\u0430 UI/Exact (\u0431\u0435\u0437 LLM)
/debug ask \u2014 \u0442\u043E \u0436\u0435 + \u043A\u043E\u0440\u043E\u0442\u043A\u0438\u0439 \u043E\u0442\u0432\u0435\u0442 \u043C\u043E\u0434\u0435\u043B\u0438
/why \u2014 \u0441\u0440\u0430\u0437\u0443 /debug ask
/gen \u2014 Generate \u0441\u0435\u0439\u0447\u0430\u0441
/look generate|refN \u2014 \u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u043A\u0430\u0434\u0440 \u043C\u043E\u0434\u0435\u043B\u0438 (vision)
/init /mask /clear \u2014 Init / Mask / Clear Init
/interrupt \u2014 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u044E
/aspect 16:9 \u2014 \u0440\u0430\u0437\u043C\u0435\u0440 \u0438\u0437 \u0442\u0430\u0431\u043B\u0438\u0446\u044B 1K
/seed lock|random \u2014 \u0437\u0430\u0444\u0438\u043A\u0441\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0438\u043B\u0438 \u0440\u0430\u043D\u0434\u043E\u043C\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C seed
/vary \u2014 \u043D\u043E\u0432\u044B\u0439 seed, \u0442\u043E\u0442 \u0436\u0435 \u043F\u0440\u043E\u043C\u043F\u0442
/pack write|critique|compose|params|inpaint|describe
/inventory \u2014 rescan \u043C\u043E\u0434\u0435\u043B\u0435\u0439 + \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u043F\u0438\u0441\u043E\u043A LoRA
\u0427\u0438\u043F\u0441\u044B \u043D\u0430\u0434 \u043F\u043E\u043B\u0435\u043C \u0432\u0432\u043E\u0434\u0430 \u0434\u0435\u043B\u0430\u044E\u0442 \u0442\u043E \u0436\u0435 \u0434\u043B\u044F aspect / seed / vary / Turbo\xB7RAW.
\u041F\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435 \u0432\u0441\u0435\u0433\u0434\u0430 \u043D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442; \u0441\u043C\u0435\u043D\u0430 \u0447\u0430\u0442\u0430 \u0432 \u0418\u0441\u0442\u043E\u0440\u0438\u0438 \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0435\u0442 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B.`;
let SLASH_COMMANDS = [
{ cmd: "/help", hint: "\u0441\u043F\u0438\u0441\u043E\u043A \u043A\u043E\u043C\u0430\u043D\u0434" },
{ cmd: "/new", hint: "\u043D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442" },
{ cmd: "/history", hint: "\u0438\u0441\u0442\u043E\u0440\u0438\u044F \u0447\u0430\u0442\u043E\u0432" },
{ cmd: "/compress", hint: "\u0441\u0436\u0430\u0442\u044C \u0441\u0442\u0430\u0440\u044B\u0435 \u0445\u043E\u0434\u044B" },
{ cmd: "/debug", hint: "\u0441\u0432\u043E\u0434\u043A\u0430 \xB7 ask = \u0441 LLM" },
{ cmd: "/why", hint: "debug + \u043F\u043E\u044F\u0441\u043D\u0435\u043D\u0438\u0435 LLM" },
{ cmd: "/gen", hint: "Generate \u0441\u0435\u0439\u0447\u0430\u0441" },
{ cmd: "/look ", hint: "generate|refN" },
{ cmd: "/init", hint: "\u043A\u0430\u043A Init" },
{ cmd: "/mask", hint: "\u043A\u0430\u043A Mask" },
{ cmd: "/clear", hint: "\u0441\u0431\u0440\u043E\u0441 Init/Mask" },
{ cmd: "/interrupt", hint: "\u0441\u0442\u043E\u043F" },
{ cmd: "/aspect ", hint: "16:9" },
{ cmd: "/seed ", hint: "lock|random" },
{ cmd: "/vary", hint: "\u043D\u043E\u0432\u044B\u0439 seed" },
{ cmd: "/pack ", hint: "write|critique|\u2026" },
{ cmd: "/inventory", hint: "rescan \u043C\u043E\u0434\u0435\u043B\u0435\u0439" }
];
const state = {
history: [],
config: null,
exact: null,
sessionExact: {},
lastUserParamIntent: false,
lastUserControlIntent: false,
lastPatch: null,
pendingSilentGen: false,
pendingPromptEnMerge: null,
enabledSkills: [],
kreaProfiles: { turbo: { steps: 8, cfg: 1, sigma_shift: 1.15 }, raw: { steps: 28, cfg: 4.5 } },
preferredEmbed: null,
busy: false,
generating: false,
chatEpoch: 0,
waitImageTimer: null,
lastImageDataUrl: null,
preferredModel: null,
inventory: { loras: [], checkpoints: [], wildcards: [], has_civitai_key: false },
inventoryFetchedAt: 0,
streamEl: null,
streamMeta: null,
streamText: "",
streamFenceDone: false,
turnHops: [],
lastSystemChars: 0,
lastSystemLayers: null,
lastContextChars: 0,
lastPromptEvalCount: null,
contextMemory: null,
ctxPanelOpen: false,
compressing: false,
busyPhase: "idle",
busyStarted: 0,
gotDelta: false,
busyTimer: null,
slots: [],
selectedSlotId: "ref1",
refSeq: 1,
genResults: [],
selectedGenResultId: null,
lightboxIndex: -1,
packUserTouched: false,
view: "chat",
boardTab: "generate",
personas: [],
chatSession: null,
pendingPersonaNote: null,
chats: [],
activeChatId: null,
restoringChat: false,
chatsPanelOpen: false,
chatsDrawerOpen: false,
chatsQuery: "",
chatsSearchHits: null,
slashIndex: 0,
llmParked: false,
expectColdLoad: false,
memoryRows: [],
userPrefs: [],
settingsTab: "behavior",
settingsPersonaId: null,
ollamaHealth: "unknown",
trainingLock: false,
activity: null
};
function getActivity() {
if (state.activity) {
return state.activity;
}
if (window.SA && typeof SA.createActivityController === "function") {
state.activity = SA.createActivityController({
getMessagesEl: () => $2("sa_messages"),
scrollToBottom: () => scrollMessagesToBottom(),
hideEmpty: () => hideChatEmpty()
});
}
return state.activity;
}
function activityBegin(title) {
const a = getActivity();
if (a) {
a.begin(title || "Assistent");
}
}
function activityStep(id, patch) {
const a = getActivity();
if (a) {
a.upsert(id, patch);
}
}
function activityDone(id, patch) {
const a = getActivity();
if (a) {
a.done(id, patch);
}
}
function activityFinish(summary) {
const a = getActivity();
if (a) {
a.finish(summary);
}
}
const HOP_BUDGET = 4;
function isContinuationTurn(opts) {
return !!(opts && (opts.fromVisionHop || opts.fromAutoCritique || opts.fromPromptEnRetry || opts.fromAskHop));
}
function isMachineTurn(opts) {
return isContinuationTurn(opts) || !!(opts && opts.fromDebug);
}
function resetTurnHops() {
state.turnHops = [];
state.pendingPromptEnMerge = null;
}
function turnHopUsed(kind) {
return (state.turnHops || []).includes(kind);
}
function claimTurnHop(kind) {
if (!Array.isArray(state.turnHops)) {
state.turnHops = [];
}
if (state.turnHops.includes(kind) || state.turnHops.length >= HOP_BUDGET) {
return false;
}
state.turnHops.push(kind);
return true;
}
function diskPersist() {
return window.SA && window.SA.persist || null;
}
function $2(id) {
return document.getElementById(id);
}
function modelShort(name) {
const s = String(name || "");
const slash = s.lastIndexOf("/");
return (slash >= 0 ? s.slice(slash + 1) : s) || "model";
}
function fmtElapsed(ms) {
const s = Math.max(0, Math.floor(ms / 1e3));
if (s < 60) {
return `${s}s`;
}
return `${Math.floor(s / 60)}m ${String(s % 60).padStart(2, "0")}s`;
}
function hideChatEmpty() {
const empty = $2("sa_chat_empty");
if (empty) {
empty.hidden = true;
}
}
let scrollMessagesRaf = 0;
function messagesNearBottom(thresholdPx = 96) {
const box = $2("sa_messages");
if (!box) {
return true;
}
return box.scrollHeight - box.scrollTop - box.clientHeight <= thresholdPx;
}
function scrollMessagesToBottom({ force = false } = {}) {
const box = $2("sa_messages");
if (!box) {
return;
}
if (!force && !messagesNearBottom()) {
return;
}
if (scrollMessagesRaf) {
return;
}
scrollMessagesRaf = requestAnimationFrame(() => {
scrollMessagesRaf = 0;
const el = $2("sa_messages");
if (el && (force || messagesNearBottom(120))) {
el.scrollTop = el.scrollHeight;
}
});
}
function showChatEmptyIfIdle() {
const box = $2("sa_messages");
const empty = $2("sa_chat_empty");
if (!box || !empty) {
return;
}
const hasMsg = [...box.children].some((el) => el.id !== "sa_chat_empty");
empty.hidden = hasMsg;
}
function setBusyPhase(phase) {
state.busyPhase = phase || "thinking";
tickBusyUi();
syncPatchActionAvailability();
syncGenerateBusy();
}
function tickBusyUi() {
if (state.busyPhase === "idle") {
return;
}
const elapsed = Date.now() - (state.busyStarted || Date.now());
if (!state.gotDelta && (state.busyPhase === "thinking" || state.busyPhase === "waiting") && elapsed > 1600) {
state.busyPhase = state.llmParked || state.expectColdLoad ? "loading" : "waiting";
}
const model = modelShort($2("sa_model")?.value);
const labels = {
encoding: "Encoding image\u2026",
waiting: "\u0416\u0434\u0443 Ollama / \u043F\u0435\u0440\u0432\u044B\u0439 \u0442\u043E\u043A\u0435\u043D\u2026",
loading: `\u0417\u0430\u0433\u0440\u0443\u0436\u0430\u044E ${model} \u0432 GPU\u2026 \u043E\u0431\u044B\u0447\u043D\u043E 30\u2013120 \u0441 \u043F\u043E\u0441\u043B\u0435 park`,
warming: `\u0412\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u044E ${model} \u0432 GPU\u2026`,
thinking: "Thinking\u2026",
streaming: "Writing\u2026",
generating: "Generating image\u2026",
parking: "\u041E\u0441\u0432\u043E\u0431\u043E\u0436\u0434\u0430\u044E VRAM (park LLM)\u2026",
applying: "Applying patch\u2026",
silent_gen: "\u041F\u0440\u0438\u043C\u0435\u043D\u044F\u044E \u043F\u0430\u0442\u0447 \u2192 Generate\u2026",
refining: "\u0423\u0442\u043E\u0447\u043D\u044F\u044E \u043E\u0442\u0432\u0435\u0442\u2026",
compressing: "\u0421\u0436\u0438\u043C\u0430\u044E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u2026"
};
const text = labels[state.busyPhase] || "Working\u2026";
const phaseKind = {
thinking: "think",
streaming: "stream",
waiting: "think",
loading: "warm",
warming: "warm",
parking: "park",
encoding: "look",
generating: "generate",
applying: "merge",
silent_gen: "generate",
refining: "prep",
compressing: "compress"
};
activityStep(`phase:${state.busyPhase}`, {
kind: phaseKind[state.busyPhase] || "think",
label: text,
status: "running"
});
const a = getActivity();
if (a && Array.isArray(a.steps)) {
for (const s of a.steps) {
if (s.id.startsWith("phase:") && s.id !== `phase:${state.busyPhase}` && s.status === "running") {
a.done(s.id);
}
}
}
const barText = $2("sa_livebar_text");
if (barText) {
barText.textContent = text;
}
const elapsedEl = $2("sa_elapsed");
if (elapsedEl) {
elapsedEl.textContent = fmtElapsed(elapsed);
}
const status = $2("sa_status");
if (status) {
status.textContent = text;
status.classList.add("sa-status-busy");
}
}
function startBusyUi(phase) {
state.busyStarted = Date.now();
state.gotDelta = false;
state.busyPhase = phase || "thinking";
$2("swarm_assistent_root")?.classList.add("sa-is-busy");
$2("sa_composer")?.classList.add("sa-composer-busy");
const send = $2("sa_btn_send");
if (send) {
send.disabled = true;
}
const input = $2("sa_input");
if (input) {
input.classList.add("sa-input-busy");
}
const bar = $2("sa_livebar");
if (bar) {
bar.hidden = false;
}
const dot = $2("sa_live_dot");
if (dot) {
dot.hidden = false;
}
tickBusyUi();
syncPatchActionAvailability();
syncGenerateBusy();
if (state.busyTimer) {
clearInterval(state.busyTimer);
}
state.busyTimer = setInterval(tickBusyUi, 400);
}
function stopBusyUi(finalStatus) {
if (state.busyTimer) {
clearInterval(state.busyTimer);
state.busyTimer = null;
}
const elapsed = Date.now() - (state.busyStarted || Date.now());
state.busyPhase = "idle";
activityFinish(finalStatus || "\u0413\u043E\u0442\u043E\u0432\u043E");
$2("swarm_assistent_root")?.classList.remove("sa-is-busy");
$2("sa_composer")?.classList.remove("sa-composer-busy");
const send = $2("sa_btn_send");
if (send) {
send.disabled = false;
}
const input = $2("sa_input");
if (input) {
input.classList.remove("sa-input-busy");
}
const bar = $2("sa_livebar");
if (bar) {
bar.hidden = true;
}
const dot = $2("sa_live_dot");
if (dot) {
dot.hidden = true;
}
const status = $2("sa_status");
if (status) {
status.classList.remove("sa-status-busy");
}
if (finalStatus != null) {
const suffix = elapsed >= 1e3 ? ` \xB7 ${fmtElapsed(elapsed)}` : "";
setStatus(finalStatus + suffix);
}
syncPatchActionAvailability();
syncGenerateBusy();
}
function setStatus(text) {
const el = $2("sa_status");
if (el) {
el.textContent = text || "";
}
}
function setInterruptVisible(on) {
const btn = $2("sa_btn_interrupt");
if (btn) {
btn.hidden = !on;
btn.classList.toggle("sa-interrupt-active", !!on);
}
}
function looksLikeKrea(text) {
const s = String(text || "");
return /krea\s*2|krea2|krea-2/i.test(s) || /krea/i.test(s);
}
function resolveCurrentCheckpoint() {
const out = {
name: null,
architecture: null,
compat_class: null,
title: null,
class: null,
source: null
};
try {
if (typeof currentModelHelper !== "undefined" && currentModelHelper) {
out.name = currentModelHelper.curModel || null;
out.architecture = currentModelHelper.curArch || null;
out.compat_class = currentModelHelper.curCompatClass || null;
out.source = "currentModelHelper";
}
} catch (e) {
}
try {
if (typeof getCurrentModel === "function") {
const model = getCurrentModel();
if (model) {
out.name = out.name || model.name || null;
out.title = model.title || null;
out.architecture = out.architecture || model.architecture || null;
out.class = model.class || null;
out.compat_class = out.compat_class || model.compat_class || null;
out.source = out.source || "getCurrentModel";
}
}
} catch (e) {
}
try {
const sel = document.getElementById("current_model") || document.getElementById("input_model");
if (sel) {
const opt = sel.selectedOptions && sel.selectedOptions[0];
const hint = [
sel.value,
opt && opt.text,
opt && opt.dataset && opt.dataset.cleanname
].filter(Boolean).join(" ");
if (!out.name && sel.value) {
out.name = sel.value;
out.source = out.source || "dropdown";
}
if (hint && !out.architecture) {
out.title = out.title || hint;
}
}
} catch (e) {
}
return out;
}
function isKreaSelected() {
try {
const m = resolveCurrentCheckpoint();
const blob = [
m.architecture,
m.compat_class,
m.title,
m.name,
m.class
].join(" ");
return looksLikeKrea(blob);
} catch (e) {
return false;
}
}
function updateGate() {
const ok = isKreaSelected();
const gate = $2("sa_gate");
const layout = $2("sa_layout");
if (gate) {
gate.hidden = ok;
if (!ok) {
const m = resolveCurrentCheckpoint();
const seen = [m.architecture, m.compat_class, m.name].filter(Boolean).join(" \xB7 ");
const p = gate.querySelector("p");
if (p) {
p.innerHTML = seen ? `Swarm Assistent is for <strong>Krea 2</strong> models only. Current: <code>${escapeHtml2(
seen
)}</code> \u2014 pick a checkpoint with architecture <code>krea-2</code>.` : "Swarm Assistent is for <strong>Krea 2</strong> models only. Select a Krea 2 checkpoint on Generate to enable the chat.";
}
}
}
if (layout) {
layout.classList.toggle("sa-disabled", !ok);
}
return ok;
}
function escapeHtml2(s) {
return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
const PROSE_SECTION_TITLES = {
critique: "\u041A\u0440\u0438\u0442\u0438\u043A\u0430",
\u043A\u0440\u0438\u0442\u0438\u043A\u0430: "\u041A\u0440\u0438\u0442\u0438\u043A\u0430",
analysis: "\u0420\u0430\u0437\u0431\u043E\u0440",
\u0440\u0430\u0437\u0431\u043E\u0440: "\u0420\u0430\u0437\u0431\u043E\u0440",
notes: "\u0417\u0430\u043C\u0435\u0442\u043A\u0438",
\u0437\u0430\u043C\u0435\u0442\u043A\u0438: "\u0417\u0430\u043C\u0435\u0442\u043A\u0438",
summary: "\u041A\u0440\u0430\u0442\u043A\u043E",
\u043A\u0440\u0430\u0442\u043A\u043E: "\u041A\u0440\u0430\u0442\u043A\u043E",
prompt: "\u041F\u0440\u043E\u043C\u043F\u0442",
\u043F\u0440\u043E\u043C\u043F\u0442: "\u041F\u0440\u043E\u043C\u043F\u0442",
"improved prompt": "\u041F\u0440\u043E\u043C\u043F\u0442",
"next prompt": "\u041F\u0440\u043E\u043C\u043F\u0442",
deliverable: "\u0418\u0442\u043E\u0433",
\u0438\u0442\u043E\u0433: "\u0418\u0442\u043E\u0433",
verdict: "\u0412\u0435\u0440\u0434\u0438\u043A\u0442",
\u0432\u0435\u0440\u0434\u0438\u043A\u0442: "\u0412\u0435\u0440\u0434\u0438\u043A\u0442",
issues: "\u041F\u0440\u043E\u0431\u043B\u0435\u043C\u044B",
\u043F\u0440\u043E\u0431\u043B\u0435\u043C\u044B: "\u041F\u0440\u043E\u0431\u043B\u0435\u043C\u044B",
fixes: "\u041F\u0440\u0430\u0432\u043A\u0438",
\u043F\u0440\u0430\u0432\u043A\u0438: "\u041F\u0440\u0430\u0432\u043A\u0438",
suggestion: "\u041F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u0435",
suggestions: "\u041F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u044F",
\u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u044F: "\u041F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u044F"
};
function localizeProseHeading(raw) {
const cleaned = String(raw || "").replace(/[*_`#]/g, "").trim();
if (!cleaned) {
return null;
}
const key = cleaned.toLowerCase().replace(/\s+/g, " ");
if (/^json\s*patch$/.test(key) || /^патч$/.test(key) || /^json\s*патч$/.test(key)) {
return null;
}
if (PROSE_SECTION_TITLES[key]) {
return PROSE_SECTION_TITLES[key];
}
const head = key.split(/[—:\-|]/)[0].trim();
if (PROSE_SECTION_TITLES[head]) {
return PROSE_SECTION_TITLES[head];
}
return cleaned;
}
function formatProseInline(escapedLine) {
let t = escapedLine;
t = t.replace(/`([^`]+)`/g, '<code class="sa-prose-code">$1</code>');
t = t.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
t = t.replace(/(^|[^*])\*([^*\n]+)\*(?!\*)/g, "$1<em>$2</em>");
return t;
}
function formatAssistantProseHtml(raw) {
let text = String(raw || "").replace(/\r\n/g, "\n");
text = text.replace(/(?:^|\n)#{1,6}\s*JSON\s*Patch\s*(?=\n|$)/gi, "\n");
text = text.replace(/(?:^|\n)\s*JSON\s*Patch\s*:?\s*(?=\n|$)/gi, "\n");
text = text.replace(/\n{3,}/g, "\n\n").trim();
if (!text) {
return "";
}
const lines = text.split("\n");
const parts = [];
let listItems = [];
const flushList = () => {
if (!listItems.length) {
return;
}
parts.push(
`<ul class="sa-prose-list">${listItems.map((li) => `<li>${formatProseInline(escapeHtml2(li))}</li>`).join("")}</ul>`
);
listItems = [];
};
for (const line of lines) {
const heading = line.match(/^#{1,3}\s+(.+?)\s*$/);
if (heading) {
flushList();
const title = localizeProseHeading(heading[1]);
if (!title) {
continue;
}
const level = Math.min((line.match(/^#+/) || ["###"])[0].length, 3);
parts.push(
`<div class="sa-prose-h sa-prose-h${level}" role="heading" aria-level="${level}">${escapeHtml2(title)}</div>`
);
continue;
}
const bullet = line.match(/^\s*[-*•]\s+(.+)$/);
if (bullet) {
listItems.push(bullet[1]);
continue;
}
flushList();
if (!line.trim()) {
parts.push('<div class="sa-prose-gap" aria-hidden="true"></div>');
continue;
}
parts.push(`<p class="sa-prose-p">${formatProseInline(escapeHtml2(line))}</p>`);
}
flushList();
return parts.join("");
}
function setAssistantBody(div, text, { live = false } = {}) {
if (!div) {
return;
}
let body = div.querySelector(".sa-msg-body");
if (!body) {
body = document.createElement("div");
body.className = "sa-msg-body";
div.appendChild(body);
}
const raw = text || "";
if (live) {
body.classList.add("sa-prose", "sa-prose-live");
body.classList.remove("sa-prose-rich");
body.textContent = raw;
return;
}
body.classList.add("sa-prose", "sa-prose-rich");
body.classList.remove("sa-prose-live");
const html = formatAssistantProseHtml(raw);
if (html) {
body.innerHTML = html;
} else {
body.textContent = "";
}
}
function val(id) {
const el = document.getElementById(id);
return el ? el.value : "";
}
function setVal(id, value) {
const el = document.getElementById(id);
if (!el) {
return;
}
el.value = value;
el.dispatchEvent(new Event("input", { bubbles: true }));
el.dispatchEvent(new Event("change", { bubbles: true }));
}
function liveNegativePrompt() {
return String(val("input_negativeprompt") || val("alt_negativeprompt_textbox") || "").trim();
}
function exactDefaultNegative() {
const { exact } = resolveExactBundle();
const n = exact?.generation?.negative ?? exact?.negative;
return n != null ? String(n).trim() : "";
}
function setNegativePrompt(text) {
const s = text != null ? String(text) : "";
if (document.getElementById("input_negativeprompt")) {
setVal("input_negativeprompt", s);
}
if (document.getElementById("alt_negativeprompt_textbox")) {
setVal("alt_negativeprompt_textbox", s);
}
}
function ensureNegativeForGenerate(patch) {
let neg = "";
if (patch && patch.negative != null && String(patch.negative).trim() !== "") {
neg = String(patch.negative).trim();
} else {
neg = liveNegativePrompt() || exactDefaultNegative();
}
if (neg) {
setNegativePrompt(neg);
if (patch && (patch.negative == null || String(patch.negative).trim() === "")) {
patch.negative = neg;
}
}
return neg;
}
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 cyrTokenRe(alts) {
const boundary = "(^|[^0-9A-Za-z_\u0410-\u042F\u0430-\u044F\u0401\u0451])";
const end = "(?=$|[^0-9A-Za-z_\u0410-\u042F\u0430-\u044F\u0401\u0451])";
return new RegExp(`${boundary}(?:${alts})${end}`, "i");
}
function parseAspectFromUserText(text) {
const t = String(text || "");
if (!t.trim()) {
return null;
}
const ratio = t.match(/(?:^|[^0-9])(\d+(?:\.\d+)?)\s*[:x×хX]\s*(\d+(?:\.\d+)?)(?=$|[^0-9])/);
if (ratio) {
const key = normalizeAspect(`${ratio[1]}:${ratio[2]}`);
if (key) {
return key;
}
}
const na = t.match(/(?:^|[^0-9])(\d+(?:\.\d+)?)\s*(?:на|к|to)\s*(\d+(?:\.\d+)?)(?=$|[^0-9])/i);
if (na) {
const key = normalizeAspect(`${na[1]}:${na[2]}`);
if (key) {
return key;
}
}
const named = t.match(/\b(16:9|9:16|1:1|4:5|2:3|3:2|4:3|2\.35:1)\b/i);
if (named) {
return normalizeAspect(named[1]);
}
if (cyrTokenRe("\u043F\u043E\u0440\u0442\u0440\u0435\u0442|\u0432\u0435\u0440\u0442\u0438\u043A\u0430\u043B[\u0430-\u044F\u0451]*").test(t) || /\b(portrait|vertical)\b/i.test(t)) {
return normalizeAspect("9:16") || normalizeAspect("2:3");
}
if (cyrTokenRe("\u0430\u043B\u044C\u0431\u043E\u043C|\u0433\u043E\u0440\u0438\u0437\u043E\u043D\u0442\u0430\u043B[\u0430-\u044F\u0451]*").test(t) || /\b(landscape|horizontal|widescreen)\b/i.test(t)) {
return normalizeAspect("16:9");
}
return null;
}
function isSameButAspectRequest(text) {
const t = String(text || "");
if (!parseAspectFromUserText(t)) {
return false;
}
return /такую\s+же|тот\s+же\s+промпт|same\s+(one|prompt|thing|again)|только\s+(поменя|смени|поставь)|поменяй\s+на|смени\s+на|only\s+change|just\s+change/i.test(t) || /поменяй\s+(размер|aspect|соотношен)/i.test(t) || /смени\s+(размер|aspect|соотношен)/i.test(t);
}
function userTextMentionsControls(text) {
const t = String(text || "");
if (/\b(horny|controls?|preference[_\s-]?bias)\b/i.test(t)) {
return true;
}
return cyrTokenRe("\u0445\u043E\u0440\u043D\u0438|\u043E\u0441\u0442\u044B\u043D\u044C|\u0441\u043B\u0430\u0439\u0434\u0435\u0440").test(t) || /\/\s*(остынь|ostyn|horny-game)/i.test(t) || /слайдер\s*вкус|вкус\s*(на|в)\s*\d|поставь\s*вкус|крутани\s*вкус/i.test(t);
}
function patchLooksLikeGeneration(patch) {
if (!patch || typeof patch !== "object") {
return false;
}
if (patch.prompt != null || patch.loras || patch.aspect != null || patch.width != null || patch.height != null || patch.steps != null || patch.cfg != null || patch.seed != null) {
return true;
}
return Array.isArray(patch.actions) && patch.actions.map(String).includes("generate");
}
function filterControlPatch(incoming, patch) {
const schema = state.config?.controls || {};
const out = {};
if (!incoming || typeof incoming !== "object") {
return out;
}
if (patchLooksLikeGeneration(patch) && !state.lastUserControlIntent) {
return out;
}
for (const [id, raw] of Object.entries(incoming)) {
if (!schema[id]) {
continue;
}
const n = Number(raw);
if (!Number.isFinite(n)) {
continue;
}
const def = Number(schema[id]?.default);
const cur = getControlValue(id, Number.isFinite(def) ? def : n);
if (Math.abs(n - cur) < 5e-4) {
continue;
}
if (!state.lastUserControlIntent && Number.isFinite(def) && Math.abs(n - def) < 5e-4 && Math.abs(cur - def) > 5e-4) {
continue;
}
out[id] = n;
}
return out;
}
function userTextMentionsParams(text) {
const t = String(text || "");
if (parseAspectFromUserText(t)) {
return true;
}
if (/\b(steps?|cfg|seed|sigma|aspect|resolution|batch|turbo|raw)\b/i.test(t)) {
return true;
}
return cyrTokenRe("\u0440\u0430\u0437\u043C\u0435\u0440|\u0448\u0438\u0440\u0438\u043D[\u0430-\u044F\u0451]*|\u0432\u044B\u0441\u043E\u0442[\u0430-\u044F\u0451]*|\u0441\u043E\u043E\u0442\u043D\u043E\u0448\u0435\u043D[\u0430-\u044F\u0451]*|\u0442\u0443\u0440\u0431\u043E|\u043F\u043E\u0440\u0442\u0440\u0435\u0442|\u0432\u0435\u0440\u0442\u0438\u043A\u0430\u043B[\u0430-\u044F\u0451]*").test(t);
}
function replyMissingJsonPatch(reply) {
const t = String(reply || "");
if (!t.trim()) {
return false;
}
if (/```(?:json)?\s*\{[\s\S]*?\}```/i.test(t)) {
return false;
}
return /###\s*JSON\s*Patch\b/i.test(t) || /JSON\s*Patch\s*:?\s*$/im.test(t);
}
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(
"\u0434\u0430\u0432\u0430\u0439\\s+\u0434\u0430\u043B\u044C\u0448\u0435|\u0441\u043B\u0435\u0434\u0443\u044E\u0449(\u0438\u0439|\u0430\u044F|\u0435\u0435|\u0443\u044E)\\s+\u043A\u0430\u0434\u0440|\u0435\u0449\u0451\\s+\u043A\u0430\u0434\u0440|\u0435\u0449\u0435\\s+\u043A\u0430\u0434\u0440|\u043A\u0430\u0434\u0440\\s*\u2116?\\s*\\d+|\u0441\u0434\u0435\u043B\u0430\u0439\\s+\u0441\u043B\u0435\u0434\u0443\u044E\u0449"
).test(t);
}
function extractPromptFromProse(reply) {
const t = String(reply || "").replace(/\r\n/g, "\n");
if (!t.trim()) {
return null;
}
const bq = [];
for (const line of t.split("\n")) {
const m = line.match(/^\s{0,3}>\s?(.*)$/);
if (m) {
bq.push(m[1]);
continue;
}
if (bq.length) {
break;
}
}
const fromBq = bq.join("\n").trim();
if (fromBq.length >= 48) {
return fromBq.slice(0, 4e3);
}
const section = t.match(
/(?:^|\n)#{1,6}\s*(?:📷\s*)?(?:prompt|промпт|improved\s+prompt|next\s+prompt|кадр[^\n]*)\s*\n+([\s\S]+?)(?=\n#{1,6}\s|\n```|$)/i
);
if (section) {
const body = section[1].replace(/^\s{0,3}>\s?/gm, "").trim();
if (body.length >= 48) {
return body.slice(0, 4e3);
}
}
return null;
}
function promptLooksKreaReady(prompt) {
const t = String(prompt || "").trim();
if (t.length < 80) {
return false;
}
const cyr = (t.match(/[\u0400-\u04FF]/g) || []).length;
const lat = (t.match(/[A-Za-z]/g) || []).length;
if (cyr >= 12) {
return false;
}
if (lat < 55) {
return false;
}
if (t.length < 120 && (t.match(/[,.;:]/g) || []).length < 2) {
return false;
}
return true;
}
function promptNeedsKreaPrep(prompt) {
return !promptLooksKreaReady(prompt);
}
function buildKreaPromptPrepRequest(patch) {
const keep = {
actions: Array.isArray(patch.actions) && patch.actions.length ? patch.actions : ["generate"]
};
if (patch.aspect) {
keep.aspect = patch.aspect;
}
if (patch.negative != null && String(patch.negative).trim() !== "") {
keep.negative = patch.negative;
} else {
const liveNeg = liveNegativePrompt() || exactDefaultNegative();
if (liveNeg) {
keep.negative = liveNeg;
}
}
if (Array.isArray(patch.loras)) {
keep.loras = patch.loras;
}
if (patch.width != null) {
keep.width = patch.width;
}
if (patch.height != null) {
keep.height = patch.height;
}
return `You are the Krea 2 prompt prep step (chat model). Rewrite SOURCE into the final Swarm Generate box text.
HARD RULES:
- JSON "prompt": English only (no Cyrillic) \u2014 translate if needed.
- Natural photographer/director prose for Qwen3-VL \u2014 not Danbooru tags, not (word:1.5), not masterpiece/best quality/8k.
- Structure & front-load: subject \u2192 pose/action \u2192 body/wardrobe \u2192 setting \u2192 materials/textures \u2192 camera/framing \u2192 lighting \u2192 medium/mood.
- Expand thin ideas; fix anti-patterns; one coherent scene.
- Keep LoRA trigger phrases in English near the subject they affect.
- Always include JSON "negative": keep/supplement SOURCE+live negative, or use Exact default if empty. Never drop it.
- Put \u201Cno blur / empty street\u201D ideas as positives in prompt, not as a huge negative dump.
- One short ack in the user language max, then ONE fenced JSON merging these keys: ${JSON.stringify(keep)} plus the new English "prompt" and "negative".
- Include actions:["generate"] when an image was requested.
SOURCE:
${patch.prompt}`;
}
function mergePromptEnRewrite(effective) {
const base = state.pendingPromptEnMerge;
state.pendingPromptEnMerge = null;
if (!base || !effective) {
return effective;
}
return {
...base,
...effective,
prompt: effective.prompt || base.prompt,
negative: effective.negative != null && String(effective.negative).trim() !== "" ? effective.negative : base.negative || liveNegativePrompt() || exactDefaultNegative() || void 0,
actions: Array.isArray(effective.actions) && effective.actions.length ? effective.actions : base.actions || ["generate"],
loras: effective.loras || base.loras,
aspect: effective.aspect || base.aspect
};
}
function userAsksNoGenerate(text) {
const t = String(text || "").trim();
if (!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(
"\u0437\u0430\u043F\u043E\u043C\u043D|\u0437\u0430\u043F\u043E\u043C\u043D\u0438|\u0437\u0430\u043F\u043E\u043C\u043D\u0438\u043C|\u0441\u043E\u0445\u0440\u0430\u043D\u0438|\u0441\u043E\u0445\u0440\u0430\u043D\u0438\u043C|\u0448\u0430\u0431\u043B\u043E\u043D|\u0431\u0430\u0437\u043E\u0432(\u044B\u0439|\u043E\u0433\u043E|\u043E\u043C\u0443|\u044B\u043C|\u0430\u044F|\u0443\u044E|\u043E\u0435)?\\s+\u043F\u0440\u043E\u043C\u043F\u0442|\u043D\u0435\\s+\u0433\u0435\u043D\u0435\u0440\u0438\u0440[\u0430-\u044F\u0451]*|\u0431\u0435\u0437\\s+\u0433\u0435\u043D\u0435\u0440\u0430\u0446[\u0430-\u044F\u0451]*|\u043D\u0435\\s+\u043D\u0430\u0434\u043E\\s+\u0433\u0435\u043D\u0435\u0440[\u0430-\u044F\u0451]*|\u0442\u043E\u043B\u044C\u043A\u043E\\s+\u0437\u0430\u043F\u043E\u043C\u043D[\u0430-\u044F\u0451]*|\u043F\u043E\u043A\u0430\\s+\u0437\u0430\u043F\u043E\u043C\u043D[\u0430-\u044F\u0451]*|\u043D\u0435\\s+\u0440\u0438\u0441\u0443\u0439|\u043D\u0435\\s+\u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0439\\s+\u0433\u0435\u043D\u0435\u0440[\u0430-\u044F\u0451]*"
).test(t);
}
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("\u043A\u0440\u0438\u0442\u0438\u043A[\u0430-\u044F\u0451]*|\u0447\u0442\u043E\\s+\u043D\u0435\\s+\u0442\u0430\u043A|\u0440\u0430\u0437\u0431\u0435\u0440\u0438").test(t)) {
return true;
}
if (cyrTokenRe("\u043E\u043F\u0438\u0448\u0438\\s+(\u044D\u0442\u043E|\u044D\u0442\u0443|\u0440\u0435\u0444|\u0438\u0437\u043E\u0431\u0440\u0430\u0436[\u0430-\u044F\u0451]*|\u043A\u0430\u0440\u0442\u0438\u043D\u043A[\u0430-\u044F\u0451]*|\u043A\u0430\u0434\u0440|\u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442|\u0440\u0435\u0444\u0435\u0440\u0435\u043D\u0441)").test(t)) {
return true;
}
return /(?:^|[^а-яёa-z0-9_])(посмотри|смотри|глянь)\s+(на\s+)?(результат|картинк[а-яё]*|изображен[а-яё]*|кадр|ген|реф)/i.test(t);
}
function packWantsVision(pack) {
const p = String(pack || "");
return p === "critique_image" || p === "describe_ref" || p === "compose_scene" || p === "inpaint_edit";
}
function resolveTurnIntent2(patch, userText, opts = {}) {
const S = window.SA && window.SA.session;
if (S && typeof S.resolveTurnIntent === "function") {
return S.resolveTurnIntent(patch, userText, { vetoFn: userAsksNoGenerate });
}
const vetoed = !isMachineTurn(opts) && userAsksNoGenerate(userText);
const modelAsked = !!(patch && (patch.generate === true || Array.isArray(patch.actions) && patch.actions.map(String).includes("generate")));
const generate = !vetoed && !opts.fromAutoCritique && modelAsked;
const hasLook = !!patch && (patch.look_at != null || patch.vision_from != null || patch.vision_slots != null);
const look = !!(hasLook && !vetoed && !generate);
const ask = Array.isArray(patch?.ask) ? patch.ask.map(String) : typeof patch?.ask === "string" && patch.ask ? [patch.ask] : [];
return { generate, look, vetoed, ask };
}
function stripGenerateAction(patch) {
if (!patch || typeof patch !== "object") {
return patch;
}
if (!Array.isArray(patch.actions)) {
return patch;
}
const next = patch.actions.map(String).filter((a) => a !== "generate");
if (next.length === patch.actions.length) {
return patch;
}
const out = { ...patch };
if (next.length) {
out.actions = next;
} else {
delete out.actions;
}
return out;
}
function stripLookAt(patch) {
if (!patch || typeof patch !== "object") {
return patch;
}
if (patch.look_at == null && patch.vision_from == null && patch.vision_slots == null) {
return patch;
}
const out = { ...patch };
delete out.look_at;
delete out.vision_from;
delete out.vision_slots;
return out;
}
function rememberLastPatch(patch) {
if (patch && typeof patch === "object") {
state.lastPatch = patch;
syncBuildGenButton();
}
}
function syncBuildGenButton() {
const btn = $2("sa_btn_build_gen");
if (!btn) {
return;
}
if (state.lastPatch) {
const keys = Object.keys(state.lastPatch).filter((k) => state.lastPatch[k] != null).slice(0, 6);
btn.title = `\u0415\u0441\u0442\u044C \u043F\u0430\u0442\u0447 Assistent (${keys.join(", ") || "\u2026"}) \u2192 Apply + Generate`;
btn.classList.add("sa-has-patch");
} else {
btn.title = "\u041D\u0435\u0442 \u043F\u0430\u0442\u0447\u0430 \u2014 Generate \u0441 \u0442\u0435\u043A\u0443\u0449\u0438\u043C \u043F\u0440\u043E\u043C\u043F\u0442\u043E\u043C SwarmUI";
btn.classList.remove("sa-has-patch");
}
}
function defaultPackId() {
return state.config?.assistant?.default_pack || $2("sa_pack")?.querySelector("option")?.value || "ordinary";
}
function syncModeBadge() {
const badge = $2("sa_mode_badge");
const pack = $2("sa_pack")?.value || defaultPackId();
if (!badge) {
return;
}
const shortMap = {
ordinary: "\u043E\u0431\u044B\u0447\u043D\u044B\u0439",
write_prompt: "write",
critique_image: "critique",
compose_scene: "compose",
fix_params: "params",
inpaint_edit: "inpaint",
describe_ref: "describe",
author_persona: "persona"
};
const short = shortMap[pack] || pack.replace(/_/g, " ").slice(0, 12);
badge.textContent = short;
badge.dataset.pack = pack;
badge.title = `\u0420\u0435\u0436\u0438\u043C: ${pack}`;
badge.classList.toggle("sa-mode-hot", pack === "critique_image" || pack === "inpaint_edit");
}
function syncLiveParamsBar() {
const el = $2("sa_live_params");
if (!el) {
return;
}
const w = parseInt(val("input_width") || "0", 10) || null;
const h = parseInt(val("input_height") || "0", 10) || null;
const aspect = guessAspectFromSize(w, h) || "\u2014";
const steps = val("input_steps") || "\u2014";
const cfg = val("input_cfgscale") || val("input_cfg") || "\u2014";
const seed = val("input_seed") || "\u2014";
const profile = detectKreaProfileName();
el.textContent = `${aspect} \xB7 ${w || "?"}\xD7${h || "?"} \xB7 steps ${steps} \xB7 cfg ${cfg} \xB7 ${profile} \xB7 seed ${seed}`;
}
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 } : {};
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 (state.restoringChat || !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 shouldRememberSessionParam(key, value) {
if (state.restoringChat || value == null) {
return false;
}
if (state.lastUserParamIntent) {
return true;
}
const exactVal = exactDefaultFor(key);
if (exactVal == null) {
return true;
}
return String(value) !== String(exactVal);
}
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));
}
}
if (!liveNegativePrompt()) {
const neg = exactDefaultNegative();
if (neg) {
setNegativePrompt(neg);
}
}
}
function shouldSkipSessionRollback(key, patchValue) {
if (state.restoringChat) {
return false;
}
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;
}
return String(patchValue) === String(exactVal);
}
function openAssistentTab() {
const tab = document.getElementById(TAB_BUTTON_ID);
if (tab) {
tab.click();
setTimeout(() => $2("sa_input")?.focus(), 50);
return true;
}
const pane = document.getElementById("assistent");
if (pane && typeof bootstrap !== "undefined" && bootstrap.Tab) {
try {
bootstrap.Tab.getOrCreateInstance(tab || pane).show();
} catch (e) {
}
}
setTimeout(() => $2("sa_input")?.focus(), 50);
return !!tab;
}
function historyMessageLimit() {
const turns = Math.max(1, Number(HISTORY_KEEP_TURNS) || 4);
return turns * 2;
}
function ctxApi() {
return window.SA?.context || null;
}
function getContextMemory() {
const C = ctxApi();
if (C?.normalizeContextMemory) {
return C.normalizeContextMemory(state.contextMemory);
}
return state.contextMemory && typeof state.contextMemory === "object" ? state.contextMemory : { summary: "", untilCount: 0, foldedTurns: 0, at: 0, uiCollapsed: false, promptEvalCount: null };
}
function setContextMemory(raw, { persist = true } = {}) {
const C = ctxApi();
state.contextMemory = C?.normalizeContextMemory ? C.normalizeContextMemory(raw) : raw && typeof raw === "object" ? raw : null;
if (state.chatSession && typeof state.chatSession === "object") {
state.chatSession.context_memory = state.contextMemory?.summary ? state.contextMemory : null;
}
if (persist && !state.restoringChat) {
persistHistory();
}
updateCtxChip();
if (state.ctxPanelOpen) {
renderCtxPanel();
}
}
function resetContextMemory({ persist = true } = {}) {
setContextMemory(ctxApi()?.emptyContextMemory?.() || {
summary: "",
untilCount: 0,
foldedTurns: 0,
at: 0,
uiCollapsed: false,
promptEvalCount: null
}, { persist });
}
function historyCharsForBudget(messages) {
return (messages || []).reduce((n, m) => n + String(m?.content || "").length, 0);
}
function currentBudgetEstimate(modelMessages) {
const C = ctxApi();
const mem = getContextMemory();
const memChars = mem.summary ? mem.summary.length : 0;
const hist = modelMessages || assembleOutgoingMessages();
const numCtx = Number($2("sa_num_ctx")?.value) || state.config?.assistant?.num_ctx || 16384;
const numPredict = Number(state.config?.assistant?.num_predict) || 3072;
if (!C?.estimateBudget) {
return {
used: 0,
numCtx,
level: "ok",
estimated: 0,
fromEval: false,
systemChars: state.lastSystemChars || 0,
historyChars: historyCharsForBudget(hist),
memoryChars: memChars,
threshold: Math.floor((numCtx - numPredict) * COMPRESS_AT)
};
}
return C.estimateBudget({
systemChars: state.lastSystemChars || 0,
historyChars: historyCharsForBudget(hist),
memoryChars: memChars,
numCtx,
numPredict,
charsPerToken: CHARS_PER_TOKEN,
compressAt: COMPRESS_AT,
promptEvalCount: state.lastPromptEvalCount ?? mem.promptEvalCount
});
}
function assembleOutgoingMessages({ includePendingUser } = {}) {
const C = ctxApi();
const mem = getContextMemory();
let msgs;
if (C?.assembleModelMessages) {
msgs = C.assembleModelMessages(state.history, mem, HISTORY_KEEP_TURNS).map((m) => {
let content = String(m.content || "");
if (m.role === "assistant") {
content = stripJsonFencesForHistory(content);
}
return { role: m.role, content: content.slice(0, 4e3) };
});
} else {
msgs = state.history.slice(-historyMessageLimit()).map((m) => {
let content = String(m.content || "");
if (m.role === "assistant") {
content = stripJsonFencesForHistory(content);
}
return { role: m.role, content: content.slice(0, 4e3) };
});
}
if (includePendingUser) {
msgs.push({ role: "user", content: String(includePendingUser) });
}
return msgs;
}
function buildCompressUserPrompt() {
const C = ctxApi();
const mem = getContextMemory();
const fold = C?.messagesToFold ? C.messagesToFold(state.history, mem, HISTORY_KEEP_TURNS) : [];
const lines = [];
if (mem.summary) {
lines.push("## Previous conversation memory");
lines.push(mem.summary);
lines.push("");
}
lines.push("## Dialogue chunk to fold");
for (const m of fold) {
const role = m.role === "assistant" ? "Assistant" : "User";
let content = String(m.content || "");
if (m.role === "assistant") {
content = stripJsonFencesForHistory(content);
}
content = content.slice(0, 1500);
lines.push(`### ${role}`);
lines.push(content || "(empty)");
lines.push("");
}
lines.push("Compress the chunk into the required heading format. Merge with previous memory when present.");
return { prompt: lines.join("\n"), foldCount: fold.length, fold };
}
function applyCompressResult(summaryText, foldCount, { uiCollapsed = false } = {}) {
const C = ctxApi();
const prev = getContextMemory();
const merged = C?.mergeSummary ? C.mergeSummary(prev.summary, summaryText) : String(summaryText || "").trim() || prev.summary;
const nextUntil = prev.untilCount + Math.max(0, foldCount);
setContextMemory({
summary: merged,
untilCount: nextUntil,
foldedTurns: Math.floor(nextUntil / 2),
at: Date.now(),
uiCollapsed: uiCollapsed || prev.uiCollapsed,
promptEvalCount: state.lastPromptEvalCount
});
}
function callOllamaOnce(payload) {
return new Promise((resolve, reject) => {
const fail = (err) => reject(new Error(String(err || "Chat failed")));
const ok = (data) => {
if (data?.error) {
fail(data.error);
return;
}
resolve(data || {});
};
if (typeof makeWSRequest === "function") {
let settled = false;
makeWSRequest(
"AssistentChatWS",
payload,
(data) => {
if (settled) {
return;
}
if (data?.error) {
settled = true;
fail(data.error);
return;
}
if (data?.done || data?.reply != null) {
settled = true;
ok(data);
}
},
0,
(err) => {
if (settled) {
return;
}
genericRequest("AssistentChat", payload, (data) => {
settled = true;
ok(data);
}, 0, (err2) => {
settled = true;
fail(err2 || err);
});
}
);
return;
}
genericRequest("AssistentChat", payload, ok, 0, fail);
});
}
async function runCompressTurn({ uiCollapsed = false, chatEpoch = state.chatEpoch } = {}) {
const C = ctxApi();
const mem = getContextMemory();
const fold = C?.messagesToFold ? C.messagesToFold(state.history, mem, HISTORY_KEEP_TURNS) : [];
if (!fold.length) {
return false;
}
const model = $2("sa_model")?.value;
if (!model) {
setStatus("\u0412\u044B\u0431\u0435\u0440\u0438 \u043C\u043E\u0434\u0435\u043B\u044C Ollama \u0432 \u2699");
return false;
}
const { prompt, foldCount } = buildCompressUserPrompt();
if (!foldCount) {
return false;
}
state.compressing = true;
updateCtxChip();
setBusyPhase("compressing");
setStatus("\u0421\u0436\u0438\u043C\u0430\u044E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u2026");
const persona = $2("sa_persona")?.value || "neutral";
const baseUrl = $2("sa_base_url")?.value || "http://127.0.0.1:11434";
const payload = {
baseUrl,
model,
pack: "compress_history",
persona,
includeBase: false,
messages: [{ role: "user", content: prompt }],
context_json: JSON.stringify({
compress: true,
previous_memory: mem.summary || null,
fold_count: foldCount
}),
skills: [],
embed_model: $2("sa_embed_model")?.value || state.preferredEmbed || ""
};
try {
const data = await callOllamaOnce(payload);
if (chatEpoch !== state.chatEpoch) {
return false;
}
if (data.prompt_eval_count != null) {
state.lastPromptEvalCount = Number(data.prompt_eval_count) || null;
} else if (data.raw?.prompt_eval_count != null) {
state.lastPromptEvalCount = Number(data.raw.prompt_eval_count) || null;
}
const reply = String(data.reply || "").trim();
if (!reply) {
setStatus("\u0421\u0436\u0430\u0442\u0438\u0435: \u043F\u0443\u0441\u0442\u043E\u0439 \u043E\u0442\u0432\u0435\u0442 \u043C\u043E\u0434\u0435\u043B\u0438");
return false;
}
applyCompressResult(reply, foldCount, { uiCollapsed });
if (uiCollapsed) {
renderHistoryIntoUi(state.history);
}
setStatus("\u041A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u0441\u0436\u0430\u0442");
return true;
} catch (e) {
console.warn("Assistent compress failed", e);
setStatus(`\u0421\u0436\u0430\u0442\u0438\u0435 \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C: ${e.message || e}`);
return false;
} finally {
state.compressing = false;
updateCtxChip();
}
}
function maybeAutoCompressBeforeSend(chatEpoch) {
if (!COMPRESS_AUTO) {
return Promise.resolve(false);
}
const C = ctxApi();
const mem = getContextMemory();
const msgs = assembleOutgoingMessages();
const budget = currentBudgetEstimate(msgs);
const should = C?.shouldCompress ? C.shouldCompress(budget, mem, (state.history || []).filter((m) => m && !m.systemish).length, {
keepMessages: historyMessageLimit()
}) : false;
if (!should) {
return Promise.resolve(false);
}
return runCompressTurn({ uiCollapsed: false, chatEpoch });
}
function formatCtxChipLabel(budget) {
const C = ctxApi();
const used = C?.formatTokenShort ? C.formatTokenShort(budget.used) : String(budget.used || 0);
const cap = C?.formatTokenShort ? C.formatTokenShort(budget.numCtx) : String(budget.numCtx || 0);
return `${used} / ${cap}`;
}
function updateCtxChip() {
const chip = $2("sa_ctx_chip");
if (!chip) {
return;
}
const budget = currentBudgetEstimate();
const mem = getContextMemory();
const label = formatCtxChipLabel(budget);
const textEl = chip.querySelector(".sa-ctx-chip-text");
if (textEl) {
textEl.textContent = label;
} else {
chip.textContent = label;
}
chip.classList.remove("sa-ctx-ok", "sa-ctx-warn", "sa-ctx-hot", "sa-ctx-compressing", "sa-ctx-has-mem");
if (state.compressing) {
chip.classList.add("sa-ctx-compressing");
} else {
chip.classList.add(`sa-ctx-${budget.level || "ok"}`);
}
if (mem.summary) {
chip.classList.add("sa-ctx-has-mem");
}
const src = budget.fromEval ? "\u0444\u0430\u043A\u0442 Ollama" : "\u043E\u0446\u0435\u043D\u043A\u0430";
chip.title = `\u041A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u043C\u043E\u0434\u0435\u043B\u0438 \xB7 ${src}${mem.summary ? " \xB7 \u0435\u0441\u0442\u044C \u0441\u0430\u043C\u043C\u0430\u0440\u0438" : ""}`;
const dot = chip.querySelector(".sa-ctx-dot");
if (dot) {
dot.hidden = !mem.summary;
}
}
function toggleCtxPanel(force) {
const panel = $2("sa_ctx_panel");
const chip = $2("sa_ctx_chip");
if (!panel || !chip) {
return;
}
const open = force != null ? !!force : !state.ctxPanelOpen;
state.ctxPanelOpen = open;
panel.hidden = !open;
chip.setAttribute("aria-expanded", open ? "true" : "false");
if (open) {
renderCtxPanel();
}
}
function renderCtxPanel() {
const body = $2("sa_ctx_panel_body");
const bar = $2("sa_ctx_bar_fill");
const auto = $2("sa_ctx_auto");
if (!body) {
return;
}
const budget = currentBudgetEstimate();
const mem = getContextMemory();
const layers = state.lastSystemLayers || {};
const layerRows = Object.entries(layers).filter(([k]) => k !== "total").map(([k, v]) => `<div class="sa-ctx-layer"><span>${escapeHtml2(k)}</span><span>${Number(v) || 0}</span></div>`).join("");
const keep = HISTORY_KEEP_TURNS;
const uncovered = Math.max(0, (state.history || []).filter((m) => m && !m.systemish).length - (mem.untilCount || 0));
body.innerHTML = `
<div class="sa-ctx-meta">${budget.fromEval ? "\u0422\u043E\u043A\u0435\u043D\u044B (prompt_eval)" : "\u041E\u0446\u0435\u043D\u043A\u0430 \u0442\u043E\u043A\u0435\u043D\u043E\u0432"} \xB7 \u043F\u043E\u0440\u043E\u0433 ${budget.threshold || "\u2014"}</div>
<div class="sa-ctx-layer"><span>system</span><span>${budget.systemChars || 0}</span></div>
<div class="sa-ctx-layer"><span>history</span><span>${budget.historyChars || 0}</span></div>
<div class="sa-ctx-layer"><span>memory</span><span>${budget.memoryChars || 0}</span></div>
${layerRows ? `<div class="sa-ctx-layers-label">system_layers</div>${layerRows}` : ""}
<div class="sa-ctx-see">\u041C\u043E\u0434\u0435\u043B\u044C \u0432\u0438\u0434\u0438\u0442: ${mem.summary ? `\u0441\u0430\u043C\u043C\u0430\u0440\u0438 (${mem.foldedTurns || 0} \u0445\u043E\u0434\u043E\u0432) +` : ""} \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0435 ${keep} \u0445\u043E\u0434\u043E\u0432 \xB7 \u0441\u044B\u0440\u044B\u0445 \u0432 \u043E\u043A\u043D\u0435 \u2248 ${Math.min(uncovered, historyMessageLimit())}</div>
${mem.summary ? `<pre class="sa-ctx-summary">${escapeHtml2(mem.summary.slice(0, 800))}${mem.summary.length > 800 ? "\u2026" : ""}</pre>` : '<div class="sa-ctx-meta">\u0421\u0430\u043C\u043C\u0430\u0440\u0438 \u0435\u0449\u0451 \u043D\u0435\u0442 \u2014 \u0441\u0442\u0430\u0440\u044B\u0435 \u0445\u043E\u0434\u044B \u043F\u0440\u043E\u0441\u0442\u043E \u043E\u0442\u0431\u0440\u0430\u0441\u044B\u0432\u0430\u044E\u0442\u0441\u044F.</div>'}
`;
if (bar) {
const pct = Math.max(0, Math.min(100, budget.used / (budget.numCtx || 1) * 100));
bar.style.width = `${pct}%`;
bar.dataset.level = budget.level || "ok";
}
if (auto) {
auto.checked = !!COMPRESS_AUTO;
}
updateCtxChip();
}
async function compressNowFromUi() {
if (state.busy || state.generating || state.compressing) {
setStatus("\u0417\u0430\u043D\u044F\u0442\u043E \u2014 \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u043A\u043E\u043D\u0446\u0430 \u043E\u0442\u0432\u0435\u0442\u0430");
return;
}
const model = $2("sa_model")?.value;
if (!model) {
setStatus("\u0412\u044B\u0431\u0435\u0440\u0438 \u043C\u043E\u0434\u0435\u043B\u044C Ollama \u0432 \u2699");
return;
}
state.busy = true;
setInterruptVisible(true);
startBusyUi("compressing");
const epoch = state.chatEpoch;
try {
const ok = await runCompressTurn({ uiCollapsed: true, chatEpoch: epoch });
if (!ok) {
setStatus("\u041D\u0435\u0447\u0435\u0433\u043E \u0441\u0436\u0438\u043C\u0430\u0442\u044C (\u0445\u0432\u043E\u0441\u0442 \u2264 keep)");
}
} finally {
if (epoch === state.chatEpoch) {
state.busy = false;
setInterruptVisible(state.generating);
stopBusyUi(getContextMemory().summary ? "\u041A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u0441\u0436\u0430\u0442" : "\u0413\u043E\u0442\u043E\u0432\u043E");
}
updateCtxChip();
if (state.ctxPanelOpen) {
renderCtxPanel();
}
}
}
function resetCompressionFromUi() {
resetContextMemory();
renderHistoryIntoUi(state.history);
setStatus("\u0421\u0436\u0430\u0442\u0438\u0435 \u0441\u0431\u0440\u043E\u0448\u0435\u043D\u043E \u2014 \u043C\u043E\u0434\u0435\u043B\u044C \u0441\u043D\u043E\u0432\u0430 \u0432\u0438\u0434\u0438\u0442 \u0442\u043E\u043B\u044C\u043A\u043E last-K");
if (state.ctxPanelOpen) {
renderCtxPanel();
}
}
function flashImagePane(slotId) {
const el = document.querySelector(`.sa-slot[data-id="${slotId || state.selectedSlotId}"]`);
if (!el) {
return;
}
el.classList.remove("sa-flash");
void el.offsetWidth;
el.classList.add("sa-flash");
}
function ensureBoard() {
if (state.slots.length) {
return;
}
state.slots = [
{ id: GEN_ID, type: "generate", label: "Generate", src: null, attach: false },
{ id: "ref1", type: "ref", label: "Ref 1", src: null, attach: true }
];
state.refSeq = 1;
state.selectedSlotId = "ref1";
}
function slotById(id) {
ensureBoard();
const key = normalizeSlotId(id);
return state.slots.find((s) => s.id === key) || null;
}
function generateSlot() {
return slotById(GEN_ID);
}
function refSlots() {
ensureBoard();
return state.slots.filter((s) => s.type === "ref");
}
function normalizeSlotId(id) {
const raw = String(id || "").trim().toLowerCase();
if (!raw) {
return "";
}
if (raw === "gen" || raw === "current" || raw === "live" || raw === "generation") {
return GEN_ID;
}
if (raw === "selected" || raw === "sel") {
return state.selectedSlotId;
}
const m = raw.match(/^ref\s*[_-]?\s*(\d+)$/);
if (m) {
return `ref${m[1]}`;
}
return raw;
}
function selectedSlot() {
return slotById(state.selectedSlotId) || generateSlot();
}
function selectedSrc() {
return selectedSlot()?.src || null;
}
function syncLastImageAlias() {
const attached = attachableSlots();
state.lastImageDataUrl = (attached[0] || selectedSlot() || generateSlot())?.src || null;
}
function attachableSlots() {
ensureBoard();
return state.slots.filter((s) => s.attach && s.src);
}
function visionReadySlots() {
ensureBoard();
return state.slots.filter((s) => s && s.src && !looksLikeModelPreview(s.src));
}
function setSlotSrc(id, src, { select = true, attach = null, note = null, switchTab = false, allowPreview = false } = {}) {
const slot = slotById(id);
if (!slot) {
return false;
}
const cleaned = src ? String(src).trim().split(/\s+/)[0] : null;
if (cleaned && cleaned.startsWith("#")) {
return false;
}
if (cleaned && !allowPreview && looksLikeModelPreview(cleaned)) {
setStatus("\u041F\u0440\u043E\u043F\u0443\u0441\u043A \u043F\u0440\u0435\u0432\u044C\u044E \u043C\u043E\u0434\u0435\u043B\u0438 (\u043D\u0443\u0436\u043D\u0430 \u0440\u0435\u0430\u043B\u044C\u043D\u0430\u044F \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u044F)");
return false;
}
slot.src = cleaned || null;
if (attach != null) {
slot.attach = !!attach;
} else if (slot.type === "ref" && slot.src) {
slot.attach = true;
}
if (select) {
state.selectedSlotId = slot.id;
}
syncLastImageAlias();
renderBoard();
flashImagePane(slot.id);
if (switchTab) {
openAssistentTab();
}
if (note) {
setStatus(note);
}
return true;
}
function addRefSlot({ src = null, select = true } = {}) {
ensureBoard();
if (refSlots().length >= MAX_REF_SLOTS) {
setStatus(`Max ${MAX_REF_SLOTS} reference windows`);
const empty = refSlots().find((s) => !s.src);
if (empty && src) {
return setSlotSrc(empty.id, src, { select, note: `Loaded into ${empty.label}` });
}
return empty || null;
}
state.refSeq += 1;
const id = `ref${state.refSeq}`;
const slot = {
id,
type: "ref",
label: `Ref ${state.refSeq}`,
src: src || null,
attach: !!src
};
state.slots.push(slot);
if (select) {
state.selectedSlotId = id;
}
renderBoard();
return slot;
}
function clearSlot(id, { silent = false } = {}) {
const slot = slotById(id);
if (!slot) {
return;
}
if (slot.type === "generate") {
if (!silent) {
setStatus("Generate window is live \u2014 use Snapshot gen to copy it");
}
return;
}
slot.src = null;
slot.attach = true;
syncLastImageAlias();
renderBoard();
if (!silent) {
setStatus(`${slot.label} cleared`);
}
}
function snapshotGenerateToRef() {
const src = generateSlot()?.src && !looksLikeModelPreview(generateSlot().src) ? generateSlot().src : findCurrentGenerateSrc({ allowPreview: false });
if (!src) {
setStatus("\u041D\u0435\u0442 \u0442\u0435\u043A\u0443\u0449\u0435\u0433\u043E \u043A\u0430\u0434\u0440\u0430 Generate (\u043F\u0440\u0435\u0432\u044C\u044E \u043C\u043E\u0434\u0435\u043B\u0438 \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F)");
return false;
}
const empty = refSlots().find((s) => !s.src);
let ok = false;
if (empty) {
ok = setSlotSrc(empty.id, src, { note: `\u0421\u043D\u0438\u043C\u043E\u043A \u2192 ${empty.label}` });
} else {
const created = addRefSlot({ src, select: true });
if (created?.src) {
setStatus(`\u0421\u043D\u0438\u043C\u043E\u043A \u2192 ${created.label}`);
flashImagePane(created.id);
ok = true;
} else {
const last = refSlots()[refSlots().length - 1];
if (last) {
ok = setSlotSrc(last.id, src, { note: `\u0421\u043D\u0438\u043C\u043E\u043A \u2192 ${last.label} (\u0437\u0430\u043C\u0435\u043D\u0430)` });
}
}
}
if (ok) {
setBoardTab("refs");
}
return ok;
}
function putImageOnBoard(src, { note = null, switchTab = false, preferSelected = true } = {}) {
if (!src) {
return false;
}
ensureBoard();
const sel = selectedSlot();
if (preferSelected && sel && sel.type === "ref") {
return setSlotSrc(sel.id, src, { note: note || `Loaded into ${sel.label}`, switchTab });
}
const empty = refSlots().find((s) => !s.src);
if (empty) {
return setSlotSrc(empty.id, src, { note: note || `Loaded into ${empty.label}`, switchTab });
}
const created = addRefSlot({ src, select: true });
if (created) {
if (switchTab) {
openAssistentTab();
}
if (note) {
setStatus(note);
}
return true;
}
return false;
}
function setImageFromSrc(src, opts = {}) {
return putImageOnBoard(src, opts);
}
function clearVisionImage(opts) {
clearSlot(state.selectedSlotId, opts);
}
function slotCatalog() {
ensureBoard();
return state.slots.map((s) => ({
id: s.id,
type: s.type,
label: s.label,
has_image: !!s.src,
attach: !!s.attach,
selected: s.id === state.selectedSlotId
}));
}
function lookAtIdsFromPatch(patch) {
if (!patch) {
return [];
}
const raw = patch.look_at || patch.vision_from || patch.vision_slots;
const list = Array.isArray(raw) ? raw : raw ? [raw] : [];
if (Array.isArray(patch.actions)) {
for (const a of patch.actions.map(String)) {
const m = a.match(/^look_at[_:]?(generate|ref\d+|selected)$/i);
if (m) {
list.push(m[1]);
}
}
}
return [...new Set(list.map(normalizeSlotId).filter(Boolean))];
}
function resolveSlotSrc(id) {
if (!id) {
return selectedSrc() || generateSlot()?.src || findCurrentGenerateSrc();
}
const slot = slotById(id);
if (slot?.src) {
return slot.src;
}
if (normalizeSlotId(id) === GEN_ID) {
return findCurrentGenerateSrc();
}
return null;
}
function isSwarmGenerateRunning() {
try {
if (typeof num_live_gens === "number" && num_live_gens > 0) {
return true;
}
if (typeof num_waiting_gens === "number" && num_waiting_gens > 0) {
return true;
}
} catch (e) {
}
try {
if (typeof mainGenHandler !== "undefined" && mainGenHandler) {
if (mainGenHandler.isGenerating === true || mainGenHandler.running === true) {
return true;
}
}
} catch (e) {
}
const interrupt = document.getElementById("interrupt_button") || document.getElementById("alt_interrupt_button");
if (interrupt && !interrupt.hidden && interrupt.offsetParent !== null) {
return true;
}
const genBtn = document.getElementById("generate_button") || document.getElementById("alt_generate_button");
if (genBtn && (genBtn.disabled || /interrupt/i.test(genBtn.textContent || ""))) {
return true;
}
return false;
}
function isGenerateUnavailable() {
if (state.generating || state.busy) {
return true;
}
return isSwarmGenerateRunning();
}
function syncGenerateBusy() {
const overlay = document.querySelector(".sa-slot-gen .sa-slot-busy");
if (overlay) {
const stuck = state.generating && !isSwarmGenerateRunning();
overlay.hidden = !state.generating && state.busyPhase !== "generating" || stuck;
}
const running = state.generating || state.busyPhase === "generating";
document.querySelectorAll(".sa-slot-gen-result").forEach((el) => {
const busy = el.querySelector(".sa-slot-busy");
if (!busy) {
return;
}
const hasImg = el.classList.contains("sa-has-image");
busy.hidden = !running || hasImg;
});
}
function syncPatchActionAvailability() {
const bar = document.querySelector(".sa-patch-actions.sa-patch-current");
if (!bar) {
return;
}
const locked = isGenerateUnavailable();
bar.querySelectorAll(".sa-btn-gen").forEach((btn) => {
btn.disabled = locked;
let spin = btn.querySelector(".sa-spinner");
if (locked) {
if (!spin) {
spin = document.createElement("span");
spin.className = "sa-spinner sa-spinner-btn";
spin.setAttribute("aria-hidden", "true");
btn.prepend(spin);
}
} else if (spin) {
spin.remove();
}
});
}
function retireStalePatchActions() {
document.querySelectorAll(".sa-patch-actions").forEach((el) => {
const note = document.createElement("div");
note.className = "sa-patch-stale";
note.textContent = "Superseded \u2014 use the latest proposal";
el.replaceWith(note);
});
}
function mountPatchBlock(host, patch, { silent = false } = {}) {
if (!host || !patch) {
return;
}
rememberLastPatch(patch);
const wrap = document.createElement("div");
wrap.className = "sa-patch" + (silent ? " sa-patch-auto" : "");
const details = document.createElement("details");
details.className = "sa-patch-details";
const summary = document.createElement("summary");
const keys = Object.keys(patch).filter((k) => patch[k] != null && k !== "notes" && k !== "actions");
summary.textContent = silent ? `\u041F\u0430\u0442\u0447 \u043F\u0440\u0438\u043C\u0435\u043D\u0451\u043D \xB7 ${keys.slice(0, 6).join(", ") || "generate"}` : `JSON \u043F\u0430\u0442\u0447 \xB7 ${keys.slice(0, 8).join(", ") || "\u2026"}`;
const pre = document.createElement("pre");
pre.textContent = JSON.stringify(patch, null, 2);
details.appendChild(summary);
details.appendChild(pre);
wrap.appendChild(details);
mountPatchActions(wrap, patch, { silent });
host.appendChild(wrap);
}
function mountPatchActions(parent, patch, { silent = false } = {}) {
if (!parent || !patch) {
return;
}
rememberLastPatch(patch);
retireStalePatchActions();
const wrap = parent.classList.contains("sa-patch") ? parent : null;
const host = wrap || parent;
if (silent) {
const note = document.createElement("div");
note.className = "sa-patch-actions sa-patch-silent sa-patch-current";
const willGen = Array.isArray(patch.actions) && patch.actions.map(String).includes("generate") || !!patch.generate || !!state.pendingSilentGen;
note.textContent = willGen ? "\u0412 \u0441\u0435\u0441\u0441\u0438\u044E \xB7 Generate\u2026" : "\u0412 \u0441\u0435\u0441\u0441\u0438\u044E";
host.appendChild(note);
return;
}
const actions = document.createElement("div");
actions.className = "sa-patch-actions sa-patch-current";
for (const [label, which] of [
["\u041F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C \u0432\u0441\u0451", "all"],
["\u041F\u0440\u043E\u043C\u043F\u0442", "prompt"],
["LoRAs", "loras"],
["\u041F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B", "params"]
]) {
const btn = document.createElement("button");
btn.type = "button";
btn.className = "basic-button";
btn.textContent = label;
btn.addEventListener("click", () => applyPatch(patch, which));
actions.appendChild(btn);
}
const toSession = document.createElement("button");
toSession.type = "button";
toSession.className = "basic-button";
toSession.textContent = "\u0412 \u0441\u0435\u0441\u0441\u0438\u044E";
toSession.addEventListener("click", async () => {
const S = window.SA && window.SA.session;
if (S) {
state.chatSession = S.mergeDelta(state.chatSession || S.emptySession(), patch);
}
await pushSessionToSwarm(state.chatSession);
rememberLastPatch(patch);
try {
const chat = findChat(state.activeChatId);
if (chat) {
chat.params = snapshotChatParams();
persistChatsStore();
}
} catch (e) {
}
setStatus("\u041F\u0430\u0442\u0447 \u0432 \u0441\u0435\u0441\u0441\u0438\u0438");
});
actions.appendChild(toSession);
const genBtn = document.createElement("button");
genBtn.type = "button";
genBtn.className = "basic-button sa-btn-gen";
genBtn.textContent = "\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C";
genBtn.addEventListener("click", async () => {
if (isGenerateUnavailable()) {
return;
}
startBusyUi("silent_gen");
const S = window.SA && window.SA.session;
if (S) {
state.chatSession = S.mergeDelta(state.chatSession || S.emptySession(), patch);
}
await pushSessionToSwarm(state.chatSession);
await runGenerateFromPatch({ ...patch, actions: ["generate"] }, { force: true, fromSession: true });
});
actions.appendChild(genBtn);
host.appendChild(actions);
syncPatchActionAvailability();
}
async function buildCurrentAndGenerate() {
if (state.busy || state.generating) {
setStatus("\u0417\u0430\u043D\u044F\u0442\u043E \u2014 \u043F\u043E\u0434\u043E\u0436\u0434\u0438 \u0438\u043B\u0438 \u043D\u0430\u0436\u043C\u0438 \u0421\u0442\u043E\u043F");
return;
}
if (typeof isGenerateUnavailable === "function" && isGenerateUnavailable()) {
setStatus("Generate \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u2014 \u0434\u043E\u0436\u0434\u0438\u0441\u044C SwarmUI");
return;
}
pullLiveIntoSession();
const S = window.SA && window.SA.session;
if (state.lastPatch && S) {
state.chatSession = S.mergeDelta(state.chatSession || S.emptySession(), state.lastPatch);
}
if (typeof startBusyUi === "function") startBusyUi(state.lastPatch ? "silent_gen" : "generating");
setStatus(state.lastPatch ? "\u0421\u0435\u0441\u0441\u0438\u044F \u2192 Generate\u2026" : "Generate \u0441 \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0441\u0435\u0441\u0441\u0438\u0435\u0439\u2026");
await pushSessionToSwarm(state.chatSession);
await runGenerateFromPatch({ actions: ["generate"] }, { force: true, fromSession: true });
}
function renderBoard() {
const board = $2("sa_board");
if (!board) {
return;
}
ensureBoard();
const tab = state.boardTab === "refs" ? "refs" : "generate";
const refs = refSlots();
const showVariantGrid = tab === "generate" && (state.genResults || []).length > 1;
board.classList.toggle("sa-board-many", tab === "refs" && (refs.some((s) => s.src) || refs.length > 1) || showVariantGrid);
board.classList.toggle("sa-board-gen-only", tab === "generate" && !showVariantGrid);
board.classList.toggle("sa-board-variants", showVariantGrid);
board.innerHTML = "";
if (tab === "generate" && showVariantGrid) {
for (const row of state.genResults) {
board.appendChild(buildGenResultEl(row));
}
} else {
const toShow = tab === "generate" ? state.slots.filter((s) => s.type === "generate") : state.slots.filter((s) => s.type !== "generate");
for (const slot of toShow) {
board.appendChild(buildSlotEl(slot));
}
}
if (tab === "refs" && refs.length < MAX_REF_SLOTS) {
const add = document.createElement("div");
add.className = "sa-add-cell";
add.textContent = "+ Ref";
add.title = "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u043E\u043A\u043D\u043E \u0440\u0435\u0444\u0435\u0440\u0435\u043D\u0441\u0430";
add.addEventListener("click", (e) => {
e.stopPropagation();
addRefSlot({ select: true });
});
add.addEventListener("dragover", (e) => {
e.preventDefault();
e.stopPropagation();
});
add.addEventListener("drop", async (e) => {
e.preventDefault();
e.stopPropagation();
const created = addRefSlot({ select: true });
if (created) {
state.selectedSlotId = created.id;
await handleDropDataTransfer(e.dataTransfer, created.id);
}
});
board.appendChild(add);
}
syncBoardChrome();
syncGenerateBusy();
syncLastImageAlias();
}
function buildGenResultEl(row) {
const el = document.createElement("div");
el.className = "sa-slot sa-slot-gen-result";
el.dataset.genResultId = row.id;
if (row.src) {
el.classList.add("sa-has-image");
}
if (row.id === state.selectedGenResultId) {
el.classList.add("sa-selected");
}
const bar = document.createElement("div");
bar.className = "sa-slot-bar";
const chip = document.createElement("span");
chip.className = "sa-slot-chip sa-live";
chip.textContent = row.label || row.id;
bar.appendChild(chip);
const openBtn = document.createElement("button");
openBtn.type = "button";
openBtn.className = "sa-slot-open";
openBtn.textContent = "\u041E\u0442\u043A\u0440\u044B\u0442\u044C";
openBtn.title = "\u041F\u0440\u043E\u0441\u043C\u043E\u0442\u0440";
openBtn.hidden = !row.src;
openBtn.addEventListener("click", (e) => {
e.stopPropagation();
selectGenResult(row.id, { restore: true, openViewer: true });
});
bar.appendChild(openBtn);
el.appendChild(bar);
if (row.src) {
const img = document.createElement("img");
img.alt = row.label || row.id;
img.src = row.src;
el.appendChild(img);
} else {
const empty = document.createElement("div");
empty.className = "sa-image-empty";
empty.innerHTML = '<div class="sa-empty-title">\u2026</div><div class="sa-empty-hint">\u0416\u0434\u0443 \u043A\u0430\u0434\u0440</div>';
el.appendChild(empty);
}
const busy = document.createElement("div");
busy.className = "sa-slot-busy";
const pending = !row.src && (state.generating || state.busyPhase === "generating");
busy.hidden = !pending;
busy.innerHTML = '<span class="sa-spinner" aria-hidden="true"></span>';
el.appendChild(busy);
el.addEventListener("click", () => {
const already = row.id === state.selectedGenResultId;
selectGenResult(row.id, { restore: true, openViewer: already && !!row.src });
});
el.addEventListener("dblclick", (e) => {
e.preventDefault();
if (row.src) {
selectGenResult(row.id, { restore: true, openViewer: true });
}
});
return el;
}
function ensureGenLightbox() {
let root = $2("sa_gen_lightbox");
if (root) {
return root;
}
const host = $2("swarm_assistent_root") || document.body;
root = document.createElement("div");
root.id = "sa_gen_lightbox";
root.className = "sa-lightbox";
root.hidden = true;
root.innerHTML = `
<div class="sa-lightbox-backdrop" data-lb="close"></div>
<div class="sa-lightbox-panel" role="dialog" aria-modal="true" aria-label="\u041F\u0440\u043E\u0441\u043C\u043E\u0442\u0440 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u0430">
<div class="sa-lightbox-head">
<span class="sa-lightbox-title" id="sa_lb_title"></span>
<span class="sa-lightbox-idx" id="sa_lb_idx"></span>
<button type="button" class="basic-button sa-icon-btn" data-lb="close" title="\u0417\u0430\u043A\u0440\u044B\u0442\u044C (Esc)">\u2715</button>
</div>
<div class="sa-lightbox-body">
<button type="button" class="sa-lightbox-nav sa-lightbox-prev" data-lb="prev" title="\u041D\u0430\u0437\u0430\u0434">\u2039</button>
<img id="sa_lb_img" alt="" />
<button type="button" class="sa-lightbox-nav sa-lightbox-next" data-lb="next" title="\u0412\u043F\u0435\u0440\u0451\u0434">\u203A</button>
</div>
<div class="sa-lightbox-actions">
<button type="button" class="basic-button" data-lb="to_ref">\u0412 Refs</button>
<button type="button" class="basic-button" data-lb="as_init">\u041A\u0430\u043A Init</button>
<button type="button" class="basic-button" data-lb="close">\u0417\u0430\u043A\u0440\u044B\u0442\u044C</button>
</div>
</div>`;
host.appendChild(root);
root.addEventListener("click", async (e) => {
const act = e.target?.closest?.("[data-lb]")?.getAttribute("data-lb");
if (!act) {
return;
}
e.preventDefault();
e.stopPropagation();
if (act === "close") {
closeGenLightbox();
} else if (act === "prev") {
stepGenLightbox(-1);
} else if (act === "next") {
stepGenLightbox(1);
} else if (act === "to_ref") {
const row = currentLightboxRow();
if (row?.src) {
const created = addRefSlot({ select: true });
if (created) {
created.src = row.src;
setBoardTab("refs");
renderBoard();
setStatus(`\u0421\u043D\u0438\u043C\u043E\u043A \u2192 ${created.label}`);
}
}
} else if (act === "as_init") {
const row = currentLightboxRow();
if (row?.src) {
await setInitFromSrc(row.src);
}
}
});
return root;
}
function currentLightboxRow() {
const list = (state.genResults || []).filter((r) => r.src);
if (!list.length || state.lightboxIndex < 0) {
return null;
}
return list[state.lightboxIndex] || null;
}
function syncGenLightbox() {
const root = ensureGenLightbox();
const list = (state.genResults || []).filter((r) => r.src);
const row = list[state.lightboxIndex];
if (!row) {
root.hidden = true;
return;
}
root.hidden = false;
const img = $2("sa_lb_img");
const title = $2("sa_lb_title");
const idx = $2("sa_lb_idx");
if (img) {
img.src = row.src;
img.alt = row.label || row.id;
}
if (title) {
title.textContent = row.label || row.id;
}
if (idx) {
idx.textContent = `${state.lightboxIndex + 1} / ${list.length}`;
}
}
function openGenLightbox(id) {
const list = (state.genResults || []).filter((r) => r.src);
let idx = list.findIndex((r) => r.id === id);
if (idx < 0) {
idx = 0;
}
if (!list.length) {
return;
}
state.lightboxIndex = idx;
ensureGenLightbox();
syncGenLightbox();
selectGenResult(list[idx].id, { restore: true, openViewer: false });
}
function closeGenLightbox() {
state.lightboxIndex = -1;
const root = $2("sa_gen_lightbox");
if (root) {
root.hidden = true;
}
}
function stepGenLightbox(delta) {
const list = (state.genResults || []).filter((r) => r.src);
if (list.length < 2) {
return;
}
state.lightboxIndex = (state.lightboxIndex + delta + list.length) % list.length;
const row = list[state.lightboxIndex];
if (row) {
selectGenResult(row.id, { restore: true, openViewer: false });
}
syncGenLightbox();
}
function syncBoardChrome() {
const tab = state.boardTab === "refs" ? "refs" : "generate";
$2("sa_board_tab_gen")?.classList.toggle("sa-board-tab-active", tab === "generate");
$2("sa_board_tab_refs")?.classList.toggle("sa-board-tab-active", tab === "refs");
$2("sa_board_tab_gen")?.setAttribute("aria-selected", tab === "generate" ? "true" : "false");
$2("sa_board_tab_refs")?.setAttribute("aria-selected", tab === "refs" ? "true" : "false");
const addBtn = $2("sa_btn_add_ref");
if (addBtn) {
addBtn.hidden = tab !== "refs";
}
const maskBtn = $2("sa_btn_as_mask");
const clearSlotBtn = $2("sa_btn_clear_image");
if (maskBtn) {
maskBtn.hidden = tab !== "refs";
}
if (clearSlotBtn) {
clearSlotBtn.hidden = tab !== "refs";
}
const badge = $2("sa_refs_badge");
if (badge) {
const refs = refSlots();
const withImg = refs.filter((s) => s.src).length;
const withVision = refs.filter((s) => s.src && s.attach).length;
if (withImg || withVision) {
badge.hidden = false;
badge.textContent = withVision ? `${withImg} \xB7 vision ${withVision}` : String(withImg);
} else {
badge.hidden = true;
}
}
let genBadge = $2("sa_gen_badge");
if (!genBadge) {
const genTab = $2("sa_board_tab_gen");
if (genTab) {
genBadge = document.createElement("span");
genBadge.id = "sa_gen_badge";
genBadge.className = "sa-board-badge";
genBadge.hidden = true;
genTab.appendChild(genBadge);
}
}
if (genBadge) {
const n = finishedGenResultCount();
if (n > 1) {
genBadge.hidden = false;
genBadge.textContent = String(n);
} else {
genBadge.hidden = true;
}
}
}
function setBoardTab(tab, { persist = true } = {}) {
state.boardTab = tab === "refs" ? "refs" : "generate";
if (persist) {
try {
localStorage.setItem(LS_BOARD_TAB, state.boardTab);
} catch (e) {
}
}
renderBoard();
}
function buildSlotEl(slot) {
const el = document.createElement("div");
el.className = `sa-slot${slot.type === "generate" ? " sa-slot-gen" : ""}`;
el.dataset.id = slot.id;
if (slot.src) {
el.classList.add("sa-has-image");
}
if (slot.id === state.selectedSlotId) {
el.classList.add("sa-selected");
}
const bar = document.createElement("div");
bar.className = "sa-slot-bar";
const chip = document.createElement("span");
chip.className = `sa-slot-chip${slot.type === "generate" ? " sa-live" : ""}`;
chip.textContent = slot.type === "generate" ? "Generate" : slot.label;
bar.appendChild(chip);
const attachLab = document.createElement("label");
attachLab.className = "sa-slot-attach";
attachLab.title = "Attach this window to the next chat (vision)";
const cb = document.createElement("input");
cb.type = "checkbox";
cb.checked = !!slot.attach;
cb.addEventListener("click", (e) => e.stopPropagation());
cb.addEventListener("change", (e) => {
e.stopPropagation();
slot.attach = cb.checked;
syncLastImageAlias();
});
attachLab.appendChild(cb);
attachLab.appendChild(document.createTextNode(" vision"));
bar.appendChild(attachLab);
el.appendChild(bar);
if (slot.src) {
const img = document.createElement("img");
img.alt = slot.label;
img.src = slot.src;
el.appendChild(img);
} else {
const empty = document.createElement("div");
empty.className = "sa-image-empty";
empty.innerHTML = slot.type === "generate" ? '<div class="sa-empty-title">Generate</div><div class="sa-empty-hint">\u0416\u0438\u0432\u043E\u0439 \u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440 \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u0438</div>' : '<div class="sa-empty-title">Reference</div><div class="sa-empty-hint">Drop \xB7 paste \xB7 \u0421\u043D\u0438\u043C\u043E\u043A gen</div>';
el.appendChild(empty);
}
const busy = document.createElement("div");
busy.className = "sa-slot-busy";
busy.hidden = !(slot.type === "generate" && (state.generating || state.busyPhase === "generating"));
busy.innerHTML = '<span class="sa-spinner" aria-hidden="true"></span>';
el.appendChild(busy);
el.addEventListener("click", () => {
state.selectedSlotId = slot.id;
renderBoard();
});
el.addEventListener("dragover", (e) => {
e.preventDefault();
e.stopPropagation();
el.classList.add("sa-dragover");
if (e.dataTransfer) {
e.dataTransfer.dropEffect = "copy";
}
});
el.addEventListener("dragleave", () => el.classList.remove("sa-dragover"));
el.addEventListener("drop", async (e) => {
e.preventDefault();
e.stopPropagation();
el.classList.remove("sa-dragover");
const targetId = slot.type === "generate" ? null : slot.id;
if (slot.type === "generate") {
const created = addRefSlot({ select: true });
await handleDropDataTransfer(e.dataTransfer, created?.id);
setBoardTab("refs");
} else {
await handleDropDataTransfer(e.dataTransfer, targetId);
}
});
return el;
}
function syncGenerateSlot() {
const slot = generateSlot();
if (!slot) {
return;
}
if (scrubPreviewFromGenerateSlot()) {
renderBoard();
}
const src = findCurrentGenerateSrc();
if (src && src !== slot.src) {
slot.src = src;
const img = document.querySelector(".sa-slot-gen img");
const empty = document.querySelector(".sa-slot-gen .sa-image-empty");
const frame = document.querySelector(".sa-slot-gen");
if (img) {
img.src = src;
} else if (frame) {
renderBoard();
return;
}
if (empty) {
empty.hidden = true;
}
frame?.classList.add("sa-has-image");
} else if (src && slot.src === src) {
if (state.generating && !isSwarmGenerateRunning()) {
const img = document.querySelector(".sa-slot-gen img");
if (img) {
const bump = src.includes("?") ? `${src}&sa_t=${Date.now()}` : `${src}?sa_t=${Date.now()}`;
img.src = bump;
}
}
} else if (!src && !slot.src) {
const empty = document.querySelector(".sa-slot-gen .sa-image-empty");
const frame = document.querySelector(".sa-slot-gen");
const img = document.querySelector(".sa-slot-gen img");
if (img) {
img.remove();
}
if (empty) {
empty.hidden = false;
}
frame?.classList.remove("sa-has-image", "sa-attached");
}
syncGenerateBusy();
syncPatchActionAvailability();
}
function maybeWelcome() {
if (localStorage.getItem(LS_WELCOMED) === "1") {
return;
}
if (!$2("sa_messages")) {
return;
}
localStorage.setItem(LS_WELCOMED, "1");
const box = $2("sa_messages");
hideChatEmpty();
const div = document.createElement("div");
div.className = "sa-msg assistant sa-welcome";
div.innerHTML = WELCOME_HTML;
box.appendChild(div);
scrollMessagesToBottom({ force: true });
}
function chatUid() {
return `c_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
}
function stripJsonFencesForHistory(content) {
return String(content || "").replace(/```(?:json)?\s*[\s\S]*?```/gi, "").replace(/###\s*Critique\b[\s\S]*?(?=###|$)/gi, "").replace(/###\s*JSON\s*Patch\b[\s\S]*$/gi, "").replace(/\n{3,}/g, "\n\n").trim();
}
function slimHistoryMessages(list) {
return (list || []).filter((m) => m && (m.role === "user" || m.role === "assistant") && !m.systemish).slice(-MAX_CHAT_MSGS).map((m) => {
let content = String(m.content || "");
if (m.role === "assistant") {
content = stripJsonFencesForHistory(content);
}
return {
role: m.role,
content: content.slice(0, 4e3),
persona: m.persona || void 0,
pack: m.pack || void 0
};
});
}
function titleFromMessages(messages) {
const u = (messages || []).find((m) => m.role === "user" && m.content);
const t = String(u?.content || "").replace(/\s+/g, " ").trim();
return t ? t.slice(0, 52) : "\u041D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442";
}
function readLiveGenFields() {
let loras = [];
try {
if (typeof loraHelper !== "undefined" && loraHelper && Array.isArray(loraHelper.selected)) {
loras = loraHelper.selected.map((l) => ({
name: l.name || l,
weight: loraHelper.loraWeightPref && loraHelper.loraWeightPref[l.name || l] || 1
}));
}
} catch (e) {
}
let checkpoint = null;
try {
if (typeof resolveCurrentCheckpoint === "function") {
const m = resolveCurrentCheckpoint();
if (m && (m.name || m.title)) {
checkpoint = {
name: m.name || m.title || null,
architecture: m.architecture || m.compat_class || m.class || null,
title: m.title || null
};
}
}
} catch (e) {
}
return {
prompt: val("alt_prompt_textbox") || val("input_prompt") || "",
negative: val("input_negativeprompt") || val("alt_negativeprompt_textbox") || "",
width: parseInt(val("input_width") || "0", 10) || null,
height: parseInt(val("input_height") || "0", 10) || null,
steps: parseInt(val("input_steps") || "0", 10) || null,
cfg: parseFloat(val("input_cfgscale") || val("input_cfg") || "") || null,
sigma_shift: parseFloat(val("input_sigmashift") || "") || null,
seed: val("input_seed") || null,
sampler: val("input_sampler") || null,
scheduler: val("input_scheduler") || null,
batch: parseInt(val("input_images") || val("input_batchsize") || "0", 10) || null,
loras,
checkpoint
};
}
function boardSnapshotForSession() {
if (typeof ensureBoard === "function") ensureBoard();
return {
slots: (state.slots || []).map((s) => ({
id: s.id,
type: s.type,
label: s.label,
src: s.src || null,
attach: !!s.attach,
note: s.note || null
})),
selectedSlotId: state.selectedSlotId || "ref1",
genResults: Array.isArray(state.genResults) ? state.genResults.map((r) => ({
id: r.id,
label: r.label,
src: r.src || null,
patch: r.patch || null
})) : [],
selectedGenResultId: state.selectedGenResultId || null,
refSeq: state.refSeq || 1
};
}
function pullLiveIntoSession() {
const S = window.SA && window.SA.session;
if (!S || typeof S.snapshotFromLive !== "function") return state.chatSession;
state.chatSession = S.snapshotFromLive({
genFields: readLiveGenFields(),
board: boardSnapshotForSession(),
persona: $2("sa_persona")?.value || "neutral",
pack: $2("sa_pack")?.value || "ordinary",
context_memory: getContextMemory()
});
return state.chatSession;
}
async function pushSessionToSwarm(session) {
const S = window.SA && window.SA.session;
const sess = session || state.chatSession || S && S.emptySession && S.emptySession();
if (!sess || !sess.gen) return;
if (typeof applyPatch === "function") await applyPatch({ ...sess.gen }, "all");
try {
const name = sess.gen.checkpoint?.name || (typeof sess.gen.checkpoint === "string" ? sess.gen.checkpoint : null);
if (name && typeof currentModelHelper !== "undefined" && currentModelHelper?.setModel) {
currentModelHelper.setModel(name);
}
} catch (e) {
}
if (sess.pack && typeof setPackValue === "function") setPackValue(sess.pack, { flash: false });
if (sess.persona && typeof applyPersonaForChat === "function") {
await applyPersonaForChat(sess.persona, { quiet: true });
}
state.chatSession = sess;
}
function snapshotChatParams() {
const S = window.SA && window.SA.session;
const session = pullLiveIntoSession();
if (S && typeof S.toPersistParams === "function") return S.toPersistParams(session);
return session || {};
}
async function restoreChatParams(params) {
state.restoringChat = true;
try {
state.sessionExact = {};
state.lastPatch = null;
state.lastUserParamIntent = false;
const S = window.SA && window.SA.session;
state.chatSession = S && typeof S.sessionFromLegacyParams === "function" ? S.sessionFromLegacyParams(params) : params && params.gen ? params : S && S.emptySession ? S.emptySession() : { gen: {}, board: {} };
if (!params || typeof params !== "object") {
if (typeof clearGenResults === "function") clearGenResults();
if (typeof renderBoard === "function") renderBoard();
setContextMemory(null, { persist: false });
return { restored: false };
}
await pushSessionToSwarm(state.chatSession);
const board = state.chatSession.board || {};
if (Array.isArray(board.slots) && board.slots.length) {
state.slots = board.slots.map((s) => ({ ...s }));
if (board.selectedSlotId) state.selectedSlotId = board.selectedSlotId;
if (board.refSeq) state.refSeq = board.refSeq;
}
if (Array.isArray(board.genResults) && board.genResults.length) {
state.genResults = board.genResults.map((r, i) => ({
id: r.id || "var" + (i + 1),
label: r.label || "\u0412\u0430\u0440\u0438\u0430\u043D\u0442 " + (i + 1),
src: r.src || null,
patch: r.patch || null
}));
state.selectedGenResultId = board.selectedGenResultId || state.genResults.find((x) => x.src)?.id || state.genResults[0]?.id || null;
const selected = state.genResults.find((x) => x.id === state.selectedGenResultId);
const gen = typeof generateSlot === "function" ? generateSlot() : null;
if (gen && selected?.src) gen.src = selected.src;
} else if (typeof clearGenResults === "function") {
clearGenResults();
}
if (typeof syncBuildGenButton === "function") syncBuildGenButton();
if (typeof syncLiveParamsBar === "function") syncLiveParamsBar();
if (typeof syncModeBadge === "function") syncModeBadge();
if (typeof renderLoraChips === "function") renderLoraChips();
if (typeof renderBoard === "function") renderBoard();
setContextMemory(state.chatSession?.context_memory || null);
return { restored: true };
} finally {
state.restoringChat = false;
}
}
function applyPersonaForChat(personaId, { quiet = false } = {}) {
const id = String(personaId || "neutral").trim() || "neutral";
return new Promise((resolve) => {
const sel = $2("sa_persona");
if (sel && [...sel.options].some((o) => o.value === id)) {
sel.value = id;
}
if (!quiet) {
onPersonaChanged();
resolve();
return;
}
saveSettings();
if (typeof genericRequest !== "function") {
resolve();
return;
}
const packKeep = $2("sa_pack")?.value;
genericRequest(
"AssistentGetConfig",
{ persona: id },
(data) => {
applyConfigPayload(data, { applyDefaults: false });
if (sel && [...sel.options].some((o) => o.value === id)) {
sel.value = id;
}
if (packKeep) {
setPackValue(packKeep, { flash: false });
}
resolve();
},
0,
() => resolve()
);
});
}
function persistChatsStore() {
try {
const chats = (state.chats || []).filter((c) => c && c.id && (c.id === state.activeChatId || (c.messages || []).length > 0)).slice().sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0)).slice(0, MAX_CHATS).map((c) => ({
id: c.id,
title: c.title || "\u041D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442",
createdAt: c.createdAt || Date.now(),
updatedAt: c.updatedAt || Date.now(),
messages: slimHistoryMessages(c.messages || []),
params: c.params || null
}));
state.chats = chats;
localStorage.setItem(LS_CHATS2, JSON.stringify({ version: 1, chats }));
saveActiveChatToDisk();
} catch (e) {
console.warn("Assistent: persist chats failed", e);
try {
const slim = (state.chats || []).filter((c) => c && c.id && (c.id === state.activeChatId || (c.messages || []).length > 0)).sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0)).slice(0, 12).map((c) => ({
...c,
messages: slimHistoryMessages(c.messages).slice(-historyMessageLimit()).map((m) => ({
...m,
content: String(m.content || "").slice(0, 1500)
}))
}));
state.chats = slim;
localStorage.setItem(LS_CHATS2, JSON.stringify({ version: 1, chats: slim }));
saveActiveChatToDisk();
} catch (e2) {
console.warn("Assistent: chats quota fallback failed", e2);
}
}
}
function saveActiveChatToDisk() {
const persist = diskPersist();
if (!persist) {
return;
}
for (const chat of state.chats || []) {
if (!chat?.id || !(chat.messages || []).length) {
continue;
}
persist.saveChat(chat);
}
}
function loadChatsStore() {
state.chats = [];
try {
const raw = localStorage.getItem(LS_CHATS2);
if (raw) {
const parsed = JSON.parse(raw);
if (Array.isArray(parsed?.chats)) {
state.chats = parsed.chats.filter((c) => c && c.id);
}
}
} catch (e) {
}
}
async function loadChatsFromDisk() {
const persist = diskPersist();
if (!persist) {
return;
}
let diskChats = null;
try {
diskChats = await persist.loadChats();
} catch (e) {
console.warn("Assistent: disk chats failed", e);
return;
}
if (!Array.isArray(diskChats)) {
return;
}
const byId = /* @__PURE__ */ new Map();
for (const c of state.chats || []) {
if (c?.id) {
byId.set(c.id, c);
}
}
for (const c of diskChats) {
if (!c?.id) {
continue;
}
const prev = byId.get(c.id);
if (!prev || (c.updatedAt || 0) >= (prev.updatedAt || 0)) {
byId.set(c.id, c);
}
}
state.chats = [...byId.values()].sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0)).slice(0, MAX_CHATS);
try {
localStorage.setItem(LS_CHATS2, JSON.stringify({ version: 1, chats: state.chats }));
} catch (e) {
}
}
function findChat(id) {
return (state.chats || []).find((c) => c.id === id) || null;
}
function saveActiveChatToStore({ dropEmpty = false } = {}) {
if (!state.activeChatId || state.restoringChat) {
return;
}
const chat = findChat(state.activeChatId);
if (!chat) {
return;
}
chat.messages = slimHistoryMessages(state.history);
chat.params = snapshotChatParams();
chat.updatedAt = Date.now();
chat.title = titleFromMessages(chat.messages);
if (dropEmpty && !chat.messages.length) {
state.chats = state.chats.filter((c) => c.id !== chat.id);
if (state.activeChatId === chat.id) {
state.activeChatId = null;
}
}
persistChatsStore();
}
function resetMessagesUi(emptyHint) {
const box = $2("sa_messages");
if (!box) {
return;
}
box.innerHTML = "";
const empty = document.createElement("div");
empty.className = "sa-chat-empty";
empty.id = "sa_chat_empty";
empty.innerHTML = emptyHint || '<div class="sa-chat-empty-title">\u041D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442</div><div class="sa-chat-empty-hint">\u041F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B Generate \u043E\u0441\u0442\u0430\u044E\u0442\u0441\u044F \u043A\u0430\u043A \u0441\u0435\u0439\u0447\u0430\u0441.<br><strong>+</strong> \u2014 \u0435\u0449\u0451 \u043E\u0434\u0438\u043D \u0447\u0430\u0442 \xB7 <strong>\u0418\u0441\u0442\u043E\u0440\u0438\u044F</strong> \u2014 \u0432\u0435\u0440\u043D\u0443\u0442\u044C\u0441\u044F \u043A \u043F\u0440\u043E\u0448\u043B\u043E\u043C\u0443 (\u0441 \u0435\u0433\u043E \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u0430\u043C\u0438).</div>';
box.appendChild(empty);
}
function renderHistoryIntoUi(messages) {
const box = $2("sa_messages");
if (!box) {
return;
}
box.innerHTML = "";
const list = slimHistoryMessages(messages);
if (!list.length) {
resetMessagesUi();
updateCtxChip();
return;
}
const mem = getContextMemory();
let start = 0;
if (mem.uiCollapsed && mem.summary && mem.untilCount > 0) {
const folded = list.slice(0, Math.min(mem.untilCount, list.length));
start = folded.length;
const details = document.createElement("details");
details.className = "sa-msg sa-msg-compress";
const summary = document.createElement("summary");
summary.textContent = `\u0421\u0436\u0430\u0442\u044B\u0439 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \xB7 ${mem.foldedTurns || Math.floor(folded.length / 2)} \u0445\u043E\u0434\u043E\u0432`;
details.appendChild(summary);
const body = document.createElement("div");
body.className = "sa-msg-compress-body";
const pre = document.createElement("pre");
pre.className = "sa-ctx-summary";
pre.textContent = mem.summary;
body.appendChild(pre);
for (const m of folded) {
const row = document.createElement("div");
row.className = `sa-msg-compress-row sa-msg-compress-${m.role}`;
row.textContent = `${m.role === "assistant" ? "\u0410\u0441\u0441\u0438\u0441\u0442\u0435\u043D\u0442" : "\u0412\u044B"}: ${String(m.content || "").slice(0, 500)}`;
body.appendChild(row);
}
details.appendChild(body);
box.appendChild(details);
}
for (let i = start; i < list.length; i++) {
const m = list[i];
if (m.role === "user") {
appendMessage("user", m.content, null, null, { historical: true });
} else {
appendMessage("assistant", m.content, null, null, {
persona: m.persona ? { id: m.persona, title: m.persona } : null,
pack: m.pack,
historical: true
});
}
}
updateCtxChip();
}
function updateSessionLabel() {
const el = $2("sa_session_label");
if (!el) {
return;
}
const chat = findChat(state.activeChatId);
el.textContent = chat?.title || "\u041D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442";
el.title = (chat?.title || "\u041D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442") + " \u2014 \u043A\u043B\u0438\u043A: \u0418\u0441\u0442\u043E\u0440\u0438\u044F";
}
function savedChatsCount() {
return (state.chats || []).filter((c) => (c.messages || []).length > 0).length;
}
function syncHistoryBadge() {
const btn = $2("sa_btn_chats");
if (!btn) {
return;
}
const n = savedChatsCount();
btn.textContent = n > 0 ? `\u0418\u0441\u0442\u043E\u0440\u0438\u044F (${n})` : "\u0418\u0441\u0442\u043E\u0440\u0438\u044F";
btn.title = n > 0 ? `\u0421\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u044B\u0445 \u0447\u0430\u0442\u043E\u0432: ${n}. \u041F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435 \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0435\u0442 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B.` : "\u0418\u0441\u0442\u043E\u0440\u0438\u044F \u0447\u0430\u0442\u043E\u0432 (\u043F\u043E\u043A\u0430 \u043F\u0443\u0441\u0442\u043E)";
}
function formatChatWhen(ts) {
if (!ts) {
return "";
}
try {
return new Date(ts).toLocaleString(void 0, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
} catch (e) {
return "";
}
}
function chatMatchesQuery(chat, q) {
if (!q) {
return true;
}
const title = String(chat?.title || "").toLowerCase();
if (title.includes(q)) {
return true;
}
const msgs = chat?.messages || [];
for (const m of msgs) {
if (String(m?.content || "").toLowerCase().includes(q)) {
return true;
}
}
return false;
}
function renderChatsList() {
const root = $2("sa_chats_list");
if (!root) {
return;
}
root.innerHTML = "";
syncHistoryBadge();
const q = (state.chatsQuery || "").trim().toLowerCase();
let chats = (state.chats || []).slice().sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0)).filter((c) => (c.messages || []).length > 0 || (c.messages_count || 0) > 0);
if (q) {
const local = chats.filter((c) => chatMatchesQuery(c, q));
const seen = new Set(local.map((c) => c.id));
const extra = (state.chatsSearchHits || []).filter((h) => h && h.id && !seen.has(h.id));
chats = local.concat(extra);
}
if (!chats.length) {
root.innerHTML = q ? '<div class="sa-chats-empty">\u041D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043D\u0430\u0448\u043B\u043E\u0441\u044C.</div>' : '<div class="sa-chats-empty">\u041F\u043E\u043A\u0430 \u043F\u0443\u0441\u0442\u043E. \u041D\u0430\u043F\u0438\u0448\u0438 \u0447\u0442\u043E-\u043D\u0438\u0431\u0443\u0434\u044C \u0432 \u0447\u0430\u0442 \u2014 \u043E\u043D \u043F\u043E\u044F\u0432\u0438\u0442\u0441\u044F \u0437\u0434\u0435\u0441\u044C. \u041F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435 \u0432\u0435\u0440\u043D\u0451\u0442 \u0438 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F, \u0438 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B Generate.</div>';
return;
}
for (const c of chats) {
const row = document.createElement("div");
row.className = "sa-chat-row" + (c.id === state.activeChatId ? " sa-chat-row-active" : "");
row.dataset.id = c.id;
const n = (c.messages || []).length || Number(c.messages_count) || 0;
const bits = [];
if (c.params?.width && c.params?.height) {
bits.push(`${c.params.width}\xD7${c.params.height}`);
}
if (c.params?.steps != null) {
bits.push(`steps ${c.params.steps}`);
}
if (c.params?.cfg != null) {
bits.push(`cfg ${c.params.cfg}`);
}
if (Array.isArray(c.params?.loras) && c.params.loras.length) {
bits.push(`LoRA ${c.params.loras.length}`);
}
const noParams = !c.params ? " \xB7 \u0431\u0435\u0437 \u0441\u043D\u0438\u043C\u043A\u0430 params" : "";
row.innerHTML = `<button type="button" class="sa-chat-row-main" data-open="1"><span class="sa-chat-row-title">${escapeHtml2(c.title || "\u0427\u0430\u0442")}</span><span class="sa-chat-row-meta">${escapeHtml2([formatChatWhen(c.updatedAt), `${n} \u0441\u043E\u043E\u0431\u0449.`, bits.join(" \xB7 ")].filter(Boolean).join(" \xB7 ") + noParams)}</span></button><button type="button" class="sa-chat-row-del" data-del="1" title="\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0447\u0430\u0442">\xD7</button>`;
root.appendChild(row);
}
}
function setChatsPanelOpen(open) {
state.chatsPanelOpen = !!open;
state.chatsDrawerOpen = state.chatsPanelOpen;
const panel = $2("sa_chats_panel");
const btn = $2("sa_btn_chats");
const root = $2("swarm_assistent_root");
if (panel) {
panel.hidden = !state.chatsPanelOpen;
}
btn?.setAttribute("aria-expanded", state.chatsPanelOpen ? "true" : "false");
btn?.classList.toggle("sa-sessions-toggle-active", state.chatsPanelOpen);
root?.classList.toggle("sa-drawer-open", state.chatsPanelOpen);
localStorage.setItem(LS_CHATS_DRAWER, state.chatsPanelOpen ? "1" : "0");
if (state.chatsPanelOpen) {
saveActiveChatToStore();
const search = $2("sa_chats_search");
if (search) {
search.value = state.chatsQuery || "";
search.focus();
}
renderChatsList();
}
saveUiStateToDisk();
}
async function startNewChat({ saveCurrent = true, force = false } = {}) {
if (!force && (state.busy || state.generating)) {
setStatus("\u0417\u0430\u043D\u044F\u0442\u043E \u2014 \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u043A\u043E\u043D\u0446\u0430 \u043E\u0442\u0432\u0435\u0442\u0430 \u0438\u043B\u0438 \u0421\u0442\u043E\u043F");
return;
}
if (force) {
abortInFlightWork({ status: "" });
}
if (saveCurrent) {
saveActiveChatToStore({ dropEmpty: true });
}
state.sessionExact = {};
state.lastUserParamIntent = false;
state.pendingSilentGen = false;
state.lastPatch = null;
clearGenResults();
const chat = {
id: chatUid(),
title: "\u041D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442",
createdAt: Date.now(),
updatedAt: Date.now(),
messages: [],
params: snapshotChatParams()
};
state.chats.unshift(chat);
state.activeChatId = chat.id;
state.history = [];
resetTurnHops();
state.packUserTouched = false;
state.pendingPersonaNote = null;
if (state.streamEl) {
try {
state.streamEl.remove();
} catch (e) {
}
state.streamEl = null;
}
resetContextMemory({ persist: false });
syncBuildGenButton();
resetMessagesUi();
persistChatsStore();
updateSessionLabel();
renderBoard();
syncHistoryBadge();
renderChatsList();
updateCtxChip();
setStatus("\u041D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442 \u2014 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B Generate \u043A\u0430\u043A \u0441\u0435\u0439\u0447\u0430\u0441");
maybeWelcome();
}
async function switchToChat(id) {
if (!id || id === state.activeChatId) {
return;
}
if (state.busy || state.generating) {
setStatus("\u0417\u0430\u043D\u044F\u0442\u043E \u2014 \u043D\u0435\u043B\u044C\u0437\u044F \u0441\u043C\u0435\u043D\u0438\u0442\u044C \u0447\u0430\u0442 \u0441\u0435\u0439\u0447\u0430\u0441");
return;
}
saveActiveChatToStore({ dropEmpty: true });
let chat = findChat(id);
if (!chat || !(chat.messages || []).length) {
try {
const full = await diskPersist()?.getChat?.(id);
if (full) {
const idx = (state.chats || []).findIndex((c) => c.id === id);
if (idx >= 0) {
state.chats[idx] = full;
} else {
state.chats.unshift(full);
}
chat = full;
}
} catch (e) {
console.warn("Assistent: getChat failed", id, e);
}
}
if (!chat) {
setStatus("\u0427\u0430\u0442 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D");
return;
}
state.activeChatId = chat.id;
state.history = slimHistoryMessages(chat.messages);
resetTurnHops();
state.packUserTouched = false;
state.pendingPersonaNote = null;
state.pendingSilentGen = false;
state.lastUserParamIntent = false;
if (state.streamEl) {
try {
state.streamEl.remove();
} catch (e) {
}
state.streamEl = null;
}
setContextMemory(chat.params?.context_memory || null, { persist: false });
renderHistoryIntoUi(state.history);
const result = await restoreChatParams(chat.params);
updateSessionLabel();
syncHistoryBadge();
renderChatsList();
setView("chat");
if (result?.restored) {
setStatus(`\u0427\u0430\u0442 \xAB${chat.title}\xBB \xB7 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u044B`);
} else {
setStatus(`\u0427\u0430\u0442 \xAB${chat.title}\xBB \xB7 \u0441\u043D\u0438\u043C\u043E\u043A \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u043E\u0432 \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u2014 Generate \u043D\u0435 \u043C\u0435\u043D\u044F\u043B\u0441\u044F`);
}
}
function deleteChat(id) {
if (!id) {
return;
}
const wasActive = id === state.activeChatId;
state.chats = state.chats.filter((c) => c.id !== id);
if (wasActive) {
state.activeChatId = null;
}
diskPersist()?.deleteChat(id)?.catch?.((e) => console.warn("Assistent: disk delete failed", e));
persistChatsStore();
syncHistoryBadge();
if (wasActive) {
startNewChat({ saveCurrent: false, force: true });
} else {
renderChatsList();
}
}
async function initChatSessions() {
loadChatsStore();
await loadChatsFromDisk();
startNewChat({ saveCurrent: false, force: true });
syncHistoryBadge();
renderChatsList();
}
function persistHistory() {
if (state.restoringChat) {
return;
}
if (!state.activeChatId) {
const chat2 = {
id: chatUid(),
title: "\u041D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442",
createdAt: Date.now(),
updatedAt: Date.now(),
messages: [],
params: snapshotChatParams()
};
state.chats.unshift(chat2);
state.activeChatId = chat2.id;
}
const chat = findChat(state.activeChatId);
if (!chat) {
return;
}
chat.messages = slimHistoryMessages(state.history);
chat.params = snapshotChatParams();
chat.updatedAt = Date.now();
chat.title = titleFromMessages(chat.messages);
persistChatsStore();
updateSessionLabel();
syncHistoryBadge();
}
function clearPersistedHistory() {
if (state.activeChatId) {
const chat = findChat(state.activeChatId);
if (chat) {
chat.messages = [];
chat.title = "\u041D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442";
chat.params = snapshotChatParams();
chat.updatedAt = Date.now();
}
persistChatsStore();
}
updateSessionLabel();
renderChatsList();
}
function clearChatHistory() {
abortInFlightWork({ status: "" });
state.history = [];
resetTurnHops();
state.packUserTouched = false;
state.pendingPersonaNote = null;
state.sessionExact = {};
state.lastUserParamIntent = false;
state.pendingSilentGen = false;
state.lastPatch = null;
clearGenResults();
syncBuildGenButton();
clearPersistedHistory();
resetContextMemory({ persist: false });
resetMessagesUi('<div class="sa-chat-empty-title">\u0427\u0430\u0442 \u043E\u0447\u0438\u0449\u0435\u043D</div><div class="sa-chat-empty-hint">\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F \u0441\u0431\u0440\u043E\u0448\u0435\u043D\u044B. \u041F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B Generate \u043D\u0430 \u043C\u0435\u0441\u0442\u0435. <strong>+</strong> \u2014 \u043D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442 \u0432 \u0418\u0441\u0442\u043E\u0440\u0438\u044E, <strong>\u0418\u0441\u0442\u043E\u0440\u0438\u044F</strong> \u2014 \u043F\u0440\u043E\u0448\u043B\u044B\u0435 \u0434\u0438\u0430\u043B\u043E\u0433\u0438.</div>');
setStatus("\u0427\u0430\u0442 \u043E\u0447\u0438\u0449\u0435\u043D");
updateSessionLabel();
syncHistoryBadge();
renderBoard();
updateCtxChip();
}
function hideSlashMenu() {
const menu = $2("sa_slash_menu");
if (menu) {
menu.hidden = true;
menu.innerHTML = "";
}
state.slashIndex = 0;
}
function slashMatches(text) {
const t = String(text || "");
if (!t.startsWith("/")) {
return [];
}
const q = t.toLowerCase();
return SLASH_COMMANDS.filter((c) => c.cmd.toLowerCase().startsWith(q) || q === "/" || c.cmd.toLowerCase().includes(q.slice(1)));
}
function renderSlashMenu(items) {
const menu = $2("sa_slash_menu");
if (!menu) {
return;
}
if (!items.length) {
hideSlashMenu();
return;
}
menu.hidden = false;
menu.innerHTML = "";
state.slashIndex = Math.max(0, Math.min(state.slashIndex, items.length - 1));
items.forEach((item, i) => {
const btn = document.createElement("button");
btn.type = "button";
btn.className = "sa-slash-item" + (i === state.slashIndex ? " sa-slash-active" : "");
btn.setAttribute("role", "option");
btn.innerHTML = `<code>${escapeHtml2(item.cmd.trim())}</code> \u2014 ${escapeHtml2(item.hint)}`;
btn.addEventListener("mousedown", (e) => {
e.preventDefault();
applySlashPick(item);
});
menu.appendChild(btn);
});
}
function applySlashPick(item) {
const input = $2("sa_input");
if (!input || !item) {
return;
}
input.value = item.cmd;
hideSlashMenu();
input.focus();
const pos = input.value.length;
input.setSelectionRange(pos, pos);
}
function updateSlashMenuFromInput() {
const text = $2("sa_input")?.value || "";
if (!text.startsWith("/") || text.includes("\n") || /\s/.test(text.trim().slice(1)) && !text.endsWith(" ")) {
const token2 = text.split(/\s/)[0] || "";
if (!token2.startsWith("/") || text.includes(" ") && !SLASH_COMMANDS.some((c) => c.cmd.startsWith(token2))) {
if (!(token2.startsWith("/") && !text.includes(" "))) {
hideSlashMenu();
return;
}
}
}
const token = text.split(/\s/)[0] || "";
if (!token.startsWith("/") || text.indexOf(" ") > 0) {
hideSlashMenu();
return;
}
renderSlashMenu(slashMatches(token));
}
function onPersonaChanged() {
const id = $2("sa_persona")?.value || "neutral";
state.sessionExact = {};
state.lastUserParamIntent = false;
saveSettings();
loadConfig(id, (data) => {
const title = data?.personas?.find((p) => p.id === id)?.title || (state.personas || []).find((p) => p.id === id)?.title || id;
if (data?.personas) {
state.personas = data.personas;
}
appendSystemNote(`\u0422\u043E\u043D \u2192 ${title}`);
state.pendingPersonaNote = `Persona is now ${id} (${title}). Adopt this voice from now on.`;
if (data?.assistant?.default_pack && $2("sa_pack") && !state.packUserTouched) {
const packId = data.assistant.default_pack;
if ([...$2("sa_pack").options || []].some((o) => o.value === packId)) {
$2("sa_pack").value = packId;
}
}
fillEmptyParamsFromExact();
renderPersonaControls(data?.controls || {}, data?.control_values || data?.exact?.controls || {});
syncPersonaDeleteButton(data?.persona_source || data?.personas?.find((p) => p.id === id)?.source);
if (state.view === "settings") {
if (state.settingsTab === "user") {
refreshUserPrefs();
}
if (state.settingsTab === "craft") {
renderMemoryList();
}
if (state.settingsTab === "more") {
fillKnobsFromConfig(data);
updateCtxChip();
}
}
});
}
function countPromptImages() {
try {
const box = document.getElementById("alt_prompt_textbox") || document.getElementById("input_prompt");
if (!box) {
return 0;
}
const text = box.value || "";
const matches = text.match(/<image(?:\s|\/|>)/gi) || text.match(/data:image\//gi);
return matches ? matches.length : 0;
} catch (e) {
return 0;
}
}
function triggerChangeForEl(el) {
if (!el) {
return;
}
if (typeof triggerChangeFor === "function") {
triggerChangeFor(el);
return;
}
el.dispatchEvent(new Event("input", { bubbles: true }));
el.dispatchEvent(new Event("change", { bubbles: true }));
}
function openInitImageGroup() {
try {
const initEl = document.getElementById("input_initimage");
if (initEl && typeof toggleGroupOpen === "function") {
toggleGroupOpen(initEl, true);
}
} catch (e) {
}
const toggler = document.getElementById("input_group_content_initimage_toggle");
if (toggler) {
toggler.checked = true;
triggerChangeForEl(toggler);
}
}
function hasFileParam(id) {
const el = document.getElementById(id);
return !!(el && el.files && el.files.length > 0);
}
function clearFileParam(id) {
const el = document.getElementById(id);
if (!el) {
return false;
}
try {
el.value = "";
if (el.files && typeof DataTransfer !== "undefined") {
el.files = new DataTransfer().files;
}
} catch (e) {
}
triggerChangeForEl(el);
return true;
}
function guessImageMime(src) {
const s = String(src || "");
if (s.startsWith("data:image/")) {
const m = s.match(/^data:(image\/[a-z0-9.+-]+)/i);
return m && m[1] || "image/png";
}
const path = s.split("?")[0];
const ext = path.substring(path.lastIndexOf(".") + 1).toLowerCase();
if (ext === "jpg" || ext === "jpeg") {
return "image/jpeg";
}
if (ext === "webp") {
return "image/webp";
}
if (ext === "gif") {
return "image/gif";
}
return "image/png";
}
async function srcToBlob(src) {
if (!src) {
return null;
}
const cleaned = String(src).trim().split(/\s+/)[0];
if (cleaned.startsWith("data:") || cleaned.startsWith("/") || cleaned.startsWith("View/") || cleaned.startsWith("http")) {
try {
const resp = await fetch(cleaned);
return await resp.blob();
} catch (e) {
console.warn("Assistent: fetch blob failed", e);
}
}
return await new Promise((resolve) => {
const tmpImg = new Image();
tmpImg.crossOrigin = "Anonymous";
tmpImg.onload = () => {
try {
const canvas = document.createElement("canvas");
canvas.width = tmpImg.naturalWidth;
canvas.height = tmpImg.naturalHeight;
const ctx = canvas.getContext("2d");
ctx.drawImage(tmpImg, 0, 0);
canvas.toBlob((blob) => resolve(blob), "image/png");
} catch (e) {
resolve(null);
}
};
tmpImg.onerror = () => resolve(null);
tmpImg.src = cleaned;
});
}
async function setFileParamFromSrc(paramId, src, { filename = "assistent.png" } = {}) {
const el = document.getElementById(paramId);
if (!el) {
setStatus(`Missing ${paramId} on Generate tab`);
return false;
}
const blob = await srcToBlob(src);
if (!blob) {
setStatus("Could not load image for init/mask");
return false;
}
const mime = blob.type || guessImageMime(src);
const file = new File([blob], filename, { type: mime });
const container = new DataTransfer();
container.items.add(file);
el.files = container.files;
triggerChangeForEl(el);
openInitImageGroup();
return true;
}
async function setInitFromSrc(src) {
const ok = await setFileParamFromSrc("input_initimage", src, { filename: "assistent_init.png" });
if (ok) {
setStatus("Init Image set");
}
return ok;
}
async function setMaskFromSrc(src) {
const ok = await setFileParamFromSrc("input_maskimage", src, { filename: "assistent_mask.png" });
if (ok) {
setStatus("Mask Image set (white = edit)");
}
return ok;
}
function clearInitAndMask() {
clearFileParam("input_initimage");
clearFileParam("input_maskimage");
const toggler = document.getElementById("input_group_content_initimage_toggle");
if (toggler) {
toggler.checked = false;
triggerChangeForEl(toggler);
}
setStatus("Init Image + Mask cleared");
}
function readInitContext() {
const creativityRaw = val("input_initimagecreativity");
const creativity = creativityRaw === "" ? null : parseFloat(creativityRaw);
return {
has_init_image: hasFileParam("input_initimage"),
has_mask_image: hasFileParam("input_maskimage"),
init_creativity: Number.isFinite(creativity) ? creativity : null,
mask_blur: parseFloat(val("input_maskblur") || "") || null,
mask_grow: parseInt(val("input_maskgrow") || val("input_maskshrinkgrow") || "", 10) || null,
init_group_on: !!document.getElementById("input_group_content_initimage_toggle")?.checked
};
}
function slimPromptForContext(raw) {
let s = String(raw || "");
s = s.replace(/data:image\/[a-zA-Z0-9.+-]+;base64,[A-Za-z0-9+/=]+/g, "[image omitted]");
s = s.replace(/<image\b[^>]*>[\s\S]*?<\/image>/gi, "[image omitted]");
s = s.replace(/<img\b[^>]*>/gi, "[image omitted]");
if (s.length > CONTEXT_PROMPT_MAX) {
s = s.slice(0, CONTEXT_PROMPT_MAX) + "\u2026";
}
return s;
}
function collectLiveContext() {
pullLiveIntoSession();
const S = window.SA && window.SA.session;
let initCtx = {};
try {
if (typeof readInitContext === "function") initCtx = readInitContext();
} catch (e) {
}
const extra = {
prompt_image_count: typeof countPromptImages === "function" ? countPromptImages() : 0,
has_vision_image: typeof visionReadySlots === "function" ? visionReadySlots().length > 0 : false,
image_slots: typeof slotCatalog === "function" ? slotCatalog() : [],
attached_slot_ids: typeof attachableSlots === "function" ? attachableSlots().map((s) => s.id) : [],
auto_apply: !!$2("sa_auto_apply")?.checked,
auto_generate: true,
...initCtx
};
if (S && typeof S.compactContext === "function") {
const ctx = S.compactContext(state.chatSession, {
architecture_ok: typeof isKreaSelected === "function" ? isKreaSelected() : true,
promptMax: typeof CONTEXT_PROMPT_MAX !== "undefined" ? CONTEXT_PROMPT_MAX : 2e3,
extra
});
const block = ctxApi()?.conversationMemoryBlock?.(getContextMemory());
if (block) {
ctx.conversation_memory = block;
}
return ctx;
}
const g = state.chatSession && state.chatSession.gen || {};
return { session: true, prompt: g.prompt || "", negative: g.negative || "", ...extra };
}
function slimInventoryLoras(list, limit) {
const selected = /* @__PURE__ */ new Set();
try {
if (typeof loraHelper !== "undefined" && loraHelper && Array.isArray(loraHelper.selected)) {
for (const l of loraHelper.selected) {
selected.add(String(l.name || l || "").toLowerCase());
}
}
} catch (e) {
}
const namesCap = Math.min(limit || INVENTORY_PROMPT_NAMES, INVENTORY_PROMPT_NAMES);
const richCap = Math.max(4, Math.min(INVENTORY_PROMPT_RICH, namesCap));
const rows = (list || []).map((l) => {
const sel = selected.has(String(l.name || "").toLowerCase());
const hasCard = !!l.has_card;
const krea = !!l.krea_likely;
const blurb = l.blurb || l.usage_hint || null;
return {
name: l.name,
title: l.title || l.name,
trigger_phrase: l.trigger_phrase || null,
triggers: Array.isArray(l.triggers) ? l.triggers.slice(0, 8) : void 0,
architecture: l.architecture || null,
compat_class: l.compat_class || null,
has_card: hasCard,
krea_likely: krea,
blurb,
default_weight: l.default_weight || void 0,
tags: Array.isArray(l.tags) ? l.tags.slice(0, 6) : void 0,
_score: (sel ? 1e3 : 0) + (hasCard ? 200 : 0) + (krea ? 50 : 0) + (blurb ? 10 : 0)
};
});
rows.sort((a, b) => b._score - a._score || String(a.name).localeCompare(String(b.name)));
let richUsed = 0;
const out = [];
for (const row of rows) {
if (out.length >= namesCap) {
break;
}
const sel = selected.has(String(row.name || "").toLowerCase());
let wantRich = sel;
if (!wantRich && richUsed < richCap && (row.krea_likely || row.has_card || row.blurb)) {
wantRich = true;
}
if (wantRich) {
const rich = { name: row.name, title: row.title };
if (row.trigger_phrase) {
rich.trigger_phrase = row.trigger_phrase;
}
if (row.triggers) {
rich.triggers = row.triggers;
}
if (row.krea_likely) {
rich.krea_likely = true;
}
if (row.has_card) {
rich.has_card = true;
}
if (row.blurb) {
rich.blurb = row.blurb;
}
if (row.default_weight) {
rich.default_weight = row.default_weight;
}
if (row.architecture) {
rich.architecture = row.architecture;
}
out.push(rich);
if (!sel) {
richUsed++;
}
} else {
const nameOnly = { name: row.name };
if (row.krea_likely) {
nameOnly.krea_likely = true;
}
out.push(nameOnly);
}
}
return out;
}
function slimInventoryCheckpoints(list, limit) {
const rows = (list || []).slice();
rows.sort((a, b) => (b.krea_likely ? 1 : 0) - (a.krea_likely ? 1 : 0) || (b.has_card ? 1 : 0) - (a.has_card ? 1 : 0) || String(a.name).localeCompare(String(b.name)));
return rows.slice(0, limit || 8).map((c) => {
const out = {
name: c.name,
title: c.title || c.name
};
if (c.architecture) {
out.architecture = c.architecture;
}
if (c.krea_likely) {
out.krea_likely = true;
}
if (c.has_card) {
out.has_card = true;
}
return out;
});
}
function pushUnique(arr, value, max) {
const v = String(value || "").trim();
if (!v || v.length < 2) {
return;
}
const lower = v.toLowerCase();
const next = (arr || []).filter((x) => String(x).toLowerCase() !== lower);
next.unshift(v.slice(0, 80));
return next.slice(0, max);
}
function extractPatch2(text) {
if (window.SA && typeof SA.extractPatch === "function") {
return SA.extractPatch(text);
}
return { prose: text || "", patch: null };
}
function normalizeAspect(raw) {
if (raw == null) {
return null;
}
let s = String(raw).trim().toLowerCase().replace(/\s+/g, "");
if (!s) {
return null;
}
if (s === "square") {
s = "1:1";
} else if (s === "portrait" || s === "vert") {
s = "2:3";
} else if (s === "landscape" || s === "horiz") {
s = "16:9";
} else if (s === "cinematic" || s === "ultrawide") {
s = "2.35:1";
}
return ASPECT_TABLE[s] ? s : null;
}
function sizeFromAspect(aspect) {
const key = normalizeAspect(aspect);
return key ? ASPECT_TABLE[key] : null;
}
function guessAspectFromSize(w, h) {
const width = parseInt(w, 10);
const height = parseInt(h, 10);
if (!width || !height) {
return null;
}
let best = null;
let bestDist = Infinity;
for (const [key, [aw, ah]] of Object.entries(ASPECT_TABLE)) {
const dist = Math.abs(width / height - aw / ah) + Math.abs(width - aw) / 4e3 + Math.abs(height - ah) / 4e3;
if (dist < bestDist) {
bestDist = dist;
best = key;
}
}
return bestDist < 0.12 ? best : null;
}
function clearPromptImagesInBox() {
const box = document.getElementById("alt_prompt_textbox") || document.getElementById("input_prompt");
if (!box) {
return false;
}
const before = box.value || "";
const next = before.replace(/<image\b[^>]*>[\s\S]*?<\/image>/gi, "").replace(/<image\b[^>]*\/?>/gi, "").replace(/data:image\/[a-zA-Z0-9.+-]+;base64,[A-Za-z0-9+/=]+/g, "").replace(/\n{3,}/g, "\n\n").trim();
if (next === before.trim()) {
return false;
}
box.value = next;
box.dispatchEvent(new Event("input", { bubbles: true }));
box.dispatchEvent(new Event("change", { bubbles: true }));
return true;
}
function clearPatchBlocksOnly() {
document.querySelectorAll("#sa_messages .sa-patch, #sa_messages .sa-patch-stale, #sa_messages .sa-civitai-list").forEach((el) => el.remove());
state.lastPatch = null;
syncBuildGenButton();
setStatus("\u041F\u0430\u0442\u0447\u0438 \u0443\u0431\u0440\u0430\u043D\u044B \u0438\u0437 \u0447\u0430\u0442\u0430");
}
function toggleMoreMenu(menuId, btnId) {
const menu = $2(menuId);
const btn = $2(btnId);
if (!menu) {
return;
}
const open = menu.hidden;
document.querySelectorAll(".sa-more-menu").forEach((m) => {
m.hidden = true;
});
document.querySelectorAll("#sa_btn_board_more, #sa_btn_clear_more").forEach((b) => b.setAttribute("aria-expanded", "false"));
if (open) {
menu.hidden = false;
btn?.setAttribute("aria-expanded", "true");
}
}
function closeAllMoreMenus() {
document.querySelectorAll(".sa-more-menu").forEach((m) => {
m.hidden = true;
});
document.querySelectorAll("#sa_btn_board_more, #sa_btn_clear_more").forEach((b) => b.setAttribute("aria-expanded", "false"));
}
function setPackValue(packName, { flash, user } = {}) {
const pack = $2("sa_pack");
if (!pack || !packName) {
return false;
}
const resolved = PACK_ALIASES[String(packName).trim()] || String(packName).trim();
if (![...pack.options].some((o) => o.value === resolved)) {
return false;
}
if (pack.value !== resolved) {
pack.value = resolved;
saveSettings();
}
if (user) {
state.packUserTouched = true;
}
if (flash) {
pack.classList.add("sa-pack-flash");
setTimeout(() => pack.classList.remove("sa-pack-flash"), 900);
}
syncModeBadge();
return true;
}
function autoSelectPack(text) {
if (state.packUserTouched) {
return null;
}
const cur = $2("sa_pack")?.value || defaultPackId();
if (cur === "ordinary") {
return null;
}
const t = String(text || "").toLowerCase();
if (!t.trim()) {
return null;
}
if (userTextMentionsParams(t) || parseAspectFromUserText(t)) {
return cur === "critique_image" || cur === "describe_ref" ? "ordinary" : "fix_params";
}
if (cyrTokenRe("\u043F\u043E\u043F\u0440\u0430\u0432\u044C|\u0438\u0441\u043F\u0440\u0430\u0432\u044C|\u043F\u0435\u0440\u0435\u043F\u0438\u0448\u0438|\u0443\u043B\u0443\u0447\u0448\u0438").test(t) || /\b(fix\s+it|make\s+it\s+better|rewrite)\b/i.test(t)) {
return "write_prompt";
}
if (cyrTokenRe("\u043E\u043F\u0438\u0448\u0438\\s+(\u0440\u0435\u0444|\u0438\u0437\u043E\u0431\u0440\u0430\u0436[\u0430-\u044F\u0451]*|\u044D\u0442\u043E\u0442|\u044D\u0442\u0443|\u043A\u0430\u0440\u0442\u0438\u043D\u043A[\u0430-\u044F\u0451]*|\u0440\u0435\u0444\u0435\u0440\u0435\u043D\u0441)").test(t) || /\b(prompt\s+from\s+image|describe\s+(this|the|ref|image)|reverse\s*prompt)\b/i.test(t) || /опиши\s+(этот|эту|картинк|референс)/i.test(t)) {
return "describe_ref";
}
if (/\b(critique|criticize)\b/i.test(t) || cyrTokenRe("\u043A\u0440\u0438\u0442\u0438\u043A[\u0430-\u044F\u0451]*|\u0447\u0442\u043E\\s+\u043D\u0435\\s+\u0442\u0430\u043A|\u0440\u0430\u0437\u0431\u0435\u0440\u0438").test(t) || /(?:^|[^а-яёa-z0-9_])(посмотри|смотри)\s+(на\s+)?(результат|картинк[а-яё]*|изображен[а-яё]*|кадр|ген)/i.test(t)) {
return "critique_image";
}
if (/\b(inpaint|mask|img2img)\b/i.test(t) || cyrTokenRe("\u0437\u0430\u043C\u0430\u0436\u044C|\u0437\u0430\u043A\u0440\u0430\u0441\u044C|\u0440\u0443\u043A\u0438|\u043B\u0438\u0446\u043E|\u043C\u0430\u0441\u043A[\u0430-\u044F\u0451]*").test(t) || /init\s*image/i.test(t)) {
return "inpaint_edit";
}
if (cur === "critique_image") {
return "ordinary";
}
if (/\b(moodboard|compose|scene)\b/i.test(t) || cyrTokenRe("\u0441\u0446\u0435\u043D[\u0430-\u044F\u0451]*|\u0430\u0442\u043C\u043E\u0441\u0444\u0435\u0440[\u0430-\u044F\u0451]*|\u043C\u0438\u0437\u0430\u043D\u0441\u0446\u0435\u043D[\u0430-\u044F\u0451]*").test(t)) {
return "compose_scene";
}
return "write_prompt";
}
function restoreDefaultPackAfterHop() {
if (state.packUserTouched) {
return;
}
const cur = $2("sa_pack")?.value || "";
if (cur === "critique_image" || cur === "describe_ref") {
setPackValue(defaultPackId(), { flash: true });
}
}
function patchHasGenTrigger(patch) {
if (!patch) {
return false;
}
if (Array.isArray(patch.actions) && patch.actions.map(String).includes("generate")) {
return true;
}
return patch.prompt != null || patch.loras || patch.width != null || patch.height != null || patch.aspect != null || patch.steps != null || patch.cfg != null || patch.seed != null || patch.sigma_shift != null || patch.images != null || patch.batch != null || patch.vary === true || Array.isArray(patch.variants) && patch.variants.length > 0 || patch.use_init_image || patch.clear_init_image || patch.init_creativity != null || patch.denoise != null || patch.use_mask_image || patch.clear_mask_image || patch.clear_prompt_images;
}
async function applyPatch(patch, which) {
if (!patch) {
return;
}
const doPrompt = !which || which === "all" || which === "prompt";
const doLoras = !which || which === "all" || which === "loras";
const doParams = !which || which === "all" || which === "size" || which === "params";
const doInit = !which || which === "all" || which === "params" || which === "init";
if (patch.pack) {
setPackValue(patch.pack, { flash: true });
}
if (doPrompt && patch.clear_prompt_images) {
clearPromptImagesInBox();
}
if (doPrompt && patch.prompt != null) {
const box = document.getElementById("alt_prompt_textbox") || document.getElementById("input_prompt");
if (box) {
box.value = patch.prompt;
box.dispatchEvent(new Event("input", { bubbles: true }));
box.dispatchEvent(new Event("change", { bubbles: true }));
}
if (Array.isArray(patch.loras)) {
for (const l of patch.loras) {
const triggers = l.triggers || (l.trigger_phrase ? [l.trigger_phrase] : []);
for (const t of triggers) {
if (t && box && box.value && !box.value.includes(t)) {
box.value = `${box.value.trim()}, ${t}`;
box.dispatchEvent(new Event("input", { bubbles: true }));
}
}
}
}
}
if (doPrompt) {
if (patch.negative != null) {
setNegativePrompt(patch.negative);
} else if (patchHasGenTrigger(patch)) {
ensureNegativeForGenerate(patch);
}
}
if (doLoras && Array.isArray(patch.loras) && typeof loraHelper !== "undefined" && loraHelper) {
try {
if (typeof loraHelper.clearLoras === "function") {
loraHelper.clearLoras();
}
} catch (e) {
}
for (const l of patch.loras) {
const name = l.name;
if (!name) {
continue;
}
try {
if (typeof loraHelper.selectLora === "function") {
loraHelper.selectLora(name);
}
if (loraHelper.loraWeightPref && l.weight != null) {
loraHelper.loraWeightPref[name] = l.weight;
}
} catch (e) {
console.warn("Assistent: selectLora failed", name, e);
}
}
try {
if (typeof loraHelper.rebuildUI === "function") {
loraHelper.rebuildUI();
}
} catch (e) {
}
}
if (doParams) {
const defaults = mergedGenerationDefaults();
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 (shouldRememberSessionParam("aspect", patch.aspect)) {
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 && !shouldSkipSessionRollback("width", patch.width)) {
setVal("input_width", String(patch.width));
if (shouldRememberSessionParam("width", patch.width)) {
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 && !shouldSkipSessionRollback("height", patch.height)) {
setVal("input_height", String(patch.height));
if (shouldRememberSessionParam("height", patch.height)) {
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 && !shouldSkipSessionRollback("steps", patch.steps)) {
setVal("input_steps", String(patch.steps));
if (shouldRememberSessionParam("steps", patch.steps)) {
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 && !shouldSkipSessionRollback("cfg", patch.cfg)) {
if (document.getElementById("input_cfgscale")) {
setVal("input_cfgscale", String(patch.cfg));
} else {
setVal("input_cfg", String(patch.cfg));
}
if (shouldRememberSessionParam("cfg", patch.cfg)) {
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");
} else if (patch.lock_seed === true) {
const cur = val("input_seed");
if (cur && String(cur) !== "-1") {
setVal("input_seed", cur);
}
} else if (patch.seed != null && !shouldSkipSessionRollback("seed", patch.seed)) {
setVal("input_seed", String(patch.seed));
if (shouldRememberSessionParam("seed", patch.seed)) {
rememberSessionExact({ seed: patch.seed });
}
}
if (patch.sigma_shift != null && !shouldSkipSessionRollback("sigma_shift", patch.sigma_shift)) {
setVal("input_sigmashift", String(patch.sigma_shift));
if (shouldRememberSessionParam("sigma_shift", patch.sigma_shift)) {
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 (shouldRememberSessionParam("sampler", patch.sampler)) {
rememberSessionExact({ sampler: patch.sampler });
}
}
if (patch.scheduler != null && document.getElementById("input_scheduler")) {
setVal("input_scheduler", String(patch.scheduler));
if (shouldRememberSessionParam("scheduler", patch.scheduler)) {
rememberSessionExact({ scheduler: patch.scheduler });
}
}
const batch = patch.images != null ? patch.images : patch.batch;
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 (shouldRememberSessionParam("images", batch)) {
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));
}
}
}
if (doInit) {
const creativity = patch.init_creativity != null ? patch.init_creativity : patch.denoise;
if (creativity != null && document.getElementById("input_initimagecreativity")) {
setVal("input_initimagecreativity", String(creativity));
openInitImageGroup();
}
if (patch.mask_blur != null && document.getElementById("input_maskblur")) {
setVal("input_maskblur", String(patch.mask_blur));
}
if (patch.mask_grow != null) {
if (document.getElementById("input_maskgrow")) {
setVal("input_maskgrow", String(patch.mask_grow));
} else if (document.getElementById("input_maskshrinkgrow")) {
setVal("input_maskshrinkgrow", String(patch.mask_grow));
}
}
if (patch.clear_init_image || patch.clear_mask_image) {
if (patch.clear_init_image) {
clearFileParam("input_initimage");
}
if (patch.clear_mask_image) {
clearFileParam("input_maskimage");
}
if (patch.clear_init_image && patch.clear_mask_image) {
const toggler = document.getElementById("input_group_content_initimage_toggle");
if (toggler) {
toggler.checked = false;
triggerChangeForEl(toggler);
}
}
}
if (patch.select_slot) {
const id = normalizeSlotId(patch.select_slot);
if (slotById(id)) {
state.selectedSlotId = id;
renderBoard();
}
}
if (patch.snapshot_generate) {
snapshotGenerateToRef();
}
const initId = patch.slot_to_init || (patch.use_init_image || Array.isArray(patch.actions) && patch.actions.map(String).includes("use_init") ? state.selectedSlotId : null);
const maskId = patch.slot_to_mask || null;
const src = resolveSlotSrc(patch.slot_to_init) || selectedSrc() || findCurrentGenerateSrc();
const wantInit = patch.use_init_image === true || !!patch.slot_to_init || Array.isArray(patch.actions) && patch.actions.map(String).includes("use_init");
const wantMask = patch.use_mask_image === true || !!patch.slot_to_mask || Array.isArray(patch.actions) && patch.actions.map(String).includes("use_mask");
if (wantInit) {
const initSrc = resolveSlotSrc(initId) || src;
if (initSrc) {
await setInitFromSrc(initSrc);
} else {
setStatus("No image for Init \u2014 drop a ref or wait for Generate");
}
}
if (wantMask) {
const maskSrc = resolveSlotSrc(maskId) || src;
if (maskSrc) {
await setMaskFromSrc(maskSrc);
} else {
setStatus("No image for Mask \u2014 drop a mask (white=edit) first");
}
}
if (patch.slot_to_prompt_image) {
setStatus("Prompt Images: drop the ref into the Swarm prompt box (no auto helper yet)");
}
}
if (patch.controls && typeof patch.controls === "object" && !Array.isArray(patch.controls)) {
const schema = state.config?.controls || {};
const filtered = filterControlPatch(patch.controls, patch);
if (Object.keys(filtered).length) {
const next = { ...state.config?.control_values || state.exact?.controls || {}, ...filtered };
savePersonaControls(filtered);
renderPersonaControls(schema, next);
}
}
const acts = Array.isArray(patch.actions) ? patch.actions.map(String) : [];
const wantSwitch = acts.includes("persona_switch") || patch.persona && typeof patch.persona === "string" || patch._persona_cloned || patch._persona_written;
if (wantSwitch) {
const newId = String(patch.persona || patch._persona_cloned || patch._persona_written || "").trim();
if (newId && AssistentConfigSafeIdClient(newId)) {
await refreshPersonasAndSwitch(newId);
} else if (acts.includes("persona_clone") || acts.includes("persona_write") || patch.persona_clone) {
await refreshPersonasAndSwitch(null);
}
}
syncChipHighlight();
syncLiveParamsBar();
syncBuildGenButton();
if (state.activeChatId && !state.restoringChat) {
const chat = findChat(state.activeChatId);
if (chat) {
chat.params = snapshotChatParams();
chat.updatedAt = Date.now();
persistChatsStore();
}
}
if (!state.restoringChat) {
setStatus(patch._persona_error ? `Persona: ${patch._persona_error}` : "Applied patch");
}
}
function AssistentConfigSafeIdClient(id) {
return /^[A-Za-z0-9][A-Za-z0-9_\-]{0,63}$/.test(String(id || ""));
}
async function refreshPersonasAndSwitch(preferId) {
await new Promise((resolve) => {
genericRequest(
"AssistentListPersonas",
{},
async (data) => {
if (Array.isArray(data?.personas)) {
state.personas = data.personas.map((p) => ({
id: p.id,
title: p.title,
accent: p.accent,
source: p.source
}));
renderPersonaOptions(state.personas, preferId || $2("sa_persona")?.value);
}
if (preferId && $2("sa_persona")) {
if ([...$2("sa_persona").options].some((o) => o.value === preferId)) {
$2("sa_persona").value = preferId;
await applyPersonaForChat(preferId, { quiet: true });
}
} else {
loadConfig($2("sa_persona")?.value, () => resolve());
return;
}
resolve();
},
0,
() => resolve()
);
});
}
function triggerGenerate() {
try {
if (typeof mainGenHandler !== "undefined" && mainGenHandler && typeof mainGenHandler.doGenerate === "function") {
mainGenHandler.doGenerate();
return true;
}
} catch (e) {
console.warn("Assistent: doGenerate failed", e);
}
const btn = document.getElementById("generate_button") || document.getElementById("alt_generate_button") || document.querySelector("button.generate-button") || document.querySelector('#generate_button, button[id*="generate"]');
if (btn) {
btn.click();
return true;
}
return false;
}
function shouldParkLlmBeforeGen() {
return !!$2("sa_park_llm")?.checked;
}
function parkLlm() {
return new Promise((resolve) => {
const model = $2("sa_model")?.value;
if (!shouldParkLlmBeforeGen() || !model || state.llmParked || typeof genericRequest !== "function") {
resolve(false);
return;
}
const baseUrl = $2("sa_base_url")?.value || "http://127.0.0.1:11434";
let settled = false;
const finish = (ok) => {
if (settled) {
return;
}
settled = true;
if (ok) {
state.llmParked = true;
state.expectColdLoad = true;
}
resolve(!!ok);
};
setTimeout(() => finish(false), 8e3);
genericRequest("AssistentParkLlm", { baseUrl, model }, () => finish(true), 0, () => finish(false));
});
}
function warmLlm({ force = false } = {}) {
return new Promise((resolve) => {
const model = $2("sa_model")?.value;
if (!model || typeof genericRequest !== "function") {
resolve(false);
return;
}
if (!force && !state.llmParked) {
resolve(false);
return;
}
const baseUrl = $2("sa_base_url")?.value || "http://127.0.0.1:11434";
let settled = false;
const finish = (ok) => {
if (settled) {
return;
}
settled = true;
state.llmParked = false;
if (ok) {
state.expectColdLoad = false;
}
resolve(!!ok);
};
setTimeout(() => finish(false), 18e4);
genericRequest("AssistentWarmLlm", { baseUrl, model }, () => finish(true), 0, () => finish(false));
});
}
function cancelWaitForNewImage() {
if (state.waitImageTimer) {
clearInterval(state.waitImageTimer);
state.waitImageTimer = null;
}
}
function bumpChatEpoch() {
state.chatEpoch = (state.chatEpoch || 0) + 1;
return state.chatEpoch;
}
function clearInFlightUi({ status } = {}) {
state.busy = false;
state.generating = false;
state.pendingSilentGen = false;
if (state.streamEl) {
try {
state.streamEl.remove();
} catch (e) {
}
state.streamEl = null;
state.streamMeta = null;
}
setInterruptVisible(false);
syncGenerateBusy();
syncPatchActionAvailability();
if (status != null) {
stopBusyUi(status);
} else {
stopBusyUi("");
}
}
function abortInFlightWork({ status, interruptSwarm = false } = {}) {
bumpChatEpoch();
cancelWaitForNewImage();
if (interruptSwarm) {
try {
if (typeof doInterrupt === "function") {
doInterrupt(false);
} else if (typeof genericRequest === "function") {
genericRequest("InterruptAll", { other_sessions: false }, () => {
}, 0, () => {
});
}
} catch (e) {
}
}
clearInFlightUi({ status: status != null ? status : "" });
}
function doInterruptNow() {
bumpChatEpoch();
cancelWaitForNewImage();
try {
if (typeof doInterrupt === "function") {
doInterrupt(false);
}
} catch (e) {
}
if (typeof genericRequest === "function") {
genericRequest("InterruptAll", { other_sessions: false }, () => {
}, 0, () => {
});
}
clearInFlightUi({ status: "\u041F\u0440\u0435\u0440\u0432\u0430\u043D\u043E" });
}
function waitForNewImage(prevSrc, timeoutMs = 18e4) {
cancelWaitForNewImage();
const epoch = state.chatEpoch;
const prev = String(prevSrc || "");
return new Promise((resolve) => {
const start = Date.now();
let sawRunning = false;
let idleTicks = 0;
let candidate = null;
state.waitImageTimer = setInterval(() => {
if (epoch !== state.chatEpoch) {
cancelWaitForNewImage();
resolve(null);
return;
}
const running = isSwarmGenerateRunning();
if (running) {
sawRunning = true;
idleTicks = 0;
} else if (sawRunning) {
idleTicks += 1;
}
const raw = findCurrentGenerateSrc();
const src = raw && !looksLikeModelPreview(raw) ? raw : null;
if (src && src !== prev) {
candidate = src;
}
if (sawRunning && !running && idleTicks >= 2) {
cancelWaitForNewImage();
resolve(candidate || src || null);
return;
}
if (candidate && !running && Date.now() - start > 500) {
cancelWaitForNewImage();
resolve(candidate);
return;
}
if (Date.now() - start > timeoutMs) {
cancelWaitForNewImage();
resolve(candidate || src || null);
}
}, 400);
});
}
const VARIANT_STRIP_KEYS = [
"variants",
"label",
"notes",
"actions",
"look_at",
"vision_from",
"vision_slots",
"search_query",
"civitai_query",
"memories",
"memory",
"memory_query",
"memory_kind",
"tag_query",
"user_prefs",
"controls",
"skills",
"persona_shelves",
"inventory_query",
"pack"
];
function normalizeVariantList(patch) {
if (!patch || !Array.isArray(patch.variants)) {
return null;
}
const items = patch.variants.filter((v) => v && typeof v === "object" && !Array.isArray(v));
if (items.length < 2) {
return null;
}
return items.slice(0, MAX_GEN_VARIANTS);
}
function stripMetaPatchKeys(obj) {
const out = { ...obj || {} };
for (const k of VARIANT_STRIP_KEYS) {
delete out[k];
}
return out;
}
function mergeVariantPatch(base, item, index) {
const merged = { ...stripMetaPatchKeys(base), ...stripMetaPatchKeys(item) };
merged.images = 1;
delete merged.batch;
if (merged.seed == null && merged.lock_seed !== true) {
merged.seed = -1;
merged.vary = true;
}
merged.actions = ["generate"];
const labelRaw = item?.label != null ? String(item.label).trim() : "";
return {
id: `var${index + 1}`,
label: (labelRaw || `\u0412\u0430\u0440\u0438\u0430\u043D\u0442 ${index + 1}`).slice(0, 48),
patch: merged
};
}
function finishedGenResultCount() {
return (state.genResults || []).filter((r) => r && r.src).length;
}
function isMultiGenResults() {
return finishedGenResultCount() > 1 || (state.genResults || []).length > 1;
}
function clearGenResults() {
state.genResults = [];
state.selectedGenResultId = null;
if (state.lightboxIndex >= 0) {
closeGenLightbox();
}
}
function selectGenResult(id, { restore = true, openViewer = false } = {}) {
const row = (state.genResults || []).find((r) => r.id === id);
if (!row) {
return false;
}
state.selectedGenResultId = row.id;
const gen = generateSlot();
if (gen && row.src) {
gen.src = row.src;
}
if (restore && row.patch) {
applyPatch(row.patch, "all").catch(() => {
});
syncLiveParamsBar();
}
renderBoard();
if (openViewer && row.src) {
openGenLightbox(row.id);
}
return true;
}
async function runGenerateFromPatch(patch, opts = {}) {
if (typeof pullLiveIntoSession === "function") pullLiveIntoSession();
if (state.chatSession && state.chatSession.gen && (opts.fromSession || opts.force)) {
const fromSess = { ...state.chatSession.gen, generate: true, actions: ["generate"] };
if (patch && typeof patch === "object") {
for (const k of Object.keys(patch)) {
if (patch[k] != null) fromSess[k] = patch[k];
}
}
patch = fromSess;
if (typeof pushSessionToSwarm === "function") await pushSessionToSwarm(state.chatSession);
}
const force = !!opts.force;
if (!force && false || !patchHasGenTrigger(patch)) {
return null;
}
const variantItems = normalizeVariantList(patch);
const jobs = variantItems ? variantItems.map((item, i) => mergeVariantPatch(patch, item, i)) : null;
if (jobs) {
state.genResults = jobs.map((j) => ({
id: j.id,
label: j.label,
src: null,
patch: j.patch
}));
state.selectedGenResultId = null;
setBoardTab("generate", { persist: true });
renderBoard();
} else {
clearGenResults();
}
const epoch = state.chatEpoch;
if (shouldParkLlmBeforeGen()) {
startBusyUi("parking");
setStatus("\u041E\u0441\u0432\u043E\u0431\u043E\u0436\u0434\u0430\u044E VRAM\u2026");
await parkLlm();
if (epoch !== state.chatEpoch) {
return null;
}
}
state.generating = true;
setInterruptVisible(true);
startBusyUi("generating");
let lastSrc = null;
const steps = jobs || [{
id: "var1",
label: "Generate",
patch: { ...stripMetaPatchKeys(patch), actions: ["generate"] }
}];
for (let i = 0; i < steps.length; i++) {
if (epoch !== state.chatEpoch) {
break;
}
const job = steps[i];
if (jobs) {
setStatus(`\u0412\u0430\u0440\u0438\u0430\u043D\u0442 ${i + 1}/${steps.length}: ${job.label}`);
} else {
setStatus("\u0413\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u044F\u2026");
}
startBusyUi("generating");
if (jobs) {
await applyPatch(job.patch, "all");
syncLiveParamsBar();
}
ensureNegativeForGenerate(job.patch);
if (epoch !== state.chatEpoch) {
break;
}
const prev = findCurrentGenerateSrc();
const ok = triggerGenerate();
if (!ok) {
setStatus(jobs ? `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0432\u0430\u0440\u0438\u0430\u043D\u0442 ${i + 1}/${steps.length}` : "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C Generate");
if (!jobs) {
break;
}
continue;
}
const src = await waitForNewImage(prev);
if (epoch !== state.chatEpoch) {
break;
}
if (src) {
lastSrc = src;
if (jobs) {
const row = state.genResults.find((r) => r.id === job.id);
if (row) {
row.src = src;
}
state.selectedGenResultId = job.id;
}
const gen = generateSlot();
if (gen) {
gen.src = src;
}
renderBoard();
}
}
state.generating = false;
setInterruptVisible(state.busy);
state.expectColdLoad = true;
if (jobs && lastSrc && state.selectedGenResultId && epoch === state.chatEpoch) {
const row = state.genResults.find((r) => r.id === state.selectedGenResultId);
if (row?.patch) {
await applyPatch(row.patch, "all");
syncLiveParamsBar();
}
}
const paneVisible = !!document.getElementById("swarm_assistent_root")?.offsetParent;
const multiDone = !!(jobs && finishedGenResultCount() > 1);
const willAutoCritique = !multiDone && !!$2("sa_auto_critique")?.checked;
if (state.view === "chat" && paneVisible && !willAutoCritique && epoch === state.chatEpoch) {
startBusyUi("warming");
setStatus("\u0412\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u044E LLM \u0432 GPU\u2026");
await warmLlm({ force: true });
}
if (epoch !== state.chatEpoch) {
return null;
}
if (jobs) {
const n = finishedGenResultCount();
const msg = n > 0 ? n > 1 ? `\u0413\u043E\u0442\u043E\u0432\u043E \xB7 ${n} \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u043E\u0432` : `\u0413\u043E\u0442\u043E\u0432\u043E \xB7 1 \u0432\u0430\u0440\u0438\u0430\u043D\u0442` : "Generate \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043D (\u043D\u043E\u0432\u043E\u0435 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E)";
if (!state.busy) {
stopBusyUi(msg);
}
setStatus(msg);
return multiDone ? null : lastSrc;
}
if (!state.busy) {
stopBusyUi(lastSrc ? "Generate \u0433\u043E\u0442\u043E\u0432" : "Generate \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043D (\u043D\u043E\u0432\u043E\u0435 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E)");
}
if (lastSrc) {
const gen = generateSlot();
if (gen) {
gen.src = lastSrc;
renderBoard();
}
setStatus("Generate \u0433\u043E\u0442\u043E\u0432");
return lastSrc;
}
if (state.busy) {
setStatus("Generate \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043D (\u043D\u043E\u0432\u043E\u0435 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E)");
}
return null;
}
async function resolveFinishedGenerateSrc(hint, { settleMs = 2e4 } = {}) {
scrubPreviewFromGenerateSlot();
let src = hint && !looksLikeModelPreview(hint) ? hint : null;
if (!src) {
src = findCurrentGenerateSrc();
}
if (isGenerateUnavailable()) {
const settled = await waitForNewImage(src, settleMs);
if (settled) {
src = settled;
}
}
return src && !looksLikeModelPreview(src) ? src : null;
}
async function maybeAutoCritique(imageSrc) {
if (!$2("sa_auto_critique")?.checked || turnHopUsed("critique") || isMultiGenResults()) {
return;
}
const src = await resolveFinishedGenerateSrc(imageSrc);
if (!src) {
setStatus("\u0410\u0432\u0442\u043E-\u043A\u0440\u0438\u0442\u0438\u043A\u0430 \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D\u0430 \u2014 \u043D\u0435\u0442 \u0433\u043E\u0442\u043E\u0432\u043E\u0433\u043E \u043A\u0430\u0434\u0440\u0430 Generate");
return;
}
if (!claimTurnHop("critique")) {
return;
}
setPackValue("critique_image", { flash: true });
if ($2("sa_input")) {
$2("sa_input").value = "Critique this result and improve the prompt for the next generation.";
}
const gen = generateSlot();
if (gen) {
gen.attach = true;
gen.src = src;
renderBoard();
}
setStatus("Auto-critique\u2026");
await sendChat({ fromAutoCritique: true, forceSlotIds: [GEN_ID] });
restoreDefaultPackAfterHop();
}
async function maybeAutoVisionLook(imageSrc) {
if (!wantsAutoVision() || $2("sa_auto_critique")?.checked || turnHopUsed("vision") || state.busy || isMultiGenResults()) {
return;
}
const src = await resolveFinishedGenerateSrc(imageSrc);
if (!src) {
return;
}
const gen = generateSlot();
if (gen) {
gen.src = src;
gen.attach = true;
renderBoard();
}
if (!claimTurnHop("vision")) {
return;
}
setPackValue("critique_image", { flash: true });
if ($2("sa_input")) {
$2("sa_input").value = "Look at the Generate result and briefly say what worked and what to fix next.";
}
setStatus("Auto look_at\u2026");
await sendChat({ fromVisionHop: true, forceSlotIds: [GEN_ID], skipAutoPack: true });
restoreDefaultPackAfterHop();
}
async function askLookAtResult() {
if (state.busy || state.generating) {
setStatus("\u0417\u0430\u043D\u044F\u0442\u043E \u2014 \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u043A\u043E\u043D\u0446\u0430 \u043E\u0442\u0432\u0435\u0442\u0430 \u0438\u043B\u0438 \u0421\u0442\u043E\u043F");
return;
}
if (!updateGate()) {
setStatus("\u0412\u044B\u0431\u0435\u0440\u0438 \u043C\u043E\u0434\u0435\u043B\u044C Krea 2");
return;
}
const preferred = (state.genResults || []).find((r) => r.id === state.selectedGenResultId && r.src)?.src || generateSlot()?.src;
const src = await resolveFinishedGenerateSrc(preferred, { settleMs: 8e3 });
if (!src) {
setStatus("\u041D\u0435\u0442 \u0433\u043E\u0442\u043E\u0432\u043E\u0433\u043E \u043A\u0430\u0434\u0440\u0430 Generate \u2014 \u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u0441\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0439");
return;
}
const gen = generateSlot();
if (gen) {
gen.src = src;
gen.attach = true;
}
setBoardTab("generate");
renderBoard();
setView("chat");
const label = (state.genResults || []).find((r) => r.id === state.selectedGenResultId)?.label;
setPackValue("critique_image", { flash: true });
if ($2("sa_input")) {
$2("sa_input").value = label ? `\u041F\u043E\u0441\u043C\u043E\u0442\u0440\u0438 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \xAB${label}\xBB: \u0447\u0442\u043E \u043F\u043E\u043B\u0443\u0447\u0438\u043B\u043E\u0441\u044C, \u0447\u0442\u043E \u0441\u043B\u043E\u043C\u0430\u043B\u043E\u0441\u044C, \u0438 \u043A\u0430\u043A \u043F\u043E\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u043F\u0440\u043E\u043C\u043F\u0442 \u0438 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B \u0434\u043B\u044F \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0433\u043E \u043A\u0430\u0434\u0440\u0430.` : "\u041F\u043E\u0441\u043C\u043E\u0442\u0440\u0438 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442: \u0447\u0442\u043E \u043F\u043E\u043B\u0443\u0447\u0438\u043B\u043E\u0441\u044C, \u0447\u0442\u043E \u0441\u043B\u043E\u043C\u0430\u043B\u043E\u0441\u044C, \u0438 \u043A\u0430\u043A \u043F\u043E\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u043F\u0440\u043E\u043C\u043F\u0442 \u0438 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B \u0434\u043B\u044F \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0433\u043E \u043A\u0430\u0434\u0440\u0430.";
}
await sendChat({ forceSlotIds: [GEN_ID], skipAutoPack: true });
}
function currentPersonaInfo() {
const id = ($2("sa_persona")?.value || localStorage.getItem(LS_PERSONA) || "neutral").trim() || "neutral";
const known = (state.personas || []).find((p) => p && p.id === id);
return {
id,
title: known && known.title || ({
neutral: "\u041D\u0435\u0439\u0442\u0440\u0430\u043B\u044C\u043D\u044B\u0439",
lewd: "\u041F\u043E\u0448\u043B\u044F\u043A",
aggressive: "\u0410\u0433\u0440\u0435\u0441\u0441\u0438\u0432\u043D\u044B\u0439"
}[id] || id)
};
}
function mountAssistantMeta(div, meta = {}) {
if (!div || div.querySelector(".sa-msg-meta")) {
return;
}
const persona = meta.persona || currentPersonaInfo();
const pack = meta.pack || $2("sa_pack")?.value || "";
div.dataset.persona = persona.id || "neutral";
if (pack) {
div.dataset.pack = pack;
}
const row = document.createElement("div");
row.className = "sa-msg-meta";
const chip = document.createElement("span");
chip.className = `sa-persona-mark sa-persona-${persona.id || "neutral"}`;
chip.textContent = persona.title || persona.id;
chip.title = `\u0425\u0430\u0440\u0430\u043A\u0442\u0435\u0440: ${persona.title || persona.id}${pack ? ` \xB7 \u0440\u0435\u0436\u0438\u043C ${pack}` : ""}`;
row.appendChild(chip);
if (pack && pack !== "ordinary" && pack !== "write_prompt") {
const packEl = document.createElement("span");
packEl.className = "sa-pack-mark";
packEl.textContent = pack.replace(/_/g, " ");
packEl.title = `\u0420\u0435\u0436\u0438\u043C: ${pack}`;
row.appendChild(packEl);
}
div.insertBefore(row, div.firstChild);
}
function appendMessage(role, text, patch, civitaiResults, meta) {
const box = $2("sa_messages");
if (!box) {
return null;
}
hideChatEmpty();
const div = document.createElement("div");
div.className = `sa-msg ${role}`;
if (role === "assistant") {
mountAssistantMeta(div, meta);
}
const { prose, patch: extracted } = role === "assistant" ? extractPatch2(text) : { prose: text, patch: null };
const finalPatch = patch || extracted;
if (role === "assistant") {
setAssistantBody(div, prose || text || "");
} else {
div.textContent = prose || text || "";
}
if (finalPatch && !(meta && meta.historical)) {
const silent = !!(meta && meta.silentPatch);
mountPatchBlock(div, finalPatch, { silent });
}
if (role === "assistant" && !(meta && meta.historical)) {
mountCurateButtons(div, meta);
}
box.appendChild(div);
scrollMessagesToBottom({ force: true });
return div;
}
function beginStreamMessage(meta) {
const box = $2("sa_messages");
if (!box) {
return null;
}
hideChatEmpty();
const div = document.createElement("div");
div.className = "sa-msg assistant sa-streaming sa-typing";
mountAssistantMeta(div, meta);
const body = document.createElement("div");
body.className = "sa-msg-body";
body.innerHTML = '<span class="sa-dots" aria-hidden="true"><i></i><i></i><i></i></span><span class="sa-typing-label">Waiting for the model\u2026</span>';
div.appendChild(body);
box.appendChild(div);
scrollMessagesToBottom({ force: true });
state.streamEl = div;
state.streamMeta = meta || null;
state.streamFenceDone = false;
return div;
}
function streamHasClosedPatchFence(text) {
const t = String(text || "");
if (!/```[\s\S]*```/.test(t)) {
return false;
}
const re = /```(?:json)?\s*([\s\S]*?)```/gi;
let match;
while ((match = re.exec(t)) !== null) {
try {
const obj = JSON.parse(match[1].trim());
const terminal = window.SA && typeof SA.isTerminalStreamPatch === "function" ? SA.isTerminalStreamPatch(obj) : typeof isPatchObject === "function" && isPatchObject(obj);
if (terminal) {
return true;
}
} catch (e) {
}
}
return false;
}
function trimToClosedPatchFence(text) {
const t = String(text || "");
const re = /```(?:json)?\s*([\s\S]*?)```/gi;
let match;
let lastEnd = -1;
while ((match = re.exec(t)) !== null) {
try {
const obj = JSON.parse(match[1].trim());
const terminal = window.SA && typeof SA.isTerminalStreamPatch === "function" ? SA.isTerminalStreamPatch(obj) : typeof isPatchObject === "function" && isPatchObject(obj);
if (terminal) {
lastEnd = match.index + match[0].length;
}
} catch (e) {
}
}
return lastEnd > 0 ? t.slice(0, lastEnd).trimEnd() : t;
}
function appendStreamDelta(delta) {
if (state.streamFenceDone) {
return;
}
if (!state.streamEl) {
beginStreamMessage(state.streamMeta || void 0);
}
if (state.streamEl) {
if (state.streamEl.classList.contains("sa-typing")) {
state.streamEl.classList.remove("sa-typing");
state.streamText = "";
setAssistantBody(state.streamEl, "", { live: true });
}
state.gotDelta = true;
state.expectColdLoad = false;
if (state.busyPhase !== "refining") {
setBusyPhase("streaming");
}
state.streamText = (state.streamText || "") + (delta || "");
if (streamHasClosedPatchFence(state.streamText)) {
state.streamText = trimToClosedPatchFence(state.streamText);
state.streamFenceDone = true;
}
setAssistantBody(state.streamEl, state.streamText, { live: true });
scrollMessagesToBottom();
}
}
function finalizeStreamMessage(fullReply, civitaiResults) {
const el = state.streamEl;
const meta = state.streamMeta;
state.streamEl = null;
state.streamMeta = null;
state.streamText = "";
state.streamFenceDone = false;
if (!el) {
appendMessage("assistant", fullReply, null, civitaiResults, meta || void 0);
return;
}
el.classList.remove("sa-streaming", "sa-typing");
mountAssistantMeta(el, meta || void 0);
const { prose, patch } = extractPatch2(fullReply);
setAssistantBody(el, prose || fullReply || "");
el.querySelectorAll(".sa-patch, .sa-civitai-list").forEach((n) => n.remove());
if (patch && !(card && !patch.prompt && !patch.actions && !patch.loras)) {
const silent = !!(meta && meta.silentPatch) || !!state.pendingSilentGen;
mountPatchBlock(el, patch, { silent });
} else if (card) {
const wrap = document.createElement("div");
wrap.className = "sa-patch sa-card-json-preview";
const pre = document.createElement("pre");
pre.textContent = JSON.stringify(card, null, 2);
wrap.appendChild(pre);
el.appendChild(wrap);
}
if (!(meta && meta.historical)) {
mountCurateButtons(el, meta);
}
scrollMessagesToBottom();
}
function mountCurateButtons(msgEl, meta) {
if (!msgEl || msgEl.querySelector(".sa-msg-curate")) {
return;
}
const wrap = document.createElement("div");
wrap.className = "sa-msg-curate";
const ok = document.createElement("button");
ok.type = "button";
ok.className = "basic-button";
ok.title = "\u0412 \u0434\u0430\u0442\u0430\u0441\u0435\u0442 (\u043E\u0434\u043E\u0431\u0440\u0438\u0442\u044C)";
ok.textContent = "+ \u0434\u0430\u0442\u0430\u0441\u0435\u0442";
ok.addEventListener("click", () => curateAssistantMessage(msgEl, "approved"));
const bad = document.createElement("button");
bad.type = "button";
bad.className = "basic-button";
bad.title = "\u041E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C \u0434\u043B\u044F \u0434\u0430\u0442\u0430\u0441\u0435\u0442\u0430";
bad.textContent = "\u0431\u0440\u0430\u043A";
bad.addEventListener("click", () => curateAssistantMessage(msgEl, "rejected"));
wrap.appendChild(ok);
wrap.appendChild(bad);
msgEl.appendChild(wrap);
}
function curateAssistantMessage(msgEl, status) {
const hist = state.history || [];
let asstText = msgEl.querySelector(".sa-msg-body")?.textContent?.trim() || msgEl.textContent?.trim() || "";
let userText = "";
for (let i = hist.length - 1; i >= 0; i--) {
if (hist[i]?.role === "assistant" && (hist[i].content || "").trim() === asstText.trim()) {
for (let j = i - 1; j >= 0; j--) {
if (hist[j]?.role === "user") {
userText = hist[j].content || "";
break;
}
}
break;
}
}
if (!userText) {
for (let i = hist.length - 1; i >= 0; i--) {
if (hist[i]?.role === "user") {
userText = hist[i].content || "";
break;
}
}
}
const messages = [
{ role: "user", content: userText },
{ role: "assistant", content: asstText }
];
window.SA?.training?.curateFromChat?.(messages, {
chatId: state.activeChatId,
persona: $2("sa_persona")?.value || "neutral",
pack: $2("sa_pack")?.value || defaultPackId(),
status
})?.then?.((ok) => {
if (ok) {
setStatus(status === "approved" ? "\u041F\u0440\u0438\u043C\u0435\u0440 \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D \u0432 \u0434\u0430\u0442\u0430\u0441\u0435\u0442" : "\u041F\u0440\u0438\u043C\u0435\u0440 \u043E\u0442\u043C\u0435\u0447\u0435\u043D \u043A\u0430\u043A \u0431\u0440\u0430\u043A");
}
});
}
function isTrainingLocked() {
return !!state.trainingLock || document.getElementById("swarm_assistent_root")?.classList.contains("sa-root-training-lock");
}
function wantsAutoVision() {
return !!$2("sa_auto_vision")?.checked;
}
function looksLikeModelPreview(src) {
const s = String(src || "").toLowerCase();
if (!s) {
return false;
}
return s.includes(".preview.") || s.includes("placeholder") || s.includes("/viewspecial/") || s.includes("viewspecial/") || s.includes("/view/models/") || /\/view\/models\//.test(s) || /[?&](?:path|file)=[^&]*\.preview\./i.test(s);
}
function findCurrentGenerateSrc({ allowPreview = false } = {}) {
let src = null;
try {
const cur = document.getElementById("current_image_img") || document.querySelector("#current_image img") || document.querySelector(".current-image img") || document.querySelector("#current_image_batch img");
if (cur) {
src = cur.dataset?.src || cur.getAttribute?.("data-src") || cur.src || null;
}
} catch (e) {
}
if (!src) {
try {
if (typeof currentMetadataMap !== "undefined" && currentMetadataMap && currentMetadataMap.image) {
src = currentMetadataMap.image;
}
} catch (e) {
}
}
if (!src) {
return null;
}
if (!allowPreview && looksLikeModelPreview(src)) {
return null;
}
return src;
}
function scrubPreviewFromGenerateSlot() {
const slot = generateSlot();
if (!slot?.src) {
return false;
}
if (!looksLikeModelPreview(slot.src)) {
return false;
}
slot.src = null;
slot.attach = false;
syncLastImageAlias();
return true;
}
function refreshImagePreview() {
scrubPreviewFromGenerateSlot();
if (wantsAutoVision()) {
const gen = generateSlot();
if (gen) {
const src = findCurrentGenerateSrc();
if (src) {
gen.attach = true;
gen.src = src;
} else {
gen.attach = false;
}
renderBoard();
}
}
syncGenerateSlot();
}
function fileToDataUrl(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(String(reader.result || ""));
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
async function acceptImageFile(file, slotId) {
if (!file || !String(file.type || "").startsWith("image/")) {
setStatus("Not an image file");
return false;
}
const dataUrl = await fileToDataUrl(file);
if (slotId) {
return setSlotSrc(slotId, dataUrl, { note: `Loaded ${file.name || "image"}` });
}
return putImageOnBoard(dataUrl, { note: `Loaded ${file.name || "image"}` });
}
async function handleDropDataTransfer(dt, slotId) {
if (!dt) {
return false;
}
if (dt.files && dt.files.length) {
for (const file of dt.files) {
if (String(file.type || "").startsWith("image/")) {
return acceptImageFile(file, slotId);
}
}
}
const uri = (dt.getData("text/uri-list") || dt.getData("text/plain") || "").trim();
if (uri) {
const first = uri.split("\n").map((l) => l.trim()).find((l) => l && !l.startsWith("#"));
if (first) {
if (slotId) {
return setSlotSrc(slotId, first, { note: "Image from drag" });
}
return putImageOnBoard(first, { note: "Image from drag" });
}
}
const html = dt.getData("text/html") || "";
const m = html.match(/src=["']([^"']+)["']/i);
if (m && m[1]) {
if (slotId) {
return setSlotSrc(slotId, m[1], { note: "Image from drag" });
}
return putImageOnBoard(m[1], { note: "Image from drag" });
}
return false;
}
async function imageToBase64ForOllama(src, maxEdge = 1024) {
if (!src) {
return null;
}
const dataUrl = await srcToDataUrl(src);
if (!dataUrl) {
return null;
}
try {
const img = await new Promise((resolve, reject) => {
const el = new Image();
el.onload = () => resolve(el);
el.onerror = reject;
el.src = dataUrl;
});
const w = img.naturalWidth || img.width || 0;
const h = img.naturalHeight || img.height || 0;
const edge = Math.max(w, h);
const canvas = document.createElement("canvas");
if (!edge || edge <= maxEdge) {
canvas.width = Math.max(w, 1);
canvas.height = Math.max(h, 1);
canvas.getContext("2d").drawImage(img, 0, 0);
} else {
const scale = maxEdge / edge;
canvas.width = Math.max(1, Math.round(w * scale));
canvas.height = Math.max(1, Math.round(h * scale));
canvas.getContext("2d").drawImage(img, 0, 0, canvas.width, canvas.height);
}
const jpeg = canvas.toDataURL("image/jpeg", 0.85);
const i = jpeg.indexOf(",");
return i >= 0 ? jpeg.slice(i + 1) : null;
} catch (e) {
console.warn("Assistent: vision resize failed", e);
const i = dataUrl.indexOf(",");
return i >= 0 ? dataUrl.slice(i + 1) : null;
}
}
async function srcToDataUrl(src) {
if (src.startsWith("data:")) {
return src;
}
try {
const resp = await fetch(src);
const blob = await resp.blob();
return await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(String(reader.result || ""));
reader.onerror = reject;
reader.readAsDataURL(blob);
});
} catch (e) {
console.warn("Assistent: vision fetch failed", e);
return null;
}
}
function loadSettings() {
const base = localStorage.getItem(LS_BASE);
const model = localStorage.getItem(LS_MODEL);
const pack = localStorage.getItem(LS_PACK);
let persona = localStorage.getItem(LS_PERSONA);
if (persona === "terse") {
persona = "aggressive";
localStorage.setItem(LS_PERSONA, persona);
}
let view = localStorage.getItem(LS_VIEW);
const auto = localStorage.getItem(LS_AUTO_VISION);
const autoApply = localStorage.getItem(LS_AUTO_APPLY);
const autoGen = localStorage.getItem(LS_AUTO_GENERATE);
const autoCrit = localStorage.getItem(LS_AUTO_CRITIQUE);
const autoDl = localStorage.getItem(LS_AUTO_DOWNLOAD);
const parkLlm2 = localStorage.getItem(LS_PARK_LLM);
const paneW = localStorage.getItem(LS_PANE_WIDTH);
if (base && $2("sa_base_url")) {
$2("sa_base_url").value = base;
}
if (pack && $2("sa_pack")) {
$2("sa_pack").value = pack;
}
if (persona && $2("sa_persona")) {
$2("sa_persona").value = persona;
}
if (auto != null && $2("sa_auto_vision")) {
$2("sa_auto_vision").checked = auto === "1";
}
if ($2("sa_auto_apply")) {
$2("sa_auto_apply").checked = autoApply == null ? true : autoApply === "1";
}
if ($2("sa_auto_generate")) {
$2("sa_auto_generate").checked = autoGen == null ? true : autoGen === "1";
}
if ($2("sa_auto_critique") && autoCrit != null) {
$2("sa_auto_critique").checked = autoCrit === "1";
}
if ($2("sa_auto_download") && autoDl != null) {
$2("sa_auto_download").checked = autoDl === "1";
}
if ($2("sa_park_llm")) {
$2("sa_park_llm").checked = parkLlm2 === "1";
}
if (model) {
state.preferredModel = model;
}
const embed = localStorage.getItem(LS_EMBED);
if (embed) {
state.preferredEmbed = embed;
}
if (paneW) {
document.documentElement.style.setProperty("--sa-image-width", paneW);
}
if (view === "cards") {
view = "chat";
}
if (view === "chat" || view === "settings" || view === "train") {
state.view = view;
}
const drawer = localStorage.getItem(LS_CHATS_DRAWER);
if (drawer != null) {
state.chatsDrawerOpen = drawer === "1";
state.chatsPanelOpen = state.chatsDrawerOpen;
}
const boardTab = localStorage.getItem(LS_BOARD_TAB);
if (boardTab === "refs" || boardTab === "generate") {
state.boardTab = boardTab;
}
}
function collectUiState() {
return {
pack: $2("sa_pack")?.value || defaultPackId(),
persona: $2("sa_persona")?.value || "neutral",
auto_vision: !!$2("sa_auto_vision")?.checked,
auto_apply: !!$2("sa_auto_apply")?.checked,
auto_generate: true,
auto_critique: !!$2("sa_auto_critique")?.checked,
auto_download: !!$2("sa_auto_download")?.checked,
park_llm: !!$2("sa_park_llm")?.checked,
pane_width: localStorage.getItem(LS_PANE_WIDTH) || "",
embed_model: $2("sa_embed_model")?.value || state.preferredEmbed || "",
base_url: $2("sa_base_url")?.value || "",
model: $2("sa_model")?.value || "",
view: state.view || "chat",
board_tab: state.boardTab || "generate",
chats_drawer: state.chatsDrawerOpen ? "1" : "0"
};
}
async function applyDiskUiState() {
const persist = diskPersist();
if (!persist) {
return;
}
let ui = null;
try {
ui = await persist.loadUiState();
} catch (e) {
return;
}
if (!ui || typeof ui !== "object") {
return;
}
const fill = (lsKey, value, apply) => {
if (value == null || value === "" || localStorage.getItem(lsKey) != null) {
return;
}
localStorage.setItem(lsKey, String(value));
apply?.(String(value));
};
fill(LS_BASE, ui.base_url, (v) => {
if ($2("sa_base_url")) {
$2("sa_base_url").value = v;
}
});
fill(LS_MODEL, ui.model, (v) => {
state.preferredModel = v;
});
fill(LS_EMBED, ui.embed_model, (v) => {
state.preferredEmbed = v;
});
fill(LS_PACK, ui.pack, (v) => {
if ($2("sa_pack")) {
$2("sa_pack").value = v;
}
});
fill(LS_PERSONA, ui.persona, (v) => {
if ($2("sa_persona")) {
$2("sa_persona").value = v;
}
});
fill(LS_PANE_WIDTH, ui.pane_width, (v) => document.documentElement.style.setProperty("--sa-image-width", v));
if (ui.view === "cards") {
ui.view = "chat";
}
if (ui.view === "chat" || ui.view === "settings" || ui.view === "train") {
fill(LS_VIEW, ui.view, (v) => {
state.view = v;
});
}
if (ui.board_tab === "refs" || ui.board_tab === "generate") {
fill(LS_BOARD_TAB, ui.board_tab, (v) => {
state.boardTab = v;
});
}
if (ui.chats_drawer != null && localStorage.getItem(LS_CHATS_DRAWER) == null) {
const open = ui.chats_drawer === true || ui.chats_drawer === "1" || ui.chats_drawer === 1;
localStorage.setItem(LS_CHATS_DRAWER, open ? "1" : "0");
state.chatsDrawerOpen = open;
state.chatsPanelOpen = open;
}
for (const [key, lsKey, id] of [
["auto_vision", LS_AUTO_VISION, "sa_auto_vision"],
["auto_apply", LS_AUTO_APPLY, "sa_auto_apply"],
["auto_generate", LS_AUTO_GENERATE, "sa_auto_generate"],
["auto_critique", LS_AUTO_CRITIQUE, "sa_auto_critique"],
["auto_download", LS_AUTO_DOWNLOAD, "sa_auto_download"],
["park_llm", LS_PARK_LLM, "sa_park_llm"]
]) {
if (ui[key] == null || localStorage.getItem(lsKey) != null) {
continue;
}
const on = ui[key] === true || ui[key] === "1" || ui[key] === 1;
if (on && key === "auto_download") {
continue;
}
localStorage.setItem(lsKey, on ? "1" : "0");
const el = $2(id);
if (el) {
el.checked = on;
}
}
}
function saveUiStateToDisk() {
diskPersist()?.saveUiState(collectUiState());
}
function saveSettings() {
localStorage.setItem(LS_BASE, $2("sa_base_url")?.value || "");
localStorage.setItem(LS_MODEL, $2("sa_model")?.value || "");
localStorage.setItem(LS_EMBED, $2("sa_embed_model")?.value || state.preferredEmbed || "");
localStorage.setItem(LS_PACK, $2("sa_pack")?.value || defaultPackId());
localStorage.setItem(LS_PERSONA, $2("sa_persona")?.value || "neutral");
localStorage.setItem(LS_VIEW, state.view || "chat");
localStorage.setItem(LS_AUTO_VISION, $2("sa_auto_vision")?.checked ? "1" : "0");
localStorage.setItem(LS_AUTO_APPLY, $2("sa_auto_apply")?.checked ? "1" : "0");
localStorage.setItem(LS_AUTO_GENERATE, $2("sa_auto_generate")?.checked ? "1" : "0");
localStorage.setItem(LS_AUTO_CRITIQUE, $2("sa_auto_critique")?.checked ? "1" : "0");
localStorage.setItem(LS_AUTO_DOWNLOAD, $2("sa_auto_download")?.checked ? "1" : "0");
localStorage.setItem(LS_PARK_LLM, $2("sa_park_llm")?.checked ? "1" : "0");
persistServerSettings();
saveUiStateToDisk();
}
function persistServerSettings() {
if (typeof genericRequest !== "function") {
return;
}
const skills = {};
document.querySelectorAll("#sa_skills_box input[data-skill]")?.forEach((el) => {
skills[el.getAttribute("data-skill")] = !!el.checked;
});
const persona = $2("sa_persona")?.value || "neutral";
const settings = {
embed_model: $2("sa_embed_model")?.value || state.preferredEmbed || "",
base_url: $2("sa_base_url")?.value || "",
[persona]: { skills }
};
genericRequest("AssistentSaveSettings", { settings }, () => {
}, 0, () => {
});
}
function applyConfigPayload(data, { applyDefaults = false } = {}) {
if (!data || data.error) {
return;
}
const prevPersona = state.config?.persona || $2("sa_persona")?.value || "";
const prevControls = state.config?.control_values && typeof state.config.control_values === "object" ? { ...state.config.control_values } : null;
state.config = data;
if (window.SA?.applyConfigPatchKeys) {
window.SA.applyConfigPatchKeys(data);
}
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);
}
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 };
}
if (data.ui?.welcome_html) {
WELCOME_HTML = data.ui.welcome_html;
}
if (data.ui?.help_text) {
HELP_TEXT = data.ui.help_text;
}
if (Array.isArray(data.ui?.slash) && data.ui.slash.length) {
SLASH_COMMANDS = data.ui.slash.map((s) => ({
cmd: s.cmd || "",
hint: s.hint || "",
action: s.action || ""
}));
}
if (Array.isArray(data.ui?.slash_extra) && data.ui.slash_extra.length) {
for (const s of data.ui.slash_extra) {
const cmd = s.cmd || "";
if (!cmd || SLASH_COMMANDS.some((c) => c.cmd === cmd)) {
continue;
}
SLASH_COMMANDS.push({
cmd,
hint: s.hint || "",
action: s.action || ""
});
}
}
if (data.ui?.help_extra) {
HELP_TEXT = `${HELP_TEXT || ""}
${data.ui.help_extra}`.trim();
}
state.enabledSkills = Array.isArray(data.enabled_skills) ? data.enabled_skills.slice() : [];
if (Array.isArray(data.personas)) {
state.personas = data.personas;
}
renderPersonaOptions(data.personas || [], data.persona || data.default_persona);
renderPackOptions(data.packs || [], applyDefaults ? data.assistant?.default_pack : null);
renderChips(data.ui?.chips || []);
renderSkillChecks(data.skills || [], state.enabledSkills);
if (applyDefaults && data.assistant?.default_pack && $2("sa_pack") && !localStorage.getItem(LS_PACK)) {
$2("sa_pack").value = data.assistant.default_pack;
}
if (data.assistant?.embed_model && !state.preferredEmbed) {
state.preferredEmbed = data.assistant.embed_model;
}
const asst = data.assistant || {};
if (asst.history_keep_turns != null) {
HISTORY_KEEP_TURNS = Math.max(1, Number(asst.history_keep_turns) || 4);
}
if (asst.compress_at != null) {
COMPRESS_AT = Math.min(0.95, Math.max(0.4, Number(asst.compress_at) || 0.7));
}
if (asst.chars_per_token != null) {
CHARS_PER_TOKEN = Math.max(1.5, Number(asst.chars_per_token) || 3.2);
}
if (asst.compress_auto != null) {
COMPRESS_AUTO = !!asst.compress_auto;
}
if (asst.max_ref_slots != null) {
MAX_REF_SLOTS = Math.max(1, Number(asst.max_ref_slots) || 4);
}
if (asst.max_gen_variants != null) {
MAX_GEN_VARIANTS = Math.max(2, Math.min(8, Number(asst.max_gen_variants) || 4));
}
if (asst.context_prompt_max != null) {
CONTEXT_PROMPT_MAX = Math.max(200, Number(asst.context_prompt_max) || 2e3);
}
if (asst.inventory_prompt_rich != null) {
INVENTORY_PROMPT_RICH = Math.max(4, Number(asst.inventory_prompt_rich) || 12);
}
if (asst.inventory_prompt_names != null) {
INVENTORY_PROMPT_NAMES = Math.max(INVENTORY_PROMPT_RICH, Number(asst.inventory_prompt_names) || 24);
}
fillKnobsFromConfig(data);
updateCtxChip();
if (applyDefaults || data.exact) {
fillEmptyParamsFromExact();
}
const nextPersona = data.persona || $2("sa_persona")?.value || "";
let controlValues = data.control_values || data.exact?.controls || {};
if (!applyDefaults && prevControls && nextPersona === prevPersona) {
controlValues = { ...controlValues, ...prevControls };
state.config.control_values = controlValues;
if (state.exact) {
state.exact.controls = { ...state.exact.controls || {}, ...prevControls };
}
}
renderPersonaControls(data.controls || {}, controlValues);
syncPersonaDeleteButton(data.persona_source || data.personas?.find((p) => p.id === (data.persona || $2("sa_persona")?.value))?.source);
}
function syncPersonaDeleteButton(source) {
const btn = $2("sa_persona_delete");
if (!btn) {
return;
}
const src = String(source || "");
const canDelete = src === "overlay" || src === "overlay+bundled";
btn.hidden = !canDelete;
btn.disabled = !canDelete;
}
let controlSaveTimer = null;
let controlsPointerDown = false;
let pendingControlsRender = null;
function renderPersonaControls(schema, values) {
const box = $2("sa_persona_controls");
if (!box) {
return;
}
if (controlsPointerDown) {
pendingControlsRender = { schema, values };
return;
}
pendingControlsRender = null;
box.innerHTML = "";
const keys = schema && typeof schema === "object" ? Object.keys(schema) : [];
if (!keys.length) {
box.hidden = true;
return;
}
box.hidden = false;
const ordered = keys.slice().sort((a, b) => {
const oa = Number(schema[a]?.order ?? 100);
const ob = Number(schema[b]?.order ?? 100);
if (oa !== ob) {
return oa - ob;
}
return String(a).localeCompare(String(b));
});
for (const id of ordered) {
const def = schema[id];
if (!def || typeof def !== "object") {
continue;
}
if (String(def.type || "slider").toLowerCase() !== "slider") {
continue;
}
const min = Number(def.min ?? -1);
const max = Number(def.max ?? 1);
const step = Number(def.step ?? 0.05);
const defVal = Number(def.default ?? 0);
let cur = values && values[id] != null ? Number(values[id]) : defVal;
if (Number.isNaN(cur)) {
cur = defVal;
}
const asPercent = String(def.display || "").toLowerCase() === "percent";
const fmt = (v) => asPercent ? `${Math.round(v)}%` : Number(v).toFixed(2);
const row = document.createElement("div");
row.className = "sa-control-row";
row.title = def.hint || id;
const lab = document.createElement("label");
lab.textContent = def.label || id;
const input = document.createElement("input");
input.type = "range";
input.min = String(min);
input.max = String(max);
input.step = String(step);
input.value = String(cur);
input.dataset.controlId = id;
const valEl = document.createElement("span");
valEl.className = "sa-control-val";
valEl.textContent = fmt(cur);
const applyLocal = (v) => {
valEl.textContent = fmt(v);
if (state.config) {
state.config.control_values = { ...state.config.control_values || {}, [id]: v };
}
if (state.exact) {
state.exact.controls = { ...state.exact.controls || {}, [id]: v };
}
};
input.addEventListener("pointerdown", () => {
controlsPointerDown = true;
});
const endPointer = () => {
const v = Number(input.value);
applyLocal(v);
controlsPointerDown = false;
if (pendingControlsRender) {
const schema2 = pendingControlsRender.schema;
pendingControlsRender = null;
renderPersonaControls(
schema2,
state.config?.control_values || state.exact?.controls || {}
);
}
if (controlSaveTimer) {
clearTimeout(controlSaveTimer);
}
controlSaveTimer = setTimeout(() => savePersonaControls({ [id]: v }), 50);
};
input.addEventListener("pointerup", endPointer);
input.addEventListener("pointercancel", endPointer);
input.addEventListener("input", () => {
applyLocal(Number(input.value));
});
input.addEventListener("change", () => {
const v = Number(input.value);
applyLocal(v);
if (controlSaveTimer) {
clearTimeout(controlSaveTimer);
}
controlSaveTimer = setTimeout(() => savePersonaControls({ [id]: v }), 50);
});
row.appendChild(lab);
row.appendChild(input);
row.appendChild(valEl);
box.appendChild(row);
}
}
function getControlValue(id, fallback) {
const v = state.config?.control_values?.[id] ?? state.exact?.controls?.[id];
const n = Number(v);
return Number.isFinite(n) ? n : fallback;
}
function coolDownHorny() {
const persona = $2("sa_persona")?.value || "";
if (persona !== "leonid") {
setStatus("/\u043E\u0441\u0442\u044B\u043D\u044C \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F Leonid");
return;
}
state.lastUserControlIntent = true;
const schema = state.config?.controls || {};
if (!schema.horny) {
setStatus("\u0423 \u044D\u0442\u043E\u0439 \u043B\u0438\u0447\u043D\u043E\u0441\u0442\u0438 \u043D\u0435\u0442 \u0441\u043B\u0430\u0439\u0434\u0435\u0440\u0430 \u0425\u043E\u0440\u043D\u0438");
return;
}
const min = Number(schema.horny.min ?? 0);
const max = Number(schema.horny.max ?? 100);
const cur = getControlValue("horny", Number(schema.horny.default ?? 35));
const next = Math.max(min, Math.min(max, cur - 30));
const values = {
...state.config?.control_values || state.exact?.controls || {},
horny: next
};
if (state.config) {
state.config.control_values = values;
}
if (state.exact) {
state.exact.controls = values;
}
renderPersonaControls(schema, values);
savePersonaControls({ horny: next });
appendSystemNote(`\u0425\u043E\u0440\u043D\u0438: ${Math.round(cur)}% \u2192 ${Math.round(next)}% (\u221230)`);
setStatus(`/\u043E\u0441\u0442\u044B\u043D\u044C \u2192 ${Math.round(next)}%`);
}
async function startHornyGame() {
const persona = $2("sa_persona")?.value || "";
if (persona !== "leonid") {
setStatus("/horny-game \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F Leonid");
return;
}
const cur = getControlValue("horny", 35);
state.lastUserControlIntent = true;
await sendChat({
skipSlash: true,
skipAutoPack: true,
forcedUserText: `\u041A\u043E\u043C\u0430\u043D\u0434\u0430 /horny-game. \u0422\u0435\u043A\u0443\u0449\u0438\u0439 controls.horny = ${Math.round(cur)} (0\u2013100).
\u041E\u0446\u0435\u043D\u0438, \u043D\u0430\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0432\u043A\u0443\u0441\u044B \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F \u0432 \u044D\u0442\u043E\u043C \u0447\u0430\u0442\u0435 / \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u043C \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0438 \u0441\u043E\u0432\u043F\u0430\u0434\u0430\u044E\u0442 \u0441 \u0442\u0432\u043E\u0438\u043C\u0438 (roleplay, outfits, realism, fetishes).
\u041F\u043E\u0441\u0442\u0430\u0432\u044C \u043D\u043E\u0432\u044B\u0439 controls.horny: \u0443\u043C\u043D\u043E\u0436\u044C/\u0441\u0434\u0432\u0438\u043D\u044C \u0442\u0435\u043A\u0443\u0449\u0435\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u043F\u0440\u043E\u043F\u043E\u0440\u0446\u0438\u043E\u043D\u0430\u043B\u044C\u043D\u043E \xAB\u043D\u0430\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0442\u0435\u0431\u0435 \u044D\u0442\u043E \u0437\u0430\u0448\u043B\u043E\xBB (\u0441\u043B\u0430\u0431\u043E\u0435 \u0441\u043E\u0432\u043F\u0430\u0434\u0435\u043D\u0438\u0435 \u2192 \u0447\u0443\u0442\u044C \u0432\u043D\u0438\u0437 \u0438\u043B\u0438 \u043F\u043E\u0447\u0442\u0438 \u0431\u0435\u0437 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439; \u0441\u0438\u043B\u044C\u043D\u043E\u0435 \u2192 \u0437\u0430\u043C\u0435\u0442\u043D\u044B\u0439 \u0440\u043E\u0441\u0442, clamp 0\u2013100).
\u0412 \u043F\u0440\u043E\u0437\u0435 \u0441\u043A\u0430\u0436\u0438 \u043A\u0440\u0430\u0442\u043A\u043E: \u0441\u043E\u0432\u043F\u0430\u043B\u043E \u043B\u0438, \u043A\u0430\u043A\u043E\u0439 \u043C\u043D\u043E\u0436\u0438\u0442\u0435\u043B\u044C/\u0441\u0434\u0432\u0438\u0433 \u0438 \u043D\u043E\u0432\u044B\u0439 %. \u041E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D JSON patch \u0441 "controls": { "horny": <number> }. \u0411\u0435\u0437 generate, \u0435\u0441\u043B\u0438 \u043D\u0435 \u043F\u0440\u043E\u0441\u0438\u043B\u0438 \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0443.`
});
setStatus("/horny-game\u2026");
}
function savePersonaControls(partial) {
const persona = $2("sa_persona")?.value || "neutral";
if (typeof genericRequest !== "function") {
return;
}
if (partial && typeof partial === "object") {
if (state.config) {
state.config.control_values = { ...state.config.control_values || {}, ...partial };
}
if (state.exact) {
state.exact.controls = { ...state.exact.controls || {}, ...partial };
}
}
genericRequest(
"AssistentSaveControls",
{ persona, controls: partial || {} },
(data) => {
if (data?.error) {
setStatus(data.error);
return;
}
if (data?.control_values && state.config) {
state.config.control_values = data.control_values;
if (state.exact) {
state.exact.controls = data.control_values;
}
}
if (!controlsPointerDown && data?.control_values) {
syncPersonaControlInputs(data.control_values);
}
},
0,
() => setStatus("controls save failed")
);
}
function syncPersonaControlInputs(values) {
const box = $2("sa_persona_controls");
if (!box || !values || typeof values !== "object") {
return;
}
box.querySelectorAll("input[data-control-id]").forEach((input) => {
const id = input.dataset.controlId;
if (values[id] == null) {
return;
}
const v = Number(values[id]);
if (!Number.isFinite(v) || input.value === String(v)) {
return;
}
input.value = String(v);
const valEl = input.parentElement?.querySelector(".sa-control-val");
if (valEl) {
const schema = state.config?.controls?.[id];
const asPercent = String(schema?.display || "").toLowerCase() === "percent";
valEl.textContent = asPercent ? `${Math.round(v)}%` : Number(v).toFixed(2);
}
});
}
async function deleteCurrentOverlayPersona() {
const id = $2("sa_persona")?.value;
if (!id) {
return;
}
const meta = (state.personas || []).find((p) => p.id === id);
const title = meta?.title || id;
const src = meta?.source || state.config?.persona_source || "";
if (src !== "overlay" && src !== "overlay+bundled") {
setStatus("Bundled personas cannot be deleted");
return;
}
if (!window.confirm(`\u0423\u0434\u0430\u043B\u0438\u0442\u044C \xAB${title}\xBB?
\u041F\u043E\u0441\u0442\u0430\u0432\u043A\u0430 (bundled) \u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u0435\u0442\u0441\u044F.`)) {
return;
}
await new Promise((resolve) => {
genericRequest(
"AssistentDeletePersona",
{ persona: id },
async (data) => {
if (data?.error) {
setStatus(data.error);
resolve();
return;
}
const next = data?.default_persona || "neutral";
if (Array.isArray(data?.personas)) {
state.personas = data.personas;
}
renderPersonaOptions(state.personas || [], next);
if ($2("sa_persona")) {
$2("sa_persona").value = next;
}
await applyPersonaForChat(next, { quiet: false });
setStatus(`\u0423\u0434\u0430\u043B\u0435\u043D\u043E: ${id}`);
resolve();
},
0,
() => {
setStatus("delete failed");
resolve();
}
);
});
}
function renderPersonaOptions(personas, selected) {
const sel = $2("sa_persona");
if (!sel) {
return;
}
const cur = selected || sel.value || localStorage.getItem(LS_PERSONA) || "neutral";
sel.innerHTML = "";
for (const p of personas) {
const opt = document.createElement("option");
opt.value = p.id;
opt.textContent = p.title || p.id;
if (p.accent) {
opt.dataset.accent = p.accent;
}
sel.appendChild(opt);
}
if ([...sel.options].some((o) => o.value === cur)) {
sel.value = cur;
}
const meta = (personas || []).find((p) => p.id === sel.value);
syncPersonaDeleteButton(meta?.source || state.config?.persona_source);
}
function renderPackOptions(packs, preferred) {
const sel = $2("sa_pack");
if (!sel) {
return;
}
const cur = preferred || sel.value || localStorage.getItem(LS_PACK) || defaultPackId();
sel.innerHTML = "";
const list = (packs || []).slice().sort((a, b) => (a.order || 100) - (b.order || 100));
for (const p of list) {
const opt = document.createElement("option");
opt.value = p.id;
opt.textContent = p.title || p.id;
sel.appendChild(opt);
}
if ([...sel.options].some((o) => o.value === cur)) {
sel.value = cur;
}
}
function renderChips(chips) {
const box = $2("sa_chips");
if (!box || !Array.isArray(chips) || !chips.length) {
return;
}
box.innerHTML = "";
for (const c of chips) {
if (c.sep) {
const sep = document.createElement("span");
sep.className = "sa-chip-sep";
sep.setAttribute("aria-hidden", "true");
box.appendChild(sep);
continue;
}
const btn = document.createElement("button");
btn.type = "button";
btn.className = "sa-chip";
btn.textContent = c.label || c.value || "";
if (c.title) {
btn.title = c.title;
}
const action = c.action || "";
const value = c.value ?? "";
if (action === "aspect") {
btn.setAttribute("data-aspect", value);
} else if (action === "seed") {
btn.setAttribute("data-seed", value);
} else if (action === "vary") {
btn.setAttribute("data-vary", value || "1");
} else if (action === "krea_profile") {
btn.setAttribute("data-krea-profile", value);
}
box.appendChild(btn);
}
}
function renderSkillChecks(skills, enabled) {
const box = $2("sa_skills_box");
if (!box) {
return;
}
const on = new Set(enabled || []);
box.innerHTML = "";
for (const s of skills || []) {
const label = document.createElement("label");
label.className = "sa-check";
const input = document.createElement("input");
input.type = "checkbox";
input.setAttribute("data-skill", s.id);
input.checked = on.has(s.id) || !enabled?.length && !!s.default;
input.addEventListener("change", () => {
state.enabledSkills = [...document.querySelectorAll("#sa_skills_box input[data-skill]:checked")].map((el) => el.getAttribute("data-skill"));
saveSettings();
});
label.appendChild(input);
label.appendChild(document.createTextNode(` ${s.title || s.id}`));
box.appendChild(label);
}
state.enabledSkills = [...document.querySelectorAll("#sa_skills_box input[data-skill]:checked")].map((el) => el.getAttribute("data-skill"));
}
function loadConfig(persona, done) {
if (typeof genericRequest !== "function") {
done?.(null);
return;
}
genericRequest(
"AssistentGetConfig",
{ persona: persona || $2("sa_persona")?.value || "neutral" },
(data) => {
applyConfigPayload(data, { applyDefaults: true });
done?.(data);
},
0,
() => done?.(null)
);
}
function chatModelSeniority(name) {
const n = String(name || "").toLowerCase();
let score = 0;
const m = n.match(/(?:^|[:\-/])(\d+)\s*b\b/);
if (m) {
score += Number(m[1]) * 1e6;
}
if (n.includes("instruct")) {
score += 5e4;
}
if (n.includes("qwen3")) {
score += 2e4;
}
if (n.includes("thinking") || n.endsWith(":latest")) {
score -= 1e4;
}
return score;
}
function pickSeniorChatModel(names) {
const list = (names || []).map((n) => String(n || "").trim()).filter(Boolean);
if (!list.length) {
return "";
}
return [...list].sort((a, b) => chatModelSeniority(b) - chatModelSeniority(a) || a.localeCompare(b))[0];
}
function resolveChatModel(names, apiPreferred) {
const list = (names || []).map((n) => String(n || "").trim()).filter(Boolean);
if (!list.length) {
return "";
}
const preferred = apiPreferred && list.includes(apiPreferred) ? apiPreferred : pickSeniorChatModel(list);
const ls = state.preferredModel || localStorage.getItem(LS_MODEL) || "";
if (ls && list.includes(ls)) {
return ls;
}
return preferred || list[0];
}
function setModelOptions(models, { error, preferred } = {}) {
const sel = $2("sa_model");
const sel2 = $2("sa_settings_chat_model");
const apply = (target) => {
if (!target) {
return;
}
let names = (models || []).map((n) => String(n || "").trim()).filter(Boolean);
names = [...names].sort((a, b) => chatModelSeniority(b) - chatModelSeniority(a) || a.localeCompare(b));
target.innerHTML = "";
if (error) {
const opt = document.createElement("option");
opt.value = "";
opt.textContent = `\u26A0 ${String(error).replace(/\s+/g, " ").slice(0, 90)}`;
target.appendChild(opt);
target.disabled = true;
return;
}
target.disabled = false;
if (!names.length) {
const opt = document.createElement("option");
opt.value = "";
opt.textContent = "No Ollama models \u2014 pull / Refresh";
target.appendChild(opt);
return;
}
for (const name of names) {
const opt = document.createElement("option");
opt.value = name;
opt.textContent = name;
target.appendChild(opt);
}
const pick = resolveChatModel(names, preferred);
if (pick) {
target.value = pick;
}
};
apply(sel);
apply(sel2);
}
function setEmbedModelOptions(models) {
const sel = $2("sa_embed_model");
if (!sel) {
return;
}
const names = (models || []).map((n) => String(n || "").trim()).filter(Boolean);
sel.innerHTML = "";
if (!names.length) {
const opt = document.createElement("option");
opt.value = state.preferredEmbed || "nomic-embed-text";
opt.textContent = opt.value + " (\u043E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F pull)";
sel.appendChild(opt);
return;
}
for (const name of names) {
const opt = document.createElement("option");
opt.value = name;
opt.textContent = name;
sel.appendChild(opt);
}
const prefer = state.preferredEmbed || localStorage.getItem(LS_EMBED) || state.config?.assistant?.embed_model;
if (prefer && names.includes(prefer)) {
sel.value = prefer;
} else if (prefer && !names.includes(prefer)) {
const opt = document.createElement("option");
opt.value = prefer;
opt.textContent = prefer;
sel.appendChild(opt);
sel.value = prefer;
}
}
function refreshModels() {
const baseUrl = $2("sa_base_url")?.value || "http://127.0.0.1:11434";
setStatus("Loading models\u2026");
if (typeof genericRequest !== "function") {
setStatus("SwarmUI API not ready");
setModelOptions([], { error: "SwarmUI API not ready" });
return;
}
genericRequest(
"AssistentListModels",
{ baseUrl },
(data) => {
const models = data.models || [];
const memoryModels = data.memory_models || [];
const preferred = (data.preferred || "").trim();
setModelOptions(models, { preferred });
setEmbedModelOptions(memoryModels);
const pick = resolveChatModel(models, preferred);
if (pick && $2("sa_model")) {
$2("sa_model").value = pick;
if ($2("sa_settings_chat_model")) {
$2("sa_settings_chat_model").value = pick;
}
state.preferredModel = pick;
localStorage.setItem(LS_MODEL, pick);
}
setStatus(models.length ? `${models.length} chat \xB7 ${memoryModels.length} memory` : "No Ollama models (gpu-rent: ollama pull)");
if (models.length) {
setOllamaHealth("ok", `Ollama \xB7 ${models.length}`, `\u0427\u0430\u0442-\u043C\u043E\u0434\u0435\u043B\u0435\u0439: ${models.length}, \u043F\u0430\u043C\u044F\u0442\u044C: ${memoryModels.length}`);
} else {
setOllamaHealth("warn", "Ollama \xB7 0 \u043C\u043E\u0434\u0435\u043B\u0435\u0439", "\u041D\u0435\u0442 \u0447\u0430\u0442-\u043C\u043E\u0434\u0435\u043B\u0435\u0439 \u2014 \u0441\u0434\u0435\u043B\u0430\u0439 ollama pull");
}
saveSettings();
},
0,
(err) => {
const msg = String(err || "Ollama unreachable");
setStatus(msg);
setModelOptions([], { error: msg });
setOllamaHealth("down", "Ollama \u2715", msg);
appendMessage("error", msg);
}
);
}
function refreshInventory(done, opts = {}) {
if (typeof genericRequest !== "function") {
if (done) {
done();
}
return;
}
const rescan = !!opts.rescan;
genericRequest(
"AssistentListInventory",
{ rescan },
(data) => {
state.inventory = {
loras: data.loras || [],
checkpoints: data.checkpoints || [],
wildcards: data.wildcards || [],
has_civitai_key: !!data.has_civitai_key,
inventory_at: data.inventory_at || Math.floor(Date.now() / 1e3),
rescanned: !!data.rescanned
};
state.inventoryFetchedAt = Date.now();
const n = state.inventory.loras.length;
const ck = state.inventory.checkpoints.length;
setStatus(`Inventory: ${n} LoRAs, ${ck} ckpts${rescan ? " (rescanned)" : ""}`);
if (state.view === "cards") {
}
if (done) {
done(state.inventory);
}
},
0,
(err) => {
console.warn("Assistent inventory", err);
if (done) {
done(null);
}
}
);
}
function refreshInventoryAsync(opts = {}) {
return new Promise((resolve) => refreshInventory(resolve, opts));
}
function memoryKindFilter() {
return $2("sa_mem_kind")?.value || "all";
}
function memoryScopeFilter() {
return $2("sa_mem_scope")?.value || "all";
}
function memorySearchFilter() {
return ($2("sa_mem_search")?.value || "").trim().toLowerCase();
}
function renderMemoryKinds(kinds) {
const sel = $2("sa_mem_kind");
if (!sel) {
return;
}
const cur = sel.value || "all";
sel.innerHTML = "";
const all = document.createElement("option");
all.value = "all";
all.textContent = "\u0412\u0441\u0435 \u0442\u0438\u043F\u044B";
sel.appendChild(all);
for (const kind of kinds || []) {
const opt = document.createElement("option");
opt.value = kind;
opt.textContent = kind;
sel.appendChild(opt);
}
if ([...sel.options].some((o) => o.value === cur)) {
sel.value = cur;
}
}
function filteredMemoryRows() {
const filter = memoryKindFilter();
const scope = memoryScopeFilter();
const q = memorySearchFilter();
const persona = $2("sa_persona")?.value || "neutral";
return (state.memoryRows || []).filter((m) => {
if (filter !== "all" && m.kind !== filter) {
return false;
}
if (scope === "shared" && m.scope !== "shared") {
return false;
}
if (scope === "personal" && !(m.scope === "personal" && (m.persona === persona || !m.persona))) {
return false;
}
if (q) {
const hay = `${m.kind || ""} ${m.key || ""} ${m.text || ""}`.toLowerCase();
if (!hay.includes(q)) {
return false;
}
}
return true;
});
}
function renderMemoryList() {
const root = $2("sa_mem_list");
if (!root) {
return;
}
const rows = filteredMemoryRows();
root.innerHTML = "";
if (!rows.length) {
root.innerHTML = '<div class="sa-mem-empty">\u041A\u0440\u0430\u0444\u0442-\u043F\u0430\u043C\u044F\u0442\u044C \u043F\u0443\u0441\u0442\u0430 \u2014 \u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0438, seed \u0438 \u043F\u0430\u0442\u0447\u0438 <code>memory_upsert</code>.</div>';
return;
}
for (const row of rows) {
const el = document.createElement("div");
el.className = "sa-mem-row";
const bundled = row.source === "bundled";
const when = row.updated ? formatChatWhen(row.updated * 1e3) : "";
const scope = row.scope === "personal" ? `\u043F\u0435\u0440\u0441\u043E\u043D\u0430 ${row.persona || "\u2014"}` : "\u043E\u0431\u0449\u0430\u044F";
el.innerHTML = `<div class="sa-mem-row-body"><div class="sa-mem-row-head"><span class="sa-mem-kind-badge">${escapeHtml2(row.kind || "note")}</span><span class="sa-mem-row-key">${escapeHtml2(row.key || "")}</span></div><div class="sa-mem-row-text">${escapeHtml2(clipDebug(row.text, 220))}</div><div class="sa-mem-row-meta">${escapeHtml2([scope, row.source || "user", when].filter(Boolean).join(" \xB7 "))}</div></div>`;
const forget = document.createElement("button");
forget.type = "button";
forget.className = "basic-button sa-mem-forget";
forget.textContent = "\xD7";
if (bundled) {
forget.disabled = true;
forget.title = "Bundled \u2014 \u0432\u0435\u0440\u043D\u0451\u0442\u0441\u044F \u043F\u0440\u0438 reseed, \u043F\u0440\u0430\u0432\u044C Config/_base/memory-seed/";
} else {
forget.title = "\u0417\u0430\u0431\u044B\u0442\u044C";
forget.addEventListener("click", () => forgetMemory(row));
}
el.appendChild(forget);
root.appendChild(el);
}
}
function refreshMemoryList() {
if (typeof genericRequest !== "function") {
return;
}
const list = $2("sa_mem_list");
if (list && !state.memoryRows.length) {
list.innerHTML = '<div class="sa-mem-empty">\u0427\u0438\u0442\u0430\u044E \u043F\u0430\u043C\u044F\u0442\u044C\u2026</div>';
}
genericRequest(
"AssistentListMemory",
{ limit: 200 },
(data) => {
state.memoryRows = Array.isArray(data?.memories) ? data.memories : [];
renderMemoryKinds(data?.kinds || []);
renderMemoryList();
const foot = $2("sa_mem_total");
if (foot) {
foot.textContent = `\u0412\u0441\u0435\u0433\u043E: ${data?.total ?? state.memoryRows.length} \xB7 ${data?.embed_model || "\u2014"}`;
}
},
0,
(err) => {
if (list) {
list.innerHTML = `<div class="sa-mem-empty">\u041F\u0430\u043C\u044F\u0442\u044C \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0430: ${escapeHtml2(String(err || "\u043E\u0448\u0438\u0431\u043A\u0430"))}</div>`;
}
}
);
}
function forgetMemory(row) {
if (!row?.kind || !row?.key || typeof genericRequest !== "function") {
return;
}
genericRequest(
"AssistentForgetMemory",
{
kind: row.kind,
key: row.key,
source: row.source || "",
scope: row.scope || "",
persona: row.scope === "personal" ? row.persona || "" : ""
},
() => {
state.memoryRows = (state.memoryRows || []).filter((m) => !(m.kind === row.kind && m.key === row.key && m.source === row.source && m.persona === row.persona));
renderMemoryList();
setStatus(`\u0417\u0430\u0431\u044B\u0442\u043E: ${row.kind}/${row.key}`);
},
0,
(err) => setStatus(String(err || "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0431\u044B\u0442\u044C"))
);
}
function clearCraftMemory(opts = {}) {
if (typeof genericRequest !== "function") {
return;
}
const label = opts.label || "\u043A\u0440\u0430\u0444\u0442-\u043F\u0430\u043C\u044F\u0442\u044C";
if (!window.confirm(`\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C ${label}? Bundled seed \u043E\u0441\u0442\u0430\u043D\u0435\u0442\u0441\u044F.`)) {
return;
}
const body = {
scope: opts.scope || "",
kind: opts.kind || "",
persona: opts.persona || ""
};
genericRequest(
"AssistentClearMemory",
body,
(data) => {
setStatus(`\u0423\u0434\u0430\u043B\u0435\u043D\u043E \u043A\u0440\u0430\u0444\u0442-\u0437\u0430\u043F\u0438\u0441\u0435\u0439: ${data?.deleted ?? 0}`);
refreshMemoryList();
},
0,
(err) => setStatus(String(err || "\u041E\u0447\u0438\u0441\u0442\u043A\u0430 \u043D\u0435 \u0443\u0434\u0430\u043B\u0430\u0441\u044C"))
);
}
function setSettingsTab(id) {
state.settingsTab = id || "behavior";
document.querySelectorAll("#sa_settings .sa-stab").forEach((btn) => {
const on = btn.getAttribute("data-stab") === state.settingsTab;
btn.classList.toggle("sa-stab-active", on);
btn.setAttribute("aria-selected", on ? "true" : "false");
});
document.querySelectorAll("#sa_settings .sa-spane").forEach((pane) => {
pane.hidden = pane.getAttribute("data-spane") !== state.settingsTab;
});
if (state.settingsTab === "craft") {
refreshMemoryList();
}
if (state.settingsTab === "user") {
refreshUserPrefs();
}
if (state.settingsTab === "personas") {
renderPersonaSettingsList();
}
if (state.settingsTab === "models") {
syncSettingsHealthLine();
const m = $2("sa_model")?.value;
if (m && $2("sa_settings_chat_model")) {
$2("sa_settings_chat_model").value = m;
}
}
if (state.settingsTab === "more") {
fillKnobsFromConfig(state.config);
}
}
function fillKnobsFromConfig(data) {
const asst = data?.assistant || state.config?.assistant || {};
const exact = data?.exact || state.config?.exact || state.exact || {};
const setNum = (id, v) => {
const el = $2(id);
if (el && v != null && Number.isFinite(Number(v))) {
el.value = String(v);
}
};
setNum("sa_num_ctx", asst.num_ctx);
setNum("sa_history_keep", asst.history_keep_turns);
setNum("sa_compress_at", asst.compress_at != null ? asst.compress_at : COMPRESS_AT);
setNum("sa_chars_per_token", asst.chars_per_token != null ? asst.chars_per_token : CHARS_PER_TOKEN);
const autoEl = $2("sa_compress_auto");
if (autoEl) {
autoEl.checked = asst.compress_auto != null ? !!asst.compress_auto : COMPRESS_AUTO;
}
setNum("sa_memory_top_k", asst.memory_top_k);
const w = asst.user_prefs_weight != null ? Number(asst.user_prefs_weight) : 1;
const weightEl = $2("sa_user_prefs_weight");
if (weightEl) {
weightEl.value = String(Math.max(0, Math.min(1.5, w)));
const lab = $2("sa_user_prefs_weight_val");
if (lab) {
lab.textContent = Number(weightEl.value).toFixed(1);
}
}
const turbo = exact.profiles?.turbo || {};
const raw = exact.profiles?.raw || {};
setNum("sa_exact_turbo_steps", turbo.steps);
setNum("sa_exact_turbo_cfg", turbo.cfg);
setNum("sa_exact_turbo_sigma", turbo.sigma_shift);
setNum("sa_exact_raw_steps", raw.steps);
setNum("sa_exact_raw_cfg", raw.cfg);
setNum("sa_exact_raw_sigma", raw.sigma_shift);
}
function saveKnobs() {
if (typeof genericRequest !== "function") {
return;
}
const num = (id) => {
const v = parseFloat($2(id)?.value);
return Number.isFinite(v) ? v : null;
};
const assistant = {
num_ctx: num("sa_num_ctx"),
history_keep_turns: num("sa_history_keep"),
compress_at: num("sa_compress_at"),
chars_per_token: num("sa_chars_per_token"),
compress_auto: $2("sa_compress_auto") ? !!$2("sa_compress_auto").checked : null,
memory_top_k: num("sa_memory_top_k"),
user_prefs_weight: num("sa_user_prefs_weight")
};
Object.keys(assistant).forEach((k) => {
if (assistant[k] == null) {
delete assistant[k];
}
});
const exact = {
profiles: {
turbo: {
steps: num("sa_exact_turbo_steps"),
cfg: num("sa_exact_turbo_cfg"),
sigma_shift: num("sa_exact_turbo_sigma")
},
raw: {
steps: num("sa_exact_raw_steps"),
cfg: num("sa_exact_raw_cfg"),
sigma_shift: num("sa_exact_raw_sigma")
}
}
};
genericRequest(
"AssistentSaveKnobs",
{ assistant, exact },
(data) => {
if (data?.assistant || data?.exact) {
applyConfigPayload({
...state.config,
assistant: data.assistant || state.config?.assistant,
exact: data.exact || state.config?.exact
});
}
if (assistant.compress_at != null) {
COMPRESS_AT = Math.min(0.95, Math.max(0.4, Number(assistant.compress_at) || 0.7));
}
if (assistant.chars_per_token != null) {
CHARS_PER_TOKEN = Math.max(1.5, Number(assistant.chars_per_token) || 3.2);
}
if (assistant.compress_auto != null) {
COMPRESS_AUTO = !!assistant.compress_auto;
}
updateCtxChip();
setStatus("Knobs \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u044B \u0432 overlay");
},
0,
(err) => setStatus(String(err || "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C knobs"))
);
}
function syncSettingsHealthLine() {
const line = $2("sa_settings_health_line");
const badge = $2("sa_ollama_health");
if (line && badge) {
line.textContent = badge.textContent || "Ollama \xB7 \u2026";
line.className = "sa-settings-health " + (badge.className || "").replace("sa-health", "").trim();
}
}
function personaSourceLabel(source) {
if (source === "overlay") {
return "\u043C\u043E\u044F";
}
if (source === "overlay+bundled") {
return "\u0432\u0441\u0442\u0440\u043E\u0435\u043D\u043E+\u043F\u0440\u0430\u0432\u043A\u0430";
}
return "\u0432\u0441\u0442\u0440\u043E\u0435\u043D\u043E";
}
function renderPersonaSettingsList() {
const root = $2("sa_persona_list");
if (!root) {
return;
}
const list = state.personas || state.config?.personas || [];
const cur = state.settingsPersonaId || $2("sa_persona")?.value || list[0]?.id;
state.settingsPersonaId = cur;
root.innerHTML = "";
for (const p of list) {
const btn = document.createElement("button");
btn.type = "button";
btn.className = "sa-persona-item" + (p.id === cur ? " sa-persona-item-active" : "");
const accent = p.accent || "currentColor";
btn.innerHTML = `<div class="sa-persona-item-title"><span class="sa-persona-dot" style="background:${escapeHtml2(accent)}"></span><span>${escapeHtml2(p.title || p.id)}</span></div><div class="sa-persona-badge">${escapeHtml2(personaSourceLabel(p.source))}</div>`;
btn.addEventListener("click", () => {
state.settingsPersonaId = p.id;
renderPersonaSettingsList();
loadPersonaPreview(p.id);
});
root.appendChild(btn);
}
syncPersonaPanelActions();
if (cur) {
loadPersonaPreview(cur);
}
}
function syncPersonaPanelActions() {
const id = state.settingsPersonaId;
const p = (state.personas || []).find((x) => x.id === id);
const canDelete = p && (p.source === "overlay" || p.source === "overlay+bundled");
const del = $2("sa_btn_persona_delete_panel");
if (del) {
del.disabled = !canDelete;
}
}
function loadPersonaPreview(id) {
const box = $2("sa_persona_preview");
if (!box || typeof genericRequest !== "function") {
return;
}
box.innerHTML = '<div class="sa-mem-empty">\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430\u2026</div>';
genericRequest(
"AssistentGetPersonaShelves",
{ persona: id },
(data) => {
const summary = data?.identity_summary || "";
const src = data?.source || "";
box.textContent = `${id} \xB7 ${personaSourceLabel(src)}
${summary || "(\u043F\u0443\u0441\u0442\u043E)"}`;
syncPersonaPanelActions();
},
0,
(err) => {
box.innerHTML = `<div class="sa-mem-empty">${escapeHtml2(String(err || "\u043E\u0448\u0438\u0431\u043A\u0430"))}</div>`;
}
);
}
function exportSelectedPersona() {
const id = state.settingsPersonaId || $2("sa_persona")?.value;
if (!id || typeof genericRequest !== "function") {
return;
}
genericRequest(
"AssistentExportPersona",
{ persona: id },
(data) => {
const pack = data?.pack;
if (!pack) {
setStatus("\u041F\u0443\u0441\u0442\u043E\u0439 \u044D\u043A\u0441\u043F\u043E\u0440\u0442");
return;
}
const blob = new Blob([JSON.stringify(pack, null, 2)], { type: "application/json" });
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = `${pack.id || id}.assistent-persona.json`;
a.click();
URL.revokeObjectURL(a.href);
setStatus(`\u042D\u043A\u0441\u043F\u043E\u0440\u0442: ${a.download}`);
},
0,
(err) => setStatus(String(err || "\u042D\u043A\u0441\u043F\u043E\u0440\u0442 \u043D\u0435 \u0443\u0434\u0430\u043B\u0441\u044F"))
);
}
function importPersonaFile(file) {
if (!file || typeof genericRequest !== "function") {
return;
}
const reader = new FileReader();
reader.onload = () => {
let pack;
try {
pack = JSON.parse(String(reader.result || ""));
} catch (e) {
setStatus("\u041D\u0435\u0432\u0430\u043B\u0438\u0434\u043D\u044B\u0439 JSON");
return;
}
let newId = pack?.id || "";
if ((state.personas || []).some((p) => p.id === newId && (p.source === "bundled" || p.source === "overlay+bundled"))) {
newId = window.prompt("Id \u0437\u0430\u043D\u044F\u0442 bundled \u2014 \u043D\u043E\u0432\u044B\u0439 id:", `${newId}_import`) || "";
}
genericRequest(
"AssistentImportPersona",
{ pack, new_id: newId || null, overwrite: false },
(data) => {
if (Array.isArray(data?.personas)) {
state.personas = data.personas;
renderPersonaOptions(data.personas, data.persona?.id);
}
state.settingsPersonaId = data?.persona?.id || newId;
renderPersonaSettingsList();
setStatus(`\u0418\u043C\u043F\u043E\u0440\u0442: ${data?.persona?.id || newId}`);
},
0,
(err) => setStatus(String(err || "\u0418\u043C\u043F\u043E\u0440\u0442 \u043D\u0435 \u0443\u0434\u0430\u043B\u0441\u044F"))
);
};
reader.readAsText(file);
}
function cloneSelectedPersona() {
const from = state.settingsPersonaId || $2("sa_persona")?.value;
if (!from) {
return;
}
const to = window.prompt("\u041D\u043E\u0432\u044B\u0439 id \u043B\u0438\u0447\u043D\u043E\u0441\u0442\u0438:", `${from}_copy`);
if (!to) {
return;
}
genericRequest(
"AssistentClonePersona",
{ from, to, title: to },
(data) => {
if (Array.isArray(data?.personas)) {
state.personas = data.personas;
renderPersonaOptions(data.personas, to);
}
state.settingsPersonaId = to;
renderPersonaSettingsList();
setStatus(`\u041A\u043B\u043E\u043D: ${to}`);
},
0,
(err) => setStatus(String(err || "\u041A\u043B\u043E\u043D \u043D\u0435 \u0443\u0434\u0430\u043B\u0441\u044F"))
);
}
function deleteSelectedOverlayPersona() {
const id = state.settingsPersonaId;
const p = (state.personas || []).find((x) => x.id === id);
if (!p || p.source !== "overlay" && p.source !== "overlay+bundled") {
setStatus("\u041C\u043E\u0436\u043D\u043E \u0443\u0434\u0430\u043B\u0438\u0442\u044C \u0442\u043E\u043B\u044C\u043A\u043E overlay");
return;
}
if (!window.confirm(`\u0423\u0434\u0430\u043B\u0438\u0442\u044C overlay-\u043B\u0438\u0447\u043D\u043E\u0441\u0442\u044C \xAB${id}\xBB?`)) {
return;
}
genericRequest(
"AssistentDeletePersona",
{ persona: id },
(data) => {
if (Array.isArray(data?.personas)) {
state.personas = data.personas;
renderPersonaOptions(data.personas, data.default_persona);
}
state.settingsPersonaId = data?.default_persona || null;
renderPersonaSettingsList();
setStatus(`\u0423\u0434\u0430\u043B\u0435\u043D\u043E: ${id}`);
},
0,
(err) => setStatus(String(err || "\u0423\u0434\u0430\u043B\u0435\u043D\u0438\u0435 \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C"))
);
}
function refreshUserPrefs() {
if (typeof genericRequest !== "function") {
return;
}
const persona = $2("sa_persona")?.value || "neutral";
genericRequest(
"AssistentListUserPrefs",
{ persona, limit: 200 },
(data) => {
state.userPrefs = Array.isArray(data?.prefs) ? data.prefs : [];
renderUserPrefsLists();
},
0,
(err) => setStatus(String(err || "User prefs \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B"))
);
}
function renderUserPrefsLists() {
const persona = $2("sa_persona")?.value || "neutral";
const global = (state.userPrefs || []).filter((p) => p.scope === "global");
const personal = (state.userPrefs || []).filter((p) => p.scope === "persona" && (p.persona_id === persona || p.persona === persona));
const fill = (rootId, rows) => {
const root = $2(rootId);
if (!root) {
return;
}
root.innerHTML = "";
if (!rows.length) {
root.innerHTML = '<div class="sa-mem-empty">\u041F\u0443\u0441\u0442\u043E</div>';
return;
}
for (const row of rows) {
const el = document.createElement("div");
el.className = "sa-mem-row";
const pin = row.pinned ? " \u2605" : "";
el.innerHTML = `<div class="sa-mem-row-body"><div class="sa-mem-row-head"><span class="sa-mem-row-key">${escapeHtml2(row.key || "")}${pin}</span></div><div class="sa-mem-row-text">${escapeHtml2(clipDebug(row.text, 200))}</div></div>`;
el.querySelector(".sa-mem-row-body")?.addEventListener("click", () => editUserPref(row));
el.querySelector(".sa-mem-row-body")?.setAttribute("title", "\u041A\u043B\u0438\u043A \u2014 \u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C");
const pinBtn = document.createElement("button");
pinBtn.type = "button";
pinBtn.className = "basic-button sa-mem-forget";
pinBtn.textContent = row.pinned ? "\u2605" : "\u2606";
pinBtn.title = row.pinned ? "Unpin" : "Pin";
pinBtn.addEventListener("click", (e) => {
e.stopPropagation();
toggleUserPrefPin(row);
});
el.appendChild(pinBtn);
const forget = document.createElement("button");
forget.type = "button";
forget.className = "basic-button sa-mem-forget";
forget.textContent = "\xD7";
forget.title = "\u0417\u0430\u0431\u044B\u0442\u044C";
forget.addEventListener("click", (e) => {
e.stopPropagation();
forgetUserPref(row);
});
el.appendChild(forget);
root.appendChild(el);
}
};
fill("sa_prefs_global", global);
fill("sa_prefs_persona", personal);
}
function editUserPref(row) {
const text = window.prompt("\u0422\u0435\u043A\u0441\u0442 \u0444\u0430\u043A\u0442\u0430:", row.text || "");
if (text == null || !String(text).trim()) {
return;
}
genericRequest(
"AssistentUpsertUserPref",
{
key: row.key,
text: String(text).trim(),
scope: row.scope || "global",
persona: row.persona_id || row.persona || $2("sa_persona")?.value || "neutral",
source: "user",
pinned: !!row.pinned
},
() => refreshUserPrefs(),
0,
(err) => setStatus(String(err || "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C"))
);
}
function toggleUserPrefPin(row) {
genericRequest(
"AssistentUpsertUserPref",
{
key: row.key,
text: row.text,
scope: row.scope || "global",
persona: row.persona_id || row.persona || $2("sa_persona")?.value || "neutral",
source: row.source || "user",
pinned: !row.pinned
},
() => refreshUserPrefs(),
0,
(err) => setStatus(String(err || "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C pin"))
);
}
function addUserPref(scope) {
const key = window.prompt("\u041A\u043B\u044E\u0447 (stable-id):", scope === "global" ? "prefer" : "tone");
if (!key) {
return;
}
const text = window.prompt("\u0422\u0435\u043A\u0441\u0442 \u0444\u0430\u043A\u0442\u0430:", "");
if (!text) {
return;
}
genericRequest(
"AssistentUpsertUserPref",
{
key: key.trim(),
text: text.trim(),
scope,
persona: $2("sa_persona")?.value || "neutral",
source: "user",
pinned: false
},
() => {
refreshUserPrefs();
setStatus("\u0421\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u043E");
},
0,
(err) => setStatus(String(err || "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C"))
);
}
function forgetUserPref(row) {
genericRequest(
"AssistentForgetUserPref",
{
key: row.key,
scope: row.scope || "global",
persona: row.persona_id || row.persona || $2("sa_persona")?.value
},
() => refreshUserPrefs(),
0,
(err) => setStatus(String(err || "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0431\u044B\u0442\u044C"))
);
}
function clearUserPrefs(scope) {
const labels = { global: "\u043E\u0431\u0449\u0438\u0435 prefs", persona: "prefs \u044D\u0442\u043E\u0439 \u043B\u0438\u0447\u043D\u043E\u0441\u0442\u0438", all: "\u0432\u0441\u0435 prefs \u043E \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u0435" };
if (!window.confirm(`\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C ${labels[scope] || scope}?`)) {
return;
}
genericRequest(
"AssistentClearUserPrefs",
{ scope, persona: $2("sa_persona")?.value || "neutral" },
(data) => {
setStatus(`\u0423\u0434\u0430\u043B\u0435\u043D\u043E: ${data?.deleted ?? 0}`);
refreshUserPrefs();
},
0,
(err) => setStatus(String(err || "\u041E\u0447\u0438\u0441\u0442\u043A\u0430 \u043D\u0435 \u0443\u0434\u0430\u043B\u0430\u0441\u044C"))
);
}
function resetUiState() {
if (!window.confirm("\u0421\u0431\u0440\u043E\u0441\u0438\u0442\u044C UI-state (local + disk)? \u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 Ollama \u0438 prefs \u043E\u0441\u0442\u0430\u043D\u0443\u0442\u0441\u044F.")) {
return;
}
const keys = Object.keys(localStorage).filter((k) => k.startsWith("swarm_assistent_"));
for (const k of keys) {
localStorage.removeItem(k);
}
diskPersist()?.saveUiState?.({});
setStatus("UI-state \u0441\u0431\u0440\u043E\u0448\u0435\u043D \u2014 \u043E\u0431\u043D\u043E\u0432\u0438 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0443");
}
function modelKeyLeaf(name) {
return String(name || "").replace(/\\/g, "/").split("/").pop().replace(/\.(safetensors|ckpt|pt|pth|gguf|bin)$/i, "").trim().toLowerCase();
}
function setOllamaHealth(level, text, title) {
state.ollamaHealth = level;
const el = $2("sa_ollama_health");
if (!el) {
return;
}
el.hidden = false;
el.textContent = text;
el.title = title || text;
el.classList.remove("sa-health-ok", "sa-health-warn", "sa-health-down");
el.classList.add(`sa-health-${level}`);
syncSettingsHealthLine();
}
function probeOllamaHealth() {
if (typeof genericRequest !== "function") {
return;
}
const baseUrl = $2("sa_base_url")?.value || "http://127.0.0.1:11434";
genericRequest(
"AssistentListModels",
{ baseUrl },
(data) => {
if (data?.error) {
setOllamaHealth("down", "Ollama \u2715", String(data.error));
return;
}
const chat = (data.models || []).length;
const mem = (data.memory_models || []).length;
if (!chat) {
setOllamaHealth("warn", "Ollama \xB7 0 \u043C\u043E\u0434\u0435\u043B\u0435\u0439", "\u041D\u0435\u0442 \u0447\u0430\u0442-\u043C\u043E\u0434\u0435\u043B\u0435\u0439 \u2014 \u0441\u0434\u0435\u043B\u0430\u0439 ollama pull");
return;
}
setOllamaHealth("ok", `Ollama \xB7 ${chat}`, `\u0427\u0430\u0442-\u043C\u043E\u0434\u0435\u043B\u0435\u0439: ${chat}, \u043F\u0430\u043C\u044F\u0442\u044C: ${mem} \xB7 ${baseUrl}`);
},
0,
(err) => setOllamaHealth("down", "Ollama \u2715", `\u041D\u0435\u0442 \u0441\u0432\u044F\u0437\u0438: ${String(err || "")} \xB7 ${baseUrl}`)
);
}
function setView(view) {
if (view === "cards") {
view = "chat";
}
if (view === "settings") {
state.view = "settings";
} else if (view === "train") {
state.view = "train";
} else {
state.view = "chat";
}
const chat = $2("sa_view_chat");
const settings = $2("sa_view_settings");
const train = $2("sa_view_train");
if (chat) {
chat.hidden = state.view !== "chat";
}
if (settings) {
settings.hidden = state.view !== "settings";
}
if (train) {
train.hidden = state.view !== "train";
}
const tabActive = (id, on) => {
$2(id)?.classList.toggle("sa-subtab-active", on);
$2(id)?.classList.toggle("sa-app-tab-active", on);
$2(id)?.setAttribute("aria-selected", on ? "true" : "false");
};
tabActive("sa_tab_chat", state.view === "chat");
tabActive("sa_tab_settings", state.view === "settings");
tabActive("sa_tab_train", state.view === "train");
saveSettings();
if (state.view === "settings") {
setSettingsTab(state.settingsTab || "behavior");
} else if (state.view === "train") {
window.SA?.training?.render?.();
} else if ((state.llmParked || state.expectColdLoad) && !state.generating) {
warmLlm({ force: true });
}
}
function openSettings(tab) {
if (tab) {
state.settingsTab = tab;
}
setView("settings");
}
function closeSettings() {
setView("chat");
}
async function addRefFromUrl(url) {
if (!url) {
return;
}
addRefSlot({ src: url, select: false });
}
function shortLoraName(name) {
const s = String(name || "");
const base = s.split(/[/\\]/).pop() || s;
return base.replace(/\.safetensors$/i, "").slice(0, 28);
}
function renderLoraChips() {
const root = $2("sa_lora_chips");
if (!root) {
return;
}
root.innerHTML = "";
let selected = [];
try {
if (typeof loraHelper !== "undefined" && Array.isArray(loraHelper?.selected)) {
selected = loraHelper.selected.map((l) => ({
name: l.name || l,
weight: loraHelper.loraWeightPref && loraHelper.loraWeightPref[l.name || l] || 1
}));
}
} catch (e) {
}
for (const l of selected) {
const btn = document.createElement("button");
btn.type = "button";
btn.className = "sa-lora-chip";
btn.title = `${l.name} \xD7${l.weight} \u2014 \u043A\u043B\u0438\u043A \u0441\u043D\u044F\u0442\u044C`;
btn.textContent = `${shortLoraName(l.name)} ${Number(l.weight).toFixed(2)}`;
btn.addEventListener("click", () => {
try {
if (typeof loraHelper !== "undefined" && typeof loraHelper.removeLora === "function") {
loraHelper.removeLora(l.name);
} else if (loraHelper?.selected) {
loraHelper.selected = loraHelper.selected.filter((x) => (x.name || x) !== l.name);
if (typeof loraHelper.rebuildUI === "function") {
loraHelper.rebuildUI();
}
}
} catch (e) {
}
renderLoraChips();
});
root.appendChild(btn);
}
const add = document.createElement("button");
add.type = "button";
add.className = "sa-lora-chip sa-lora-add";
add.textContent = "+ LoRA";
add.title = "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0438\u0437 inventory";
add.addEventListener("click", (e) => {
e.stopPropagation();
openLoraPicker(add);
});
root.appendChild(add);
}
function openLoraPicker(anchor) {
document.querySelectorAll(".sa-lora-picker").forEach((n) => n.remove());
const picker = document.createElement("div");
picker.className = "sa-lora-picker";
const inv = (state.inventory?.loras || []).slice().sort((a, b) => (b.krea_likely ? 1 : 0) - (a.krea_likely ? 1 : 0));
const filter = document.createElement("input");
filter.type = "search";
filter.placeholder = "\u0424\u0438\u043B\u044C\u0442\u0440 LoRA\u2026";
filter.style.cssText = "width:100%;box-sizing:border-box;margin-bottom:0.25rem;padding:0.3rem;";
picker.appendChild(filter);
const list = document.createElement("div");
picker.appendChild(list);
const draw = () => {
list.innerHTML = "";
const q = filter.value.trim().toLowerCase();
let n = 0;
for (const l of inv) {
const name = l.name || "";
if (q && !String(name).toLowerCase().includes(q) && !String(l.title || "").toLowerCase().includes(q)) {
continue;
}
const btn = document.createElement("button");
btn.type = "button";
btn.textContent = `${shortLoraName(name)}${l.krea_likely ? " \xB7 krea" : ""}`;
btn.title = name;
btn.addEventListener("click", async () => {
await applyPatch({
loras: [
...(() => {
try {
return (loraHelper?.selected || []).map((x) => ({
name: x.name || x,
weight: loraHelper.loraWeightPref && loraHelper.loraWeightPref[x.name || x] || 1
}));
} catch (e) {
return [];
}
})(),
{ name, weight: l.default_weight ? parseFloat(l.default_weight) : 0.8, triggers: l.triggers || (l.trigger_phrase ? [l.trigger_phrase] : []) }
]
}, "loras");
picker.remove();
renderLoraChips();
});
list.appendChild(btn);
if (++n >= 40) {
break;
}
}
if (!n) {
list.innerHTML = '<div class="sa-chat-empty-hint">\u041D\u0435\u0442 LoRA</div>';
}
};
filter.addEventListener("input", draw);
draw();
const composer = $2("sa_composer") || document.body;
composer.style.position = composer.style.position || "relative";
composer.appendChild(picker);
const onDoc = (ev) => {
if (!picker.contains(ev.target) && ev.target !== anchor) {
picker.remove();
document.removeEventListener("mousedown", onDoc);
}
};
setTimeout(() => document.addEventListener("mousedown", onDoc), 0);
filter.focus();
}
function inventoryIsStale(maxAgeMs = 2e4) {
if (!state.inventoryFetchedAt) {
return true;
}
return Date.now() - state.inventoryFetchedAt > maxAgeMs;
}
async function ensureFreshInventory({ forceRescan } = {}) {
const rescan = forceRescan || inventoryIsStale(2e4);
await refreshInventoryAsync({ rescan });
}
function triggerSwarmModelRefresh(done) {
if (typeof genericRequest !== "function") {
if (done) {
done();
}
return;
}
genericRequest(
"TriggerRefresh",
{ strong: true },
() => {
if (done) {
done();
}
},
0,
() => {
if (done) {
done();
}
}
);
}
async function handleReplySideEffects(reply, civitaiResults, opts = {}) {
const { fromAutoCritique, fromVisionHop, fromDebug } = opts;
void civitaiResults;
if (fromDebug) {
state.pendingSilentGen = false;
return;
}
const extracted = typeof extractPatch2 === "function" ? extractPatch2(reply) : { patch: null };
let effective = extracted && extracted.patch ? extracted.patch : null;
if (opts.fromPromptEnRetry && effective && typeof mergePromptEnRewrite === "function") {
effective = mergePromptEnRewrite(effective);
}
const S = window.SA && window.SA.session;
const act = getActivity();
if (effective && act && typeof act.noteModelCommands === "function") {
act.noteModelCommands(effective);
}
if (effective && S) {
state.chatSession = S.mergeDelta(state.chatSession || S.emptySession(), effective);
activityDone("delta", {
kind: "delta",
label: "\u041E\u0431\u043D\u043E\u0432\u0438\u043B \u0441\u0435\u0441\u0441\u0438\u044E",
detail: Object.keys(effective).filter((k) => effective[k] != null && !["actions", "notes"].includes(k)).slice(0, 10).join(", ")
});
try {
const chat = typeof findChat === "function" ? findChat(state.activeChatId) : null;
if (chat && typeof snapshotChatParams === "function") {
chat.params = snapshotChatParams();
if (typeof persistChatsStore === "function") persistChatsStore();
}
} catch (e) {
}
if (typeof rememberLastPatch === "function") rememberLastPatch(effective);
}
if (Array.isArray(effective?.actions) && effective.actions.map(String).includes("interrupt")) {
if (typeof doInterruptNow === "function") doInterruptNow();
}
const intent = resolveTurnIntent2(effective, opts.userText || "", opts);
if (effective) {
if (intent.generate) {
const acts = Array.isArray(effective.actions) ? effective.actions.map(String) : [];
effective = { ...effective, actions: acts.includes("generate") ? acts : acts.concat("generate"), generate: true };
} else if (typeof stripGenerateAction === "function") {
effective = stripGenerateAction(effective);
if (effective && effective.generate) {
effective = { ...effective };
delete effective.generate;
}
}
if (!intent.look && typeof stripLookAt === "function") effective = stripLookAt(effective);
if (typeof rememberLastPatch === "function") rememberLastPatch(effective);
}
if (intent.vetoed) {
state.pendingSilentGen = false;
const a = getActivity();
if (a) {
a.skip("generate", {
kind: "generate",
label: "Generate \u043E\u0442\u043C\u0435\u043D\u0451\u043D",
detail: "\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C \u043F\u043E\u043F\u0440\u043E\u0441\u0438\u043B \u043D\u0435 \u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C"
});
}
}
const askList = Array.isArray(intent.ask) ? intent.ask : [];
if (askList.length && typeof claimTurnHop === "function" && claimTurnHop("ask")) {
activityStep("ask", {
kind: "ask",
label: `\u0417\u0430\u043F\u0440\u043E\u0441\u0438\u043B ${askList.join(", ")}`,
detail: "\u043F\u043E\u0434\u0433\u0440\u0443\u0436\u0430\u044E \u0434\u0435\u0442\u0430\u043B\u0438\u2026",
status: "running"
});
pullLiveIntoSession();
const bits = [];
if (askList.some((a) => /settings/i.test(String(a)))) {
const dump = S && typeof S.fullSettingsDump === "function" ? S.fullSettingsDump(state.chatSession, {
exact: state.exact,
kreaProfiles: state.kreaProfiles,
sessionExact: state.sessionExact
}) : collectLiveContext();
bits.push("SETTINGS_JSON:\n" + JSON.stringify(dump));
}
if (askList.some((a) => /inventory/i.test(String(a)))) {
const inv = state.inventory || {};
bits.push("INVENTORY_JSON:\n" + JSON.stringify({
loras: (inv.loras || []).slice(0, 40).map((l) => ({ name: l.name || l, trigger_phrase: l.trigger_phrase || null })),
checkpoints: (inv.checkpoints || []).slice(0, 16).map((c) => c.name || c),
wildcards: (inv.wildcards || []).slice(0, 20).map((w) => w.name || w)
}));
}
if (bits.length) {
activityDone("ask", { detail: "\u0434\u0435\u0442\u0430\u043B\u0438 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u044B \u043C\u043E\u0434\u0435\u043B\u0438" });
await sendChat({ skipSlash: true, skipAutoPack: true, fromAskHop: true, forcedUserText: bits.join("\n\n") });
return;
}
}
if (effective && intent.look && !fromVisionHop && !fromAutoCritique) {
activityStep("look", {
kind: "look",
label: "\u0421\u043C\u043E\u0442\u0440\u0438\u0442 \u043D\u0430 \u043A\u0430\u0434\u0440",
status: "running"
});
if (typeof maybeVisionHop === "function") {
const hopped = await maybeVisionHop(effective, opts.attachedSlotIds || []);
if (hopped) {
activityDone("look", { detail: "vision hop" });
return;
}
activityDone("look", { detail: "\u043A\u0430\u0434\u0440 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D" });
}
}
if (intent.generate && effective?.prompt && typeof promptNeedsKreaPrep === "function" && promptNeedsKreaPrep(effective.prompt) && !fromVisionHop && typeof claimTurnHop === "function" && claimTurnHop("krea_prep")) {
state.pendingPromptEnMerge = { ...effective };
activityStep("prep", {
kind: "prep",
label: "\u0413\u043E\u0442\u043E\u0432\u043B\u044E \u043F\u0440\u043E\u043C\u043F\u0442 \u0434\u043B\u044F Krea",
detail: "EN + \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430",
status: "running"
});
if (typeof appendSystemNote === "function") appendSystemNote("\u0413\u043E\u0442\u043E\u0432\u043B\u044E \u043F\u0440\u043E\u043C\u043F\u0442 \u0434\u043B\u044F Krea\u2026");
await sendChat({
skipSlash: true,
skipAutoPack: true,
fromPromptEnRetry: true,
userWantsGenerate: true,
forcedUserText: typeof buildKreaPromptPrepRequest === "function" ? buildKreaPromptPrepRequest(effective) : String(effective.prompt || "")
});
return;
}
if (intent.generate) {
activityStep("generate", {
kind: "generate",
label: intent.vetoed ? "Generate \u043E\u0442\u043C\u0435\u043D\u0451\u043D (\u0432\u0435\u0442\u043E)" : "Generate",
status: intent.vetoed ? "skip" : "running"
});
if (typeof startBusyUi === "function") startBusyUi("silent_gen");
if (S && effective) state.chatSession = S.mergeDelta(state.chatSession || S.emptySession(), effective);
await pushSessionToSwarm(state.chatSession);
if (typeof syncLiveParamsBar === "function") syncLiveParamsBar();
const srcOut = await runGenerateFromPatch(
{ ...effective || {}, actions: ["generate"], generate: true },
{ force: true, fromSession: true }
);
activityDone("generate", { detail: srcOut ? "\u043A\u0430\u0434\u0440 \u0433\u043E\u0442\u043E\u0432" : "\u0431\u0435\u0437 \u043A\u0430\u0434\u0440\u0430" });
if (srcOut) {
if (typeof maybeAutoCritique === "function") await maybeAutoCritique(srcOut);
if (typeof maybeAutoVisionLook === "function") await maybeAutoVisionLook(srcOut);
}
} else if (effective && $2("sa_auto_apply")?.checked) {
if (S) state.chatSession = S.mergeDelta(state.chatSession || S.emptySession(), effective);
await pushSessionToSwarm(state.chatSession);
if (typeof syncLiveParamsBar === "function") syncLiveParamsBar();
if (!state.generating && typeof stopBusyUi === "function") {
stopBusyUi(intent.vetoed ? "\u0417\u0430\u043F\u043E\u043C\u043D\u0438\u043B \xB7 \u0431\u0435\u0437 Generate" : "");
}
}
state.pendingSilentGen = false;
}
async function applyQuickPatch(patch, note) {
const withActions = { ...patch };
if (!Array.isArray(withActions.actions) && patchHasGenTrigger(withActions)) {
withActions.actions = ["generate"];
}
const prevIntent = state.lastUserParamIntent;
state.lastUserParamIntent = true;
const S = window.SA && window.SA.session;
if (S) {
state.chatSession = S.mergeDelta(state.chatSession || S.emptySession(), withActions);
}
await pushSessionToSwarm(state.chatSession);
state.lastUserParamIntent = prevIntent;
setStatus(note || "Applied");
if (patchHasGenTrigger(withActions)) {
await runGenerateFromPatch(withActions, { force: true, fromSession: true });
}
syncChipHighlight();
}
function syncChipHighlight() {
const bar = $2("sa_chips");
if (!bar) {
return;
}
const cur = guessAspectFromSize(val("input_width"), val("input_height"));
const seed = val("input_seed");
bar.querySelectorAll("[data-aspect]").forEach((btn) => {
btn.classList.toggle("sa-chip-active", btn.getAttribute("data-aspect") === cur);
});
bar.querySelectorAll("[data-seed]").forEach((btn) => {
const mode = btn.getAttribute("data-seed");
const active = mode === "lock" && seed && seed !== "-1" || mode === "random" && (!seed || seed === "-1");
btn.classList.toggle("sa-chip-active", active);
});
}
function appendSystemNote(text) {
const box = $2("sa_messages");
if (!box) {
return;
}
hideChatEmpty();
const div = document.createElement("div");
div.className = "sa-msg assistant sa-system-note";
div.textContent = text;
box.appendChild(div);
scrollMessagesToBottom();
}
function clipDebug(s, max) {
const t = String(s || "").replace(/\s+/g, " ").trim();
if (!t) {
return "\u2014";
}
return t.length > max ? `${t.slice(0, max)}\u2026` : t;
}
function formatDebugLoras(list) {
if (!Array.isArray(list) || !list.length) {
return "\u043D\u0435\u0442";
}
return list.slice(0, 8).map((l) => {
const name = l?.name || l;
const w = l?.weight != null ? `@${l.weight}` : "";
return `${name}${w}`;
}).join(", ");
}
function buildDebugSummary() {
const persona = $2("sa_persona")?.value || "neutral";
const pack = $2("sa_pack")?.value || defaultPackId();
const chatModel = $2("sa_model")?.value || "\u2014";
const embed = $2("sa_embed_model")?.value || state.preferredEmbed || "\u2014";
const profile = detectKreaProfileName();
const defaults = mergedGenerationDefaults(profile);
const session = state.sessionExact || {};
const exactGen = state.exact?.generation || state.config?.exact?.generation || {};
const ctx = (() => {
try {
return collectLiveContext();
} catch (e) {
return {};
}
})();
const aspect = guessAspectFromSize(ctx.width, ctx.height) || defaults.aspect || "\u2014";
const why = [];
if (Object.keys(session).length) {
why.push(`session_exact \u043F\u0435\u0440\u0435\u043A\u0440\u044B\u0432\u0430\u0435\u0442 Exact: ${Object.keys(session).join(", ")}`);
} else {
why.push("session_exact \u043F\u0443\u0441\u0442 \u2014 params \u0438\u0437 Exact + \u043F\u0440\u043E\u0444\u0438\u043B\u044C \u0447\u0435\u043A\u043F\u043E\u0438\u043D\u0442\u0430");
}
why.push(`\u043F\u0440\u043E\u0444\u0438\u043B\u044C \u0447\u0435\u043A\u043F\u043E\u0438\u043D\u0442\u0430: ${profile} (\u0438\u043C\u044F/title \u2192 turbo|raw)`);
if (persona === "cinema" || state.exact?.generation?.aspect) {
why.push(`persona/exact aspect: ${state.exact?.generation?.aspect || exactGen.aspect || "\u2014"}`);
}
if (state.lastPatch) {
const keys = Object.keys(state.lastPatch).filter((k) => state.lastPatch[k] != null && k !== "notes");
why.push(`\u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0439 \u043F\u0430\u0442\u0447 \u0437\u0430\u0434\u0430\u043B: ${keys.slice(0, 12).join(", ")}`);
} else {
why.push("\u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0433\u043E \u043F\u0430\u0442\u0447\u0430 Assistent \u0435\u0449\u0451 \u043D\u0435\u0442");
}
why.push("\u043F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442: user \u2192 About the user \u2192 session_exact \u2192 exact(+persona) \u2192 live UI \u2192 craft memory_hits");
const lines = [
"### Debug Assistent",
`persona=${persona} \xB7 pack=${pack}`,
`chat=${chatModel} \xB7 embed=${embed}`,
`skills=${(state.enabledSkills || []).join(",") || "\u2014"}`,
`auto: apply=${!!$2("sa_auto_apply")?.checked} gen=${true} vision=${!!$2("sa_auto_vision")?.checked} critique=${!!$2("sa_auto_critique")?.checked}`,
"",
"Live SwarmUI:",
` ckpt=${ctx.checkpoint?.name || "\u2014"} \xB7 krea_profile=${ctx.krea_profile || profile}`,
` ${ctx.width || "?"}\xD7${ctx.height || "?"} (${aspect}) \xB7 steps=${ctx.steps ?? "\u2014"} \xB7 cfg=${ctx.cfg ?? "\u2014"} \xB7 sigma=${ctx.sigma_shift ?? "\u2014"} \xB7 seed=${ctx.seed ?? "\u2014"} \xB7 batch=${ctx.batch ?? "\u2014"}`,
` loras: ${formatDebugLoras(ctx.selected_loras || ctx.enabled_loras)}`,
` available_loras=${(ctx.available_loras || []).length}${ctx.available_loras_truncated ? ` truncated/${ctx.available_loras_total || "?"}` : ""}`,
` prompt: ${clipDebug(ctx.prompt, 220)}`,
` negative: ${clipDebug(ctx.negative, 120)}`,
` init=${!!ctx.has_init_image} mask=${!!ctx.has_mask_image} prompt_images=${ctx.prompt_image_count || 0}`,
` has_vision_image=${!!ctx.has_vision_image} \xB7 images_in_request=${!!ctx.images_in_request} \xB7 vision_ready=${visionReadySlots().length}`,
` context_json_chars\u2248${JSON.stringify(ctx).length} \xB7 last_system_chars=${state.lastSystemChars || "\u2014"} \xB7 last_context_chars=${state.lastContextChars || "\u2014"}`,
state.lastSystemLayers ? ` system_layers: ${Object.entries(state.lastSystemLayers).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : " system_layers: \u2014 (\u043E\u0442\u043F\u0440\u0430\u0432\u044C \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435, \u0447\u0442\u043E\u0431\u044B \u0437\u0430\u043F\u043E\u043B\u043D\u0438\u0442\u044C)",
(() => {
const b = currentBudgetEstimate();
const mem = getContextMemory();
return ` budget\u2248${b.used}/${b.numCtx} (${b.fromEval ? "eval" : "est"}) \xB7 level=${b.level} \xB7 memory_until=${mem.untilCount || 0} \xB7 auto_compress=${COMPRESS_AUTO}`;
})(),
"",
"Exact defaults (merged):",
` generation=${JSON.stringify(exactGen)}`,
` effective=${JSON.stringify({
steps: defaults.steps,
cfg: defaults.cfg,
sigma_shift: defaults.sigma_shift,
aspect: defaults.aspect,
images: defaults.images,
profile: defaults.profile
})}`,
` session_exact=${Object.keys(session).length ? JSON.stringify(session) : "{}"}`,
"",
"\u041F\u043E\u0447\u0435\u043C\u0443 \u0442\u0430\u043A:",
...why.map((w) => ` \xB7 ${w}`)
];
if (state.lastPatch) {
lines.push("", `last_patch: ${clipDebug(JSON.stringify(state.lastPatch), 360)}`);
}
return lines.join("\n");
}
async function handleSlashCommand(raw) {
const text = String(raw || "").trim();
if (!text.startsWith("/")) {
return false;
}
const parts = text.slice(1).split(/\s+/);
const cmd = (parts[0] || "").toLowerCase();
const arg = parts.slice(1).join(" ").trim();
if (cmd === "help" || cmd === "?") {
appendSystemNote(HELP_TEXT);
setStatus("/help");
return true;
}
if (cmd === "new" || cmd === "newchat") {
await startNewChat({ saveCurrent: true });
return true;
}
if (cmd === "history" || cmd === "chats" || cmd === "sessions") {
setChatsPanelOpen(true);
setStatus("/history");
return true;
}
if (cmd === "compress" || cmd === "compact" || cmd === "\u0441\u0436\u0430\u0442\u044C") {
await compressNowFromUi();
return true;
}
if (cmd === "debug" || cmd === "dbg" || cmd === "why") {
const dump = buildDebugSummary();
appendSystemNote(dump);
const argL = String(arg || "").toLowerCase().trim();
const wantLlm = cmd === "why" || /^(ask|llm|explain|поясни|почему|модель)(\s|$)/i.test(argL);
if (wantLlm) {
setStatus("/debug ask\u2026");
await sendChat({
forcedUserText: "\u041E\u0442\u043B\u0430\u0434\u043A\u0430 Assistent. \u041D\u0438\u0436\u0435 \u0444\u0430\u043A\u0442\u044B UI (\u0443\u0436\u0435 \u0441\u043E\u0431\u0440\u0430\u043D\u044B \u043A\u043B\u0438\u0435\u043D\u0442\u043E\u043C). \u041A\u0440\u0430\u0442\u043A\u043E \u0441\u0432\u043E\u0438\u043C\u0438 \u0441\u043B\u043E\u0432\u0430\u043C\u0438 (5\u201310 \u0441\u0442\u0440\u043E\u043A, \u044F\u0437\u044B\u043A \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F): \u043A\u0430\u043A\u0438\u0435 \u043F\u0440\u043E\u043C\u043F\u0442/params \u0441\u0435\u0439\u0447\u0430\u0441, \u0447\u0442\u043E \u0438\u0437 Exact vs session_exact vs live, \u0447\u0442\u043E \u0441\u0434\u0435\u043B\u0430\u043B \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0439 \u043F\u0430\u0442\u0447 \u0438 \u043F\u043E\u0447\u0435\u043C\u0443 \u0442\u0430\u043A \u043B\u043E\u0433\u0438\u0447\u043D\u043E. \u0411\u0435\u0437 JSON patch, \u0431\u0435\u0437 generate, \u0431\u0435\u0437 look_at.\n\n" + dump,
skipSlash: true,
skipAutoPack: true,
fromDebug: true,
skipAppendUser: true
});
} else {
setStatus("/debug");
}
return true;
}
if (cmd === "gen" || cmd === "generate") {
const prev = findCurrentGenerateSrc();
startBusyUi("generating");
state.generating = true;
setInterruptVisible(true);
if (!triggerGenerate()) {
state.generating = false;
stopBusyUi("Could not start Generate");
return true;
}
const src = await waitForNewImage(prev);
state.generating = false;
setInterruptVisible(state.busy);
if (src) {
const gen = generateSlot();
if (gen) {
gen.src = src;
renderBoard();
}
stopBusyUi("Generate done");
} else {
stopBusyUi("Generate finished");
}
return true;
}
if (cmd === "look") {
const id = normalizeSlotId(arg || GEN_ID) || GEN_ID;
const slot = slotById(id);
if (!slot) {
setStatus(`\u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u044B\u0439 \u0441\u043B\u043E\u0442: ${arg || GEN_ID}`);
return true;
}
if (slot.type !== "generate") {
setBoardTab("refs");
} else {
setBoardTab("generate");
}
if (!slot.src && id === GEN_ID) {
const src = findCurrentGenerateSrc();
if (src) {
slot.src = src;
}
}
if (!slot.src) {
setStatus(`Slot ${id} is empty`);
return true;
}
slot.attach = true;
renderBoard();
if ($2("sa_input")) {
$2("sa_input").value = `Look at ${id} and describe what you see.`;
}
setPackValue("critique_image", { flash: true });
await sendChat({ forceSlotIds: [id], skipAutoPack: true });
return true;
}
if (cmd === "init") {
const src = selectedSrc() || findCurrentGenerateSrc();
if (!src) {
setStatus("No image for Init");
return true;
}
await setInitFromSrc(src);
setPackValue("inpaint_edit", { flash: true });
return true;
}
if (cmd === "mask") {
const src = selectedSrc();
if (!src) {
setStatus("Select a window with a mask image");
return true;
}
await setMaskFromSrc(src);
setPackValue("inpaint_edit", { flash: true });
return true;
}
if (cmd === "clear") {
clearInitAndMask();
return true;
}
if (cmd === "interrupt" || cmd === "stop") {
doInterruptNow();
clearInFlightUi({ status: "\u041F\u0440\u0435\u0440\u0432\u0430\u043D\u043E" });
return true;
}
if (cmd === "aspect") {
const key = normalizeAspect(arg);
if (!key) {
setStatus(`Unknown aspect. Try: ${Object.keys(ASPECT_TABLE).join(", ")}`);
return true;
}
await applyQuickPatch({ aspect: key, actions: ["generate"] }, `Aspect ${key}`);
return true;
}
if (cmd === "seed") {
const mode = (arg || "random").toLowerCase();
if (mode === "lock" || mode === "keep") {
await applyQuickPatch({ lock_seed: true }, "Seed locked");
} else {
await applyQuickPatch({ seed: -1, vary: true, actions: ["generate"] }, "Seed random");
}
return true;
}
if (cmd === "vary") {
await applyQuickPatch({ vary: true, seed: -1, actions: ["generate"] }, "Vary (new seed)");
return true;
}
if (cmd === "inventory" || cmd === "inv") {
setStatus("Rescanning models\u2026");
triggerSwarmModelRefresh(async () => {
await refreshInventoryAsync({ rescan: true });
const n = state.inventory?.loras?.length || 0;
const ck = state.inventory?.checkpoints?.length || 0;
appendSystemNote(`Inventory refreshed: ${n} LoRAs, ${ck} checkpoints.`);
setStatus(`Inventory: ${n} LoRAs, ${ck} ckpts (rescanned)`);
});
return true;
}
if (cmd === "pack") {
if (!setPackValue(arg, { flash: true, user: true })) {
setStatus("Pack: write|ordinary|critique|compose|params|inpaint|describe|persona");
} else {
setStatus(`Pack \u2192 ${$2("sa_pack")?.value}`);
}
return true;
}
if (cmd === "\u043E\u0441\u0442\u044B\u043D\u044C" || cmd === "ostyn" || cmd === "cool" || cmd === "cooldown") {
coolDownHorny();
return true;
}
if (cmd === "horny-game" || cmd === "hornygame" || cmd === "horny_game") {
await startHornyGame();
return true;
}
if (cmd === "persona") {
const sub = (parts[1] || "new").toLowerCase();
const rest = parts.slice(2).join(" ").trim();
setPackValue("author_persona", { flash: true, user: true });
if (sub === "save") {
await sendChat({
skipAutoPack: true,
forcedUserText: "\u0421\u043E\u0445\u0440\u0430\u043D\u0438 \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u043B\u0438\u0447\u043D\u043E\u0441\u0442\u0438 \u0441\u0435\u0439\u0447\u0430\u0441 (persona_clone / persona_write). \u041D\u0435 \u0443\u0434\u0430\u043B\u044F\u0439 \u043B\u0438\u0447\u043D\u043E\u0441\u0442\u0438."
});
return true;
}
const fromId = sub === "clone" && rest ? rest.split(/\s+/)[0] : $2("sa_persona")?.value || "neutral";
await sendChat({
skipAutoPack: true,
forcedUserText: `\u041D\u0430\u0447\u043D\u0438 \u0438\u043D\u0442\u0435\u0440\u0432\u044C\u044E author_persona: \u043A\u043B\u043E\u043D \u0441 \u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A\u0430 \xAB${fromId}\xBB. \u0421\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0439 \u043F\u043E \u043F\u043E\u043B\u043A\u0430\u043C \u0433\u0440\u0443\u043F\u043F\u0430\u043C\u0438. \u041D\u0435 \u043F\u0438\u0448\u0438 \u043D\u0430 \u0434\u0438\u0441\u043A, \u043F\u043E\u043A\u0430 \u043C\u0430\u043B\u043E \u043E\u0442\u0432\u0435\u0442\u043E\u0432. \u041D\u0435 \u0443\u0434\u0430\u043B\u044F\u0439 \u043B\u0438\u0447\u043D\u043E\u0441\u0442\u0438.`
});
return true;
}
appendSystemNote(`Unknown command /${cmd}.
${HELP_TEXT}`);
setStatus(`Unknown /${cmd}`);
return true;
}
async function maybeVisionHop(patch, attachedSlotIds) {
const ids = lookAtIdsFromPatch(patch);
if (!ids.length || turnHopUsed("vision")) {
return false;
}
scrubPreviewFromGenerateSlot();
const have = ids.map((id) => slotById(id)).filter((s) => s && s.src && !looksLikeModelPreview(s.src));
if (!have.length) {
const genSrc = findCurrentGenerateSrc();
if (ids.includes(GEN_ID) && genSrc) {
const gen = generateSlot();
if (gen) {
gen.src = genSrc;
have.push(gen);
}
}
}
if (!have.length) {
setStatus("look_at: \u043D\u0435\u0442 \u0440\u0435\u0430\u043B\u044C\u043D\u043E\u0433\u043E \u043A\u0430\u0434\u0440\u0430 (\u043F\u0440\u0435\u0432\u044C\u044E \u043C\u043E\u0434\u0435\u043B\u0438 \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D\u043E)");
return false;
}
const already = new Set(attachedSlotIds || []);
const need = have.filter((s) => !already.has(s.id));
if (!need.length) {
return false;
}
if (!claimTurnHop("vision")) {
return false;
}
for (const s of need) {
s.attach = true;
}
renderBoard();
if ($2("sa_input")) {
$2("sa_input").value = `Look at board slots: ${need.map((s) => s.id).join(", ")}. Continue using these images.`;
}
setStatus(`Vision hop \u2190 ${need.map((s) => s.label).join(", ")}`);
await sendChat({ fromVisionHop: true, forceSlotIds: need.map((s) => s.id) });
return true;
}
async function sendChat(opts = {}) {
if ((state.busy || state.generating) && !isContinuationTurn(opts)) {
return;
}
if (isTrainingLocked() && !isContinuationTurn(opts)) {
setStatus("\u0418\u0434\u0451\u0442 \u0442\u0440\u0435\u043D\u0438\u0440\u043E\u0432\u043A\u0430 \u2014 \u0447\u0430\u0442 \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D");
return;
}
const rawInput = ($2("sa_input")?.value || "").trim();
const text = (opts.forcedUserText || rawInput).trim();
if (!text) {
return;
}
if (!isMachineTurn(opts)) {
state.lastUserParamIntent = userTextMentionsParams(text);
state.lastUserControlIntent = userTextMentionsControls(text);
state.pendingSilentGen = false;
}
if (!isMachineTurn(opts) && !opts.skipSlash) {
if (rawInput.startsWith("/")) {
if ($2("sa_input")) {
$2("sa_input").value = "";
}
const handled = await handleSlashCommand(rawInput);
if (handled) {
return;
}
}
}
if (!isMachineTurn(opts) && isSameButAspectRequest(text)) {
const aspect = parseAspectFromUserText(text);
if (aspect) {
if ($2("sa_input")) {
$2("sa_input").value = "";
}
appendMessage("user", text);
state.history.push({ role: "user", content: text });
persistHistory();
restoreDefaultPackAfterHop();
const patch = { aspect, actions: ["generate"] };
if (state.lastPatch?.prompt) {
patch.prompt = state.lastPatch.prompt;
}
if (Array.isArray(state.lastPatch?.loras) && state.lastPatch.loras.length) {
patch.loras = state.lastPatch.loras;
}
appendSystemNote(`\u0421\u0442\u0430\u0432\u043B\u044E ${aspect} \u0438 Generate (\u0442\u043E\u0442 \u0436\u0435 \u043F\u0440\u043E\u043C\u043F\u0442) \u2014 \u0431\u0435\u0437 \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u043E\u0439 \u043A\u0440\u0438\u0442\u0438\u043A\u0438.`);
await applyQuickPatch(patch, `Aspect ${aspect}`);
return;
}
}
if (!updateGate()) {
setStatus("\u0412\u044B\u0431\u0435\u0440\u0438 \u043C\u043E\u0434\u0435\u043B\u044C Krea 2");
return;
}
if (!opts.skipAutoPack && !isMachineTurn(opts)) {
const guessed = autoSelectPack(text);
if (guessed) {
setPackValue(guessed, { flash: true });
}
}
if (!opts.fromDebug && false) {
setPackValue("ordinary", { flash: false });
}
const pack = opts.fromDebug ? "debug_explain" : $2("sa_pack")?.value || defaultPackId();
const persona = $2("sa_persona")?.value || localStorage.getItem(LS_PERSONA) || "neutral";
const model = $2("sa_model")?.value;
if (!model) {
setStatus("\u0412\u044B\u0431\u0435\u0440\u0438 \u043C\u043E\u0434\u0435\u043B\u044C Ollama \u0432 \u2699");
refreshModels();
return;
}
const chatEpoch = bumpChatEpoch();
state.busy = true;
state.llmParked = false;
setInterruptVisible(true);
if (!isContinuationTurn(opts) && !opts.fromAskHop) {
activityBegin(opts.fromDebug ? "Debug" : "\u0425\u043E\u0434 Assistent");
activityStep("think", { kind: "think", label: "\u0414\u0443\u043C\u0430\u044E\u2026", status: "running" });
} else if (opts.fromAskHop) {
activityStep("think", { kind: "think", label: "\u041E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0441 \u0434\u0435\u0442\u0430\u043B\u044F\u043C\u0438\u2026", status: "running" });
} else if (opts.fromVisionHop) {
activityStep("look", { kind: "look", label: "\u0421\u043C\u043E\u0442\u0440\u0438\u0442 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435\u2026", status: "running" });
} else if (opts.fromPromptEnRetry) {
activityStep("prep", { kind: "prep", label: "\u0414\u043E\u043F\u0438\u0441\u044B\u0432\u0430\u044E EN-\u043F\u0440\u043E\u043C\u043F\u0442\u2026", status: "running" });
}
if (state.expectColdLoad && !isContinuationTurn(opts)) {
startBusyUi("warming");
setStatus("\u0412\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u044E LLM \u0432 GPU\u2026");
try {
await warmLlm({ force: true });
} catch (e) {
console.warn("Assistent warm before send", e);
}
if (chatEpoch !== state.chatEpoch) {
return;
}
}
startBusyUi(state.expectColdLoad ? "loading" : "thinking");
saveSettings();
setStatus("\u041E\u0431\u043D\u043E\u0432\u043B\u044F\u044E inventory\u2026");
try {
await ensureFreshInventory({ forceRescan: !!opts.fromDownload });
} catch (e) {
console.warn("Assistent inventory refresh", e);
}
if (chatEpoch !== state.chatEpoch) {
return;
}
if (!isContinuationTurn(opts) && !opts.fromDownload) {
resetTurnHops();
}
let wantedIds = (opts.forceSlotIds || []).map(normalizeSlotId).filter(Boolean);
const sendVision = !!(opts.fromVisionHop || wantedIds.length && opts.forceSlotIds);
if (!sendVision) {
wantedIds = [];
}
const visionSlots = wantedIds.map((id) => slotById(id)).filter((s) => s && s.src && !looksLikeModelPreview(s.src));
let images = null;
if (visionSlots.length) {
startBusyUi("encoding");
setStatus("\u041A\u043E\u0434\u0438\u0440\u0443\u044E \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435\u2026");
images = [];
for (const slot of visionSlots) {
if (chatEpoch !== state.chatEpoch) {
return;
}
const b64 = await imageToBase64ForOllama(slot.src);
if (b64) {
images.push(b64);
}
}
if (!images.length) {
images = null;
}
}
if (chatEpoch !== state.chatEpoch) {
return;
}
const msgMeta = {
persona: currentPersonaInfo(),
pack,
silentPatch: !!state.pendingSilentGen
};
if (!opts.skipAppendUser) {
state.history.push({ role: "user", content: opts.historyUserText || text });
if (state.pendingPersonaNote) {
state.history.push({ role: "user", content: state.pendingPersonaNote, systemish: true });
state.pendingPersonaNote = null;
}
appendMessage("user", opts.historyUserText || text);
if ($2("sa_input")) {
$2("sa_input").value = "";
}
persistHistory();
} else {
const marker = opts.historyUserText || (opts.fromDebug ? "/debug ask" : "");
if (marker) {
state.history.push({ role: "user", content: marker });
persistHistory();
}
}
if (!opts.fromDebug && !opts.fromCompress && !opts.fromAskHop && !opts.fromVisionHop && !opts.fromAutoCritique && !isContinuationTurn(opts)) {
try {
await maybeAutoCompressBeforeSend(chatEpoch);
} catch (e) {
console.warn("Assistent auto-compress", e);
}
if (chatEpoch !== state.chatEpoch) {
return;
}
startBusyUi(state.expectColdLoad ? "loading" : "thinking");
}
const context = collectLiveContext();
context.has_vision_image = visionReadySlots().length > 0;
context.images_in_request = !!(images && images.length);
context.attached_slot_ids = attachableSlots().map((s) => s.id);
context.vision_slot_ids = visionSlots.map((s) => s.id);
context.persona = persona;
if (chatEpoch !== state.chatEpoch) {
return;
}
let messages = assembleOutgoingMessages(
opts.skipAppendUser ? { includePendingUser: text } : void 0
);
if (!messages.length && text) {
messages = [{ role: "user", content: text }];
}
if (images && messages.length) {
messages[messages.length - 1].images = images;
}
startBusyUi("thinking");
const baseUrl = $2("sa_base_url")?.value || "http://127.0.0.1:11434";
const payload = {
baseUrl,
model,
pack,
persona,
includeBase: !opts.fromDebug,
messages,
context_json: JSON.stringify(context),
skills: opts.fromDebug ? [] : state.enabledSkills || [],
embed_model: $2("sa_embed_model")?.value || state.preferredEmbed || ""
};
const finishOk = async (reply, civitaiResults, meta = {}) => {
if (chatEpoch !== state.chatEpoch) {
return;
}
if (meta.system_chars != null) {
state.lastSystemChars = Number(meta.system_chars) || 0;
}
if (meta.system_layers && typeof meta.system_layers === "object") {
state.lastSystemLayers = meta.system_layers;
}
if (meta.prompt_eval_count != null) {
state.lastPromptEvalCount = Number(meta.prompt_eval_count) || null;
const mem = getContextMemory();
if (mem.summary) {
setContextMemory({ ...mem, promptEvalCount: state.lastPromptEvalCount }, { persist: true });
}
}
try {
state.lastContextChars = context && JSON.stringify(context).length || 0;
} catch (e) {
state.lastContextChars = 0;
}
updateCtxChip();
const prose = extractPatch2(reply).prose || reply;
state.history.push({ role: "assistant", content: prose, persona, pack });
persistHistory();
setBusyPhase(state.pendingSilentGen ? "silent_gen" : "thinking");
try {
await handleReplySideEffects(reply, civitaiResults, {
...opts,
userText: text,
userWantsGenerate: !!opts.userWantsGenerate || !isMachineTurn(opts) && state.pendingSilentGen,
attachedSlotIds: visionSlots.map((s) => s.id)
});
} finally {
if (chatEpoch !== state.chatEpoch) {
return;
}
if (!state.generating) {
state.busy = false;
setInterruptVisible(false);
stopBusyUi("\u0413\u043E\u0442\u043E\u0432\u043E");
} else {
state.busy = false;
setInterruptVisible(true);
}
updateCtxChip();
}
};
const finishErr = (msg) => {
if (chatEpoch !== state.chatEpoch) {
return;
}
state.busy = false;
setInterruptVisible(state.generating);
stopBusyUi(msg);
if (state.streamEl) {
state.streamEl.classList.remove("sa-streaming", "sa-typing");
state.streamEl.classList.add("error");
setAssistantBody(state.streamEl, msg);
state.streamEl = null;
state.streamMeta = null;
} else {
appendMessage("error", msg);
}
};
if (typeof makeWSRequest === "function") {
beginStreamMessage(msgMeta);
makeWSRequest(
"AssistentChatWS",
payload,
(data) => {
if (chatEpoch !== state.chatEpoch) {
return;
}
if (data.phase === "waiting_ollama") {
setBusyPhase(state.expectColdLoad ? "loading" : "waiting");
const label = state.streamEl?.querySelector(".sa-typing-label");
if (label) {
label.textContent = state.expectColdLoad ? `\u0417\u0430\u0433\u0440\u0443\u0436\u0430\u044E ${modelShort(model)} \u0432 GPU\u2026` : "\u0414\u0443\u043C\u0430\u044E\u2026";
}
return;
}
if (data.error) {
finishErr(String(data.error));
return;
}
if (data.clear_stream) {
if (state.streamEl) {
state.streamEl.classList.add("sa-typing");
const body = state.streamEl.querySelector(".sa-msg-body") || state.streamEl;
body.innerHTML = '<span class="sa-dots" aria-hidden="true"><i></i><i></i><i></i></span><span class="sa-typing-label">\u0423\u0442\u043E\u0447\u043D\u044F\u044E\u2026</span>';
}
setBusyPhase("refining");
return;
}
if (data.delta) {
appendStreamDelta(data.delta);
return;
}
if (data.done || data.reply != null) {
const reply = data.reply || state.streamEl?.querySelector(".sa-msg-body")?.textContent || "";
const civitai = data.civitai_results || [];
finalizeStreamMessage(reply, civitai);
finishOk(reply, civitai, {
system_chars: data.system_chars,
system_layers: data.system_layers,
prompt_eval_count: data.prompt_eval_count ?? data.raw?.prompt_eval_count
});
}
},
0,
(err) => {
if (chatEpoch !== state.chatEpoch) {
return;
}
console.warn("AssistentChatWS failed, falling back", err);
if (state.streamEl) {
state.streamEl.remove();
state.streamEl = null;
state.streamMeta = null;
}
genericRequest(
"AssistentChat",
payload,
(data) => {
if (chatEpoch !== state.chatEpoch) {
return;
}
if (data.error) {
finishErr(String(data.error));
return;
}
const reply = data.reply || "";
appendMessage("assistant", reply, null, data.civitai_results || [], msgMeta);
finishOk(reply, data.civitai_results || [], {
system_chars: data.system_chars,
system_layers: data.system_layers,
prompt_eval_count: data.prompt_eval_count ?? data.raw?.prompt_eval_count
});
},
0,
(err2) => finishErr(String(err2 || err || "Chat failed"))
);
}
);
return;
}
genericRequest(
"AssistentChat",
payload,
(data) => {
if (chatEpoch !== state.chatEpoch) {
return;
}
if (data.error) {
finishErr(String(data.error));
return;
}
const reply = data.reply || "";
appendMessage("assistant", reply, null, data.civitai_results || [], msgMeta);
finishOk(reply, data.civitai_results || [], {
system_chars: data.system_chars,
system_layers: data.system_layers,
prompt_eval_count: data.prompt_eval_count ?? data.raw?.prompt_eval_count
});
},
0,
(err) => finishErr(String(err || "Chat failed"))
);
}
function wireDropZone() {
const board = $2("sa_board");
const layout = $2("sa_layout");
layout?.addEventListener("dragover", (e) => {
if (e.dataTransfer?.types?.includes("Files") || e.dataTransfer?.types?.includes("text/uri-list")) {
e.preventDefault();
}
});
layout?.addEventListener("drop", async (e) => {
if (!e.dataTransfer) {
return;
}
if (e.target && e.target.closest && e.target.closest(".sa-slot, .sa-add-cell")) {
return;
}
e.preventDefault();
await handleDropDataTransfer(e.dataTransfer);
});
board?.addEventListener("keydown", (e) => {
if (e.key === "Escape" && state.lightboxIndex >= 0) {
e.preventDefault();
closeGenLightbox();
return;
}
if ((e.key === "Enter" || e.key === " ") && state.boardTab === "generate" && state.selectedGenResultId) {
const row = (state.genResults || []).find((r) => r.id === state.selectedGenResultId);
if (row?.src) {
e.preventDefault();
openGenLightbox(row.id);
return;
}
}
if ((e.key === "ArrowLeft" || e.key === "ArrowRight") && state.lightboxIndex >= 0) {
e.preventDefault();
stepGenLightbox(e.key === "ArrowRight" ? 1 : -1);
return;
}
if (e.key === "Delete" || e.key === "Backspace") {
if (e.target && (e.target.tagName === "TEXTAREA" || e.target.tagName === "INPUT")) {
return;
}
e.preventDefault();
clearSlot(state.selectedSlotId);
}
});
document.addEventListener("paste", async (e) => {
const pane = document.getElementById("assistent");
if (!pane || !pane.classList.contains("active")) {
return;
}
if (e.target && (e.target.tagName === "TEXTAREA" || e.target.tagName === "INPUT")) {
const items2 = e.clipboardData?.items;
let hasImage = false;
if (items2) {
for (const item of items2) {
if (item.type.startsWith("image/")) {
hasImage = true;
break;
}
}
}
if (!hasImage) {
return;
}
}
const items = e.clipboardData?.items;
if (!items) {
return;
}
for (const item of items) {
if (item.type.startsWith("image/")) {
e.preventDefault();
const file = item.getAsFile();
if (file) {
const sel = selectedSlot();
await acceptImageFile(file, sel && sel.type === "ref" ? sel.id : null);
}
return;
}
}
});
}
function wireSplitter() {
const splitter = $2("sa_splitter");
const layout = $2("sa_layout");
const pane = $2("sa_image_pane");
if (!splitter || !layout || !pane) {
return;
}
let dragging = false;
splitter.addEventListener("mousedown", (e) => {
e.preventDefault();
dragging = true;
splitter.classList.add("sa-dragging");
document.body.style.cursor = "col-resize";
document.body.style.userSelect = "none";
});
window.addEventListener("mousemove", (e) => {
if (!dragging) {
return;
}
const rect = layout.getBoundingClientRect();
const x = e.clientX - rect.left;
const pct = Math.min(56, Math.max(22, x / rect.width * 100));
const value = `${pct}%`;
document.documentElement.style.setProperty("--sa-image-width", value);
localStorage.setItem(LS_PANE_WIDTH, value);
});
window.addEventListener("mouseup", () => {
if (!dragging) {
return;
}
dragging = false;
splitter.classList.remove("sa-dragging");
document.body.style.cursor = "";
document.body.style.userSelect = "";
saveUiStateToDisk();
});
}
function registerSendButton() {
if (typeof registerMediaButton !== "function") {
setTimeout(registerSendButton, 500);
return;
}
if (window.__swarmAssistentMediaRegistered) {
return;
}
window.__swarmAssistentMediaRegistered = true;
registerMediaButton(
"Send to Assistent",
(src) => {
putImageOnBoard(src, {
switchTab: true,
note: "Image sent to Assistent",
preferSelected: false
});
const pack = $2("sa_pack");
if (pack && (pack.value === "ordinary" || pack.value === "write_prompt")) {
pack.value = "critique_image";
saveSettings();
}
},
"Open Assistent with this image (vision / critique / prompt help)",
["image"],
true,
true
);
}
async function bootstrapPersisted() {
try {
await applyDiskUiState();
} catch (e) {
console.warn("Assistent: ui-state restore failed", e);
}
try {
await initChatSessions();
} catch (e) {
console.warn("Assistent: chat sessions failed", e);
}
loadConfig(localStorage.getItem(LS_PERSONA) || "neutral", () => {
refreshModels();
refreshInventory(() => {
renderLoraChips();
});
});
probeOllamaHealth();
}
function wire() {
if (!$2("swarm_assistent_root")) {
return;
}
if (typeof genericRequest !== "function") {
setTimeout(wire, 300);
return;
}
if (window.__swarmAssistentWired) {
return;
}
window.__swarmAssistentWired = true;
loadSettings();
setView(state.view || "chat");
updateGate();
ensureBoard();
setBoardTab(state.boardTab || "generate", { persist: false });
syncGenerateSlot();
if (wantsAutoVision()) {
refreshImagePreview();
}
bootstrapPersisted();
setChatsPanelOpen(!!state.chatsDrawerOpen);
wireDropZone();
wireSplitter();
registerSendButton();
wireSlashInput();
$2("sa_btn_new_chat")?.addEventListener("click", () => startNewChat({ saveCurrent: true }));
$2("sa_ctx_chip")?.addEventListener("click", (e) => {
e.stopPropagation();
toggleCtxPanel();
});
$2("sa_ctx_close")?.addEventListener("click", (e) => {
e.stopPropagation();
toggleCtxPanel(false);
});
$2("sa_ctx_panel")?.addEventListener("click", (e) => e.stopPropagation());
$2("sa_ctx_compress")?.addEventListener("click", () => compressNowFromUi());
$2("sa_ctx_reset")?.addEventListener("click", () => resetCompressionFromUi());
$2("sa_ctx_auto")?.addEventListener("change", () => {
COMPRESS_AUTO = !!$2("sa_ctx_auto")?.checked;
const settingsAuto = $2("sa_compress_auto");
if (settingsAuto) {
settingsAuto.checked = COMPRESS_AUTO;
}
if (state.config?.assistant) {
state.config.assistant.compress_auto = COMPRESS_AUTO;
}
setStatus(COMPRESS_AUTO ? "\u0410\u0432\u0442\u043E\u0441\u0436\u0430\u0442\u0438\u0435 \u0432\u043A\u043B\u044E\u0447\u0435\u043D\u043E" : "\u0410\u0432\u0442\u043E\u0441\u0436\u0430\u0442\u0438\u0435 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u043E");
});
$2("sa_compress_auto")?.addEventListener("change", () => {
COMPRESS_AUTO = !!$2("sa_compress_auto")?.checked;
const panelAuto = $2("sa_ctx_auto");
if (panelAuto) {
panelAuto.checked = COMPRESS_AUTO;
}
});
$2("sa_btn_chats")?.addEventListener("click", (e) => {
e.stopPropagation();
setChatsPanelOpen(!state.chatsPanelOpen);
});
$2("sa_btn_chats_close")?.addEventListener("click", () => setChatsPanelOpen(false));
$2("sa_session_label")?.addEventListener("click", (e) => {
e.stopPropagation();
setChatsPanelOpen(!state.chatsPanelOpen);
});
$2("sa_session_label")?.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
setChatsPanelOpen(!state.chatsPanelOpen);
}
});
$2("sa_chats_panel")?.addEventListener("click", (e) => e.stopPropagation());
$2("sa_chats_list")?.addEventListener("click", (e) => {
const row = e.target.closest(".sa-chat-row");
if (!row) {
return;
}
const id = row.dataset.id;
if (e.target.closest("[data-del]")) {
e.preventDefault();
if (window.confirm("\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u044D\u0442\u043E\u0442 \u0447\u0430\u0442 \u0438\u0437 \u0438\u0441\u0442\u043E\u0440\u0438\u0438?")) {
deleteChat(id);
}
return;
}
if (e.target.closest("[data-open]")) {
switchToChat(id);
}
});
let chatsSearchTimer = null;
$2("sa_chats_search")?.addEventListener("input", () => {
const q = ($2("sa_chats_search")?.value || "").trim();
state.chatsQuery = q;
if (!q) {
state.chatsSearchHits = null;
renderChatsList();
return;
}
renderChatsList();
clearTimeout(chatsSearchTimer);
chatsSearchTimer = setTimeout(async () => {
try {
const hits = await diskPersist()?.searchChats?.(q);
if ((state.chatsQuery || "") !== q) {
return;
}
state.chatsSearchHits = Array.isArray(hits) ? hits : [];
renderChatsList();
} catch (e) {
}
}, 220);
});
$2("sa_tab_chat")?.addEventListener("click", () => setView("chat"));
$2("sa_tab_train")?.addEventListener("click", () => setView("train"));
$2("sa_tab_settings")?.addEventListener("click", () => openSettings(state.settingsTab || "behavior"));
$2("sa_board_tab_gen")?.addEventListener("click", () => setBoardTab("generate"));
$2("sa_board_tab_refs")?.addEventListener("click", () => setBoardTab("refs"));
$2("sa_persona")?.addEventListener("change", onPersonaChanged);
$2("sa_persona_delete")?.addEventListener("click", () => deleteCurrentOverlayPersona());
document.querySelectorAll("#sa_settings .sa-stab").forEach((btn) => {
btn.addEventListener("click", () => setSettingsTab(btn.getAttribute("data-stab")));
});
$2("sa_btn_mem_refresh")?.addEventListener("click", () => {
refreshMemoryList();
});
$2("sa_mem_kind")?.addEventListener("change", renderMemoryList);
$2("sa_mem_scope")?.addEventListener("change", renderMemoryList);
$2("sa_mem_search")?.addEventListener("input", () => renderMemoryList());
$2("sa_btn_mem_clear_kind")?.addEventListener("click", () => {
const kind = memoryKindFilter();
clearCraftMemory({ kind: kind === "all" ? "" : kind, label: kind === "all" ? "\u0432\u0435\u0441\u044C \u043A\u0440\u0430\u0444\u0442 (\u0444\u0438\u043B\u044C\u0442\u0440 \u0442\u0438\u043F\u0430)" : `\u0442\u0438\u043F ${kind}` });
});
$2("sa_btn_mem_clear_persona")?.addEventListener("click", () => {
clearCraftMemory({ scope: "personal", persona: $2("sa_persona")?.value || "neutral", label: "\u043A\u0440\u0430\u0444\u0442 \u044D\u0442\u043E\u0439 \u043B\u0438\u0447\u043D\u043E\u0441\u0442\u0438" });
});
$2("sa_btn_mem_clear_shared")?.addEventListener("click", () => {
clearCraftMemory({ scope: "shared", label: "\u043E\u0431\u0449\u0443\u044E \u043A\u0440\u0430\u0444\u0442-\u043F\u0430\u043C\u044F\u0442\u044C" });
});
$2("sa_btn_mem_clear_all")?.addEventListener("click", () => {
clearCraftMemory({ label: "\u0432\u0435\u0441\u044C \u043A\u0440\u0430\u0444\u0442 (non-bundled)" });
});
$2("sa_btn_prefs_refresh")?.addEventListener("click", () => refreshUserPrefs());
$2("sa_btn_pref_add_global")?.addEventListener("click", () => addUserPref("global"));
$2("sa_btn_pref_add_persona")?.addEventListener("click", () => addUserPref("persona"));
$2("sa_btn_prefs_clear_global")?.addEventListener("click", () => clearUserPrefs("global"));
$2("sa_btn_prefs_clear_persona")?.addEventListener("click", () => clearUserPrefs("persona"));
$2("sa_btn_prefs_clear_all")?.addEventListener("click", () => clearUserPrefs("all"));
$2("sa_user_prefs_weight")?.addEventListener("input", () => {
const lab = $2("sa_user_prefs_weight_val");
if (lab) {
lab.textContent = Number($2("sa_user_prefs_weight").value).toFixed(1);
}
});
$2("sa_user_prefs_weight")?.addEventListener("change", () => saveKnobs());
$2("sa_memory_top_k")?.addEventListener("change", () => saveKnobs());
$2("sa_btn_knobs_save")?.addEventListener("click", () => saveKnobs());
$2("sa_btn_reset_ui")?.addEventListener("click", () => resetUiState());
$2("sa_btn_persona_export")?.addEventListener("click", () => exportSelectedPersona());
$2("sa_btn_persona_import")?.addEventListener("click", () => $2("sa_persona_import_file")?.click());
$2("sa_persona_import_file")?.addEventListener("change", (e) => {
const file = e.target?.files?.[0];
if (file) {
importPersonaFile(file);
}
e.target.value = "";
});
$2("sa_btn_persona_clone")?.addEventListener("click", () => cloneSelectedPersona());
$2("sa_btn_persona_delete_panel")?.addEventListener("click", () => deleteSelectedOverlayPersona());
$2("sa_btn_settings_health")?.addEventListener("click", () => {
probeOllamaHealth();
setTimeout(syncSettingsHealthLine, 400);
});
$2("sa_settings_chat_model")?.addEventListener("change", () => {
const v = $2("sa_settings_chat_model")?.value;
if (v && $2("sa_model")) {
$2("sa_model").value = v;
saveSettings();
}
});
$2("sa_btn_look_result")?.addEventListener("click", () => askLookAtResult());
$2("sa_ollama_health")?.addEventListener("click", () => probeOllamaHealth());
document.addEventListener("keydown", (e) => {
if (state.lightboxIndex >= 0) {
if (e.key === "Escape") {
e.preventDefault();
closeGenLightbox();
return;
}
if (e.key === "ArrowLeft") {
e.preventDefault();
stepGenLightbox(-1);
return;
}
if (e.key === "ArrowRight") {
e.preventDefault();
stepGenLightbox(1);
return;
}
}
if (e.key !== "Escape") {
return;
}
let closed = false;
if (state.view === "settings") {
closeSettings();
closed = true;
}
if (state.chatsPanelOpen) {
setChatsPanelOpen(false);
closed = true;
}
const slash = $2("sa_slash_menu");
if (slash && !slash.hidden) {
slash.hidden = true;
closed = true;
}
closeAllMoreMenus();
if (closed) {
e.preventDefault();
}
});
document.getElementById(TAB_BUTTON_ID)?.addEventListener("click", () => {
setTimeout(() => $2("sa_input")?.focus(), 80);
});
$2("sa_btn_refresh_models")?.addEventListener("click", () => {
saveSettings();
refreshModels();
probeOllamaHealth();
});
$2("sa_btn_refresh_inventory")?.addEventListener("click", () => refreshInventory(() => {
renderLoraChips();
}, { rescan: true }));
$2("sa_btn_add_ref")?.addEventListener("click", () => {
setBoardTab("refs");
addRefSlot({ select: true });
});
$2("sa_btn_use_current")?.addEventListener("click", () => snapshotGenerateToRef());
$2("sa_btn_as_init")?.addEventListener("click", async () => {
closeAllMoreMenus();
const src = selectedSrc() || findCurrentGenerateSrc();
if (!src) {
setStatus("\u041D\u0435\u0442 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u044F \u0434\u043B\u044F Init");
return;
}
await setInitFromSrc(src);
const pack = $2("sa_pack");
if (pack && (pack.value === "ordinary" || pack.value === "write_prompt")) {
setPackValue("inpaint_edit", { flash: true });
}
});
$2("sa_btn_as_mask")?.addEventListener("click", async () => {
closeAllMoreMenus();
const src = selectedSrc();
if (!src) {
setStatus("\u0412\u044B\u0431\u0435\u0440\u0438 \u043E\u043A\u043D\u043E \u0441 \u043C\u0430\u0441\u043A\u043E\u0439");
return;
}
await setMaskFromSrc(src);
setPackValue("inpaint_edit", { flash: true });
});
$2("sa_btn_clear_init")?.addEventListener("click", () => {
if (window.confirm("\u0421\u0431\u0440\u043E\u0441\u0438\u0442\u044C Init \u0438 Mask?")) {
clearInitAndMask();
}
closeAllMoreMenus();
});
$2("sa_btn_clear_image")?.addEventListener("click", () => clearSlot(state.selectedSlotId));
$2("sa_btn_board_more")?.addEventListener("click", (e) => {
e.stopPropagation();
toggleMoreMenu("sa_board_more_menu", "sa_btn_board_more");
});
$2("sa_btn_send")?.addEventListener("click", () => sendChat());
$2("sa_btn_build_gen")?.addEventListener("click", () => buildCurrentAndGenerate());
$2("sa_btn_interrupt")?.addEventListener("click", () => {
doInterruptNow();
clearInFlightUi({ status: "\u041F\u0440\u0435\u0440\u0432\u0430\u043D\u043E" });
});
$2("sa_btn_clear")?.addEventListener("click", () => {
if (window.confirm("\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u0432\u0435\u0441\u044C \u0447\u0430\u0442 Assistent?")) {
clearChatHistory();
}
});
$2("sa_btn_clear_more")?.addEventListener("click", (e) => {
e.stopPropagation();
toggleMoreMenu("sa_clear_more_menu", "sa_btn_clear_more");
});
$2("sa_btn_clear_confirm")?.addEventListener("click", () => {
closeAllMoreMenus();
if (window.confirm("\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u0432\u0435\u0441\u044C \u0447\u0430\u0442 Assistent?")) {
clearChatHistory();
}
});
$2("sa_btn_clear_patches")?.addEventListener("click", () => {
closeAllMoreMenus();
clearPatchBlocksOnly();
});
document.addEventListener("click", () => {
if (state.chatsPanelOpen) {
setChatsPanelOpen(false);
}
if (state.ctxPanelOpen) {
toggleCtxPanel(false);
}
closeAllMoreMenus();
});
$2("sa_board_more_menu")?.addEventListener("click", (e) => e.stopPropagation());
$2("sa_clear_more_menu")?.addEventListener("click", (e) => e.stopPropagation());
$2("sa_base_url")?.addEventListener("change", saveSettings);
$2("sa_model")?.addEventListener("change", () => {
const v = $2("sa_model")?.value;
if (v && $2("sa_settings_chat_model")) {
$2("sa_settings_chat_model").value = v;
}
saveSettings();
});
$2("sa_embed_model")?.addEventListener("change", () => {
state.preferredEmbed = $2("sa_embed_model")?.value || "";
saveSettings();
});
$2("sa_pack")?.addEventListener("change", () => {
state.packUserTouched = true;
saveSettings();
syncModeBadge();
});
$2("sa_chips")?.addEventListener("click", async (e) => {
const btn = e.target.closest(".sa-chip");
if (!btn || state.busy || state.generating) {
return;
}
const aspect = btn.getAttribute("data-aspect");
const seed = btn.getAttribute("data-seed");
const vary = btn.getAttribute("data-vary");
const profile = btn.getAttribute("data-krea-profile");
if (aspect) {
await applyQuickPatch({ aspect, actions: ["generate"] }, `Aspect ${aspect}`);
} else if (seed === "lock") {
await applyQuickPatch({ lock_seed: true }, "Seed locked");
} else if (seed === "random") {
await applyQuickPatch({ seed: -1, actions: ["generate"] }, "Seed random");
} else if (vary) {
await applyQuickPatch({ vary: true, seed: -1, actions: ["generate"] }, "Vary");
} else if (profile === "turbo") {
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 || mergedGenerationDefaults("raw");
await applyQuickPatch({ steps: p.steps ?? 28, cfg: p.cfg ?? 4.5, sigma_shift: p.sigma_shift, actions: ["generate"] }, "RAW");
}
renderLoraChips();
});
$2("sa_auto_vision")?.addEventListener("change", () => {
saveSettings();
const gen = generateSlot();
if (gen) {
gen.attach = wantsAutoVision();
renderBoard();
}
});
$2("sa_auto_apply")?.addEventListener("change", saveSettings);
$2("sa_auto_generate")?.addEventListener("change", saveSettings);
$2("sa_auto_critique")?.addEventListener("change", saveSettings);
$2("sa_auto_download")?.addEventListener("change", saveSettings);
$2("sa_park_llm")?.addEventListener("change", saveSettings);
syncChipHighlight();
setInterval(syncChipHighlight, 2500);
setInterval(renderLoraChips, 4e3);
syncLiveParamsBar();
setInterval(syncLiveParamsBar, 1200);
syncModeBadge();
syncBuildGenButton();
setInterval(updateGate, 2e3);
setInterval(syncGenerateSlot, 700);
setInterval(() => {
if (!state.busy && !state.generating) {
probeOllamaHealth();
}
}, 45e3);
setInterval(() => {
if (!state.busy && !state.generating) {
}
}, 12e4);
window.addEventListener("beforeunload", () => {
try {
saveActiveChatToStore({ dropEmpty: true });
const chat = findChat(state.activeChatId);
if (chat && (chat.messages || []).length) {
diskPersist()?.saveChat(chat, { immediate: true });
}
diskPersist()?.saveUiState(collectUiState(), { immediate: true });
} catch (e) {
}
});
setInterval(() => {
if (!state.busy) {
const tabOn = !!document.getElementById(TAB_BUTTON_ID)?.classList.contains("tab-button-selected") || !!document.getElementById("swarm_assistent_root")?.offsetParent;
refreshInventory(null, { rescan: inventoryIsStale(tabOn ? 45e3 : 12e4) });
}
}, 3e4);
window.SA = window.SA || {};
window.SA.app = {
getState: () => state,
setTrainingLock(on) {
state.trainingLock = !!on;
},
refreshModels: () => refreshModels(),
setStatus: (msg) => setStatus(msg)
};
window.swarmAssistent = {
setImageFromSrc,
putImageOnBoard,
clearVisionImage,
snapshotGenerateToRef,
openAssistentTab,
sendToAssistent: (src) => {
putImageOnBoard(src, { switchTab: true, note: "\u0418\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E \u0432 Assistent", preferSelected: false });
setBoardTab("refs");
},
isKreaSelected,
resolveCurrentCheckpoint,
refreshInventory,
applyPatch,
triggerGenerate,
setInitFromSrc,
setMaskFromSrc,
clearInitAndMask,
slotById,
renderBoard,
setBoardTab
};
}
function wireSlashInput() {
const input = $2("sa_input");
if (!input || input.dataset.saSlashWired) {
return;
}
input.dataset.saSlashWired = "1";
input.addEventListener("input", () => updateSlashMenuFromInput());
input.addEventListener("keydown", (e) => {
const menu = $2("sa_slash_menu");
const open = menu && !menu.hidden;
if (open) {
const items = slashMatches(input.value.split(/\s/)[0] || "");
if (e.key === "ArrowDown") {
e.preventDefault();
state.slashIndex = Math.min(items.length - 1, (state.slashIndex || 0) + 1);
renderSlashMenu(items);
return;
}
if (e.key === "ArrowUp") {
e.preventDefault();
state.slashIndex = Math.max(0, (state.slashIndex || 0) - 1);
renderSlashMenu(items);
return;
}
if (e.key === "Tab" || e.key === "Enter" && !e.shiftKey) {
const pick = items[state.slashIndex || 0];
if (pick && input.value.trim() === (input.value.split(/\s/)[0] || "")) {
e.preventDefault();
applySlashPick(pick);
return;
}
}
if (e.key === "Escape") {
hideSlashMenu();
return;
}
}
if (e.key === "Enter" && !e.shiftKey && !e.altKey) {
e.preventDefault();
hideSlashMenu();
sendChat();
}
});
input.addEventListener("blur", () => setTimeout(hideSlashMenu, 150));
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", wire);
} else {
wire();
}
})();
// src/training.js
var $ = (id) => document.getElementById(id);
function escapeHtml(s) {
return String(s ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
function attachTraining(SA2) {
const state = {
ttab: "dataset",
samples: [],
hfResults: [],
hfSelected: null,
hfCheck: null,
hfMapping: null,
trainWs: null,
polling: null,
agentSettings: { enabled: true, auto_link_on_approve: true, heard_quota: 3 },
agentLinked: 0
};
function setAgentHeardStats(linked) {
const el = $("sa_agent_heard_stats");
if (el) {
el.textContent = `\u041F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u043E: ${linked ?? state.agentLinked ?? "\u2014"}`;
}
}
async function loadAgentHeardSettings() {
try {
const data = await SA2.request("AssistentGetDatasetAgentSettings", {});
const s = data?.settings || {};
state.agentSettings = {
enabled: s.enabled !== false,
auto_link_on_approve: s.auto_link_on_approve !== false,
heard_quota: s.heard_quota ?? 3
};
state.agentLinked = data?.linked ?? 0;
if ($("sa_agent_heard_enabled")) $("sa_agent_heard_enabled").checked = state.agentSettings.enabled;
if ($("sa_agent_auto_link")) $("sa_agent_auto_link").checked = state.agentSettings.auto_link_on_approve;
if ($("sa_agent_heard_quota")) $("sa_agent_heard_quota").value = String(state.agentSettings.heard_quota);
setAgentHeardStats(state.agentLinked);
} catch (e) {
console.warn("loadAgentHeardSettings", e);
}
}
async function saveAgentHeardSettings() {
const settings = {
enabled: !!$("sa_agent_heard_enabled")?.checked,
auto_link_on_approve: !!$("sa_agent_auto_link")?.checked,
heard_quota: Math.max(0, Math.min(8, parseInt($("sa_agent_heard_quota")?.value, 10) || 3))
};
try {
const data = await SA2.request("AssistentSaveDatasetAgentSettings", { settings });
state.agentSettings = data?.settings || settings;
setTrainStatus("\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \xAB\u0443\u0441\u043B\u044B\u0448\u0430\u043D\u043D\u043E\u0433\u043E\xBB \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u044B");
} catch (e) {
setTrainStatus(String(e.message || e));
}
}
async function syncAllToAgent() {
setTrainStatus("\u041F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435 \u043A \u0430\u0433\u0435\u043D\u0442\u0443\u2026");
try {
await saveAgentHeardSettings();
const data = await SA2.request("AssistentSyncDatasetToAgent", { approved_only: true, relink: false });
state.agentLinked = data?.total_linked ?? state.agentLinked;
setAgentHeardStats(state.agentLinked);
setTrainStatus(`\u041F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u043E: +${data?.linked_now ?? 0}, \u0432\u0441\u0435\u0433\u043E ${data?.total_linked ?? "\u2014"}`);
await refreshSamples();
} catch (e) {
setTrainStatus(String(e.message || e));
}
}
function setTrainStatus(msg) {
const el = $("sa_train_status");
if (el) el.textContent = msg || "";
}
function setTrainingTab(id) {
state.ttab = id || "dataset";
document.querySelectorAll("#sa_training .sa-ttab").forEach((btn) => {
const on = btn.getAttribute("data-ttab") === state.ttab;
btn.classList.toggle("sa-ttab-active", on);
btn.setAttribute("aria-selected", on ? "true" : "false");
});
document.querySelectorAll("#sa_training .sa-tpane").forEach((pane) => {
pane.hidden = pane.getAttribute("data-tpane") !== state.ttab;
});
if (state.ttab === "dataset") {
refreshSamples();
loadAgentHeardSettings();
}
if (state.ttab === "train") {
syncModelfileModels();
syncQloraModels();
}
if (state.ttab === "models") refreshTrainModels();
}
async function refreshSamples() {
try {
const status = $("sa_train_filter_status")?.value || "all";
const persona = $("sa_train_filter_persona")?.value || "all";
const data = await SA2.request("AssistentListTrainSamples", { status, persona, limit: 300 });
state.samples = data?.samples || [];
const stats = $("sa_train_stats");
if (stats) stats.textContent = `\u041E\u0434\u043E\u0431\u0440\u0435\u043D\u043E: ${data?.approved ?? "\u2014"} \xB7 \u0432\u0441\u0435\u0433\u043E: ${data?.total ?? "\u2014"}`;
const personaSel = $("sa_train_filter_persona");
if (personaSel && $("sa_persona")) {
const cur = personaSel.value || "all";
personaSel.innerHTML = '<option value="all">\u0412\u0441\u0435 \u043B\u0438\u0447\u043D\u043E\u0441\u0442\u0438</option>';
for (const opt of $("sa_persona").options) {
const o = document.createElement("option");
o.value = opt.value;
o.textContent = opt.textContent;
personaSel.appendChild(o);
}
personaSel.value = cur;
}
renderSamples();
} catch (e) {
setTrainStatus(String(e.message || e));
}
}
function renderSamples() {
const root = $("sa_train_samples");
if (!root) return;
if (!state.samples.length) {
root.innerHTML = '<div class="sa-mem-empty">\u041D\u0435\u0442 \u043F\u0440\u0438\u043C\u0435\u0440\u043E\u0432. \u041E\u0442\u043C\u0435\u0442\u044C \u043E\u0442\u0432\u0435\u0442\u044B \u0432 \u0447\u0430\u0442\u0435 \u0438\u043B\u0438 \u0438\u043C\u043F\u043E\u0440\u0442\u0438\u0440\u0443\u0439 \u0434\u0430\u0442\u0430\u0441\u0435\u0442.</div>';
return;
}
root.innerHTML = "";
for (const s of state.samples) {
const div = document.createElement("div");
div.className = "sa-train-sample";
div.dataset.id = s.id;
const msgs = s.messages || [];
const preview = msgs.map((m) => `${m.role}: ${(m.content || "").slice(0, 120)}`).join("\n");
const linked = s.agent_linked ? " \xB7 \u{1F517} \u0430\u0433\u0435\u043D\u0442" : "";
div.innerHTML = `
<div class="sa-train-sample-head">
<span class="sa-hf-badge sa-hf-badge-${s.status === "approved" ? "ok" : s.status === "rejected" ? "no" : "map"}">${escapeHtml(s.status)}</span>
<span>${escapeHtml(s.source)} \xB7 ${escapeHtml(s.persona || "\u2014")} \xB7 ${escapeHtml(s.pack || "\u2014")}${linked}</span>
<button type="button" class="basic-button" data-approve="1">\u2713</button>
<button type="button" class="basic-button" data-reject="1">\u2715</button>
<button type="button" class="basic-button" data-link="1" title="\u041F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u043A \u0430\u0433\u0435\u043D\u0442\u0443">\u{1F517}</button>
<button type="button" class="basic-button" data-unlink="1" title="\u041E\u0442\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u043E\u0442 \u0430\u0433\u0435\u043D\u0442\u0430">\u26D3</button>
<button type="button" class="basic-button sa-danger-btn" data-del="1">\u0423\u0434\u0430\u043B\u0438\u0442\u044C</button>
</div>
<textarea spellcheck="false">${escapeHtml(preview)}</textarea>`;
root.appendChild(div);
}
}
async function upsertSample(patch) {
await SA2.request("AssistentUpsertTrainSample", patch);
await refreshSamples();
}
function hfStringColumns(check) {
const cols = check?.schema?.columns;
if (Array.isArray(cols) && cols.length) return cols;
const feats = check?.features;
if (Array.isArray(feats)) {
return feats.map((f) => f?.name).filter(Boolean);
}
if (feats && typeof feats === "object") return Object.keys(feats);
return [];
}
function renderHfMappingUI(check) {
const row = $("sa_hf_mapping_row");
if (!row) return;
const gate = check?.gate;
const schemaKind = check?.schema?.kind;
const needsMapping = gate === "mapping" || schemaKind === "fiction_tags_text";
row.hidden = !needsMapping;
if (!needsMapping) {
state.hfMapping = null;
return;
}
const cols = hfStringColumns(check);
const userSel = $("sa_hf_user_col");
const asstSel = $("sa_hf_asst_col");
const presetSel = $("sa_hf_mapping_preset");
if (userSel) {
userSel.innerHTML = cols.map((c) => `<option value="${escapeHtml(c)}">${escapeHtml(c)}</option>`).join("");
if (cols.includes("tags")) userSel.value = "tags";
else if (cols.includes("title")) userSel.value = "title";
}
if (asstSel) {
asstSel.innerHTML = cols.map((c) => `<option value="${escapeHtml(c)}">${escapeHtml(c)}</option>`).join("");
if (cols.includes("text")) asstSel.value = "text";
else if (cols.includes("output")) asstSel.value = "output";
}
if (schemaKind === "fiction_tags_text" && presetSel) {
presetSel.value = "fiction_tags_text";
state.hfMapping = { kind: "fiction_tags_text", preset: "fiction_tags_text" };
}
}
function buildHfMappingPayload() {
const preset = $("sa_hf_mapping_preset")?.value;
if (preset === "fiction_tags_text") {
return { kind: "fiction_tags_text", preset: "fiction_tags_text" };
}
const userCol = $("sa_hf_user_col")?.value;
const asstCol = $("sa_hf_asst_col")?.value;
if (userCol && asstCol) {
return { kind: "custom", user_col: userCol, assistant_col: asstCol };
}
return state.hfMapping;
}
async function syncQloraModels() {
try {
const baseUrl = $("sa_base_url")?.value || localStorage.getItem("swarm_assistent_base_url") || "";
const data = await SA2.request("AssistentListModels", { baseUrl });
const models = data?.models || [];
const sel = $("sa_qlora_ollama_base");
if (!sel) return;
const cur = sel.value;
sel.innerHTML = '<option value="">\u2014</option>';
for (const m of models) {
const opt = document.createElement("option");
opt.value = m;
opt.textContent = m;
sel.appendChild(opt);
}
if (cur) sel.value = cur;
else if ($("sa_model")?.value) sel.value = $("sa_model").value;
} catch (e) {
}
}
function renderHfList() {
const root = $("sa_hf_list");
if (!root) return;
root.innerHTML = "";
const showAll = !!$("sa_hf_show_all")?.checked;
for (const r of state.hfResults) {
if (!showAll && r.gate === "rejected") continue;
const row = document.createElement("div");
row.className = "sa-hf-row" + (state.hfSelected === r.id ? " sa-hf-row-active" : "") + (r.gate === "rejected" ? " sa-hf-rejected" : "");
row.dataset.id = r.id;
const badge = r.gate === "ok" ? "ok" : r.gate === "mapping" ? "map" : "no";
row.innerHTML = `<span class="sa-hf-badge sa-hf-badge-${badge}">${escapeHtml(r.gate)}</span><strong>${escapeHtml(r.id)}</strong><span>${escapeHtml(r.reason || "")}</span>`;
root.appendChild(row);
}
}
async function searchHf() {
const q = ($("sa_hf_search")?.value || "").trim();
setTrainStatus("\u041F\u043E\u0438\u0441\u043A\u2026");
try {
const data = await SA2.request("AssistentSearchHfDatasets", {
q,
limit: 24,
show_all: !!$("sa_hf_show_all")?.checked
});
state.hfResults = data?.results || [];
renderHfList();
setTrainStatus(`\u041D\u0430\u0439\u0434\u0435\u043D\u043E: ${state.hfResults.length}`);
} catch (e) {
setTrainStatus(String(e.message || e));
}
}
async function checkHfLink() {
const link = ($("sa_hf_link")?.value || "").trim();
const status = $("sa_hf_status");
if (!link) return;
if (status) status.textContent = "\u041F\u0440\u043E\u0432\u0435\u0440\u044F\u044E\u2026";
try {
const data = await SA2.request("AssistentCheckHfDataset", { dataset: link });
state.hfCheck = data;
state.hfSelected = data.id;
if (status) {
status.textContent = data.gate === "rejected" ? `\u041E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u043E: ${data.reason}` : `${data.gate}: ${data.reason || "OK"}`;
}
const preview = $("sa_hf_preview");
if (preview) {
preview.hidden = false;
preview.textContent = JSON.stringify(data.sample_rows || data.features || data, null, 2).slice(0, 8e3);
}
const importRow = $("sa_hf_import_row");
if (importRow) importRow.hidden = data.gate === "rejected";
renderHfMappingUI(data);
} catch (e) {
if (status) status.textContent = String(e.message || e);
}
}
async function importHf() {
if (!state.hfSelected && !state.hfCheck?.id) {
setTrainStatus("\u0421\u043D\u0430\u0447\u0430\u043B\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u043D\u0430\u0431\u043E\u0440");
return;
}
const id = state.hfSelected || state.hfCheck.id;
const limit = Number($("sa_hf_import_limit")?.value) || 200;
const mapping = buildHfMappingPayload();
try {
const data = await SA2.request("AssistentImportHfDataset", { dataset: id, limit, mapping });
setTrainStatus(`\u0418\u043C\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u043E: ${data.imported}${data.runner_only ? " (runner-only)" : ""}`);
await refreshSamples();
} catch (e) {
setTrainStatus(String(e.message || e));
}
}
async function syncModelfileModels() {
try {
const baseUrl = $("sa_base_url")?.value || localStorage.getItem("swarm_assistent_base_url") || "";
const data = await SA2.request("AssistentListModels", { baseUrl });
const models = data?.models || [];
for (const selId of ["sa_modelfile_base"]) {
const sel = $(selId);
if (!sel) continue;
const cur = sel.value;
sel.innerHTML = '<option value="">\u2014</option>';
for (const m of models) {
const opt = document.createElement("option");
opt.value = m;
opt.textContent = m;
sel.appendChild(opt);
}
if (cur) sel.value = cur;
}
const personaSel = $("sa_modelfile_persona");
if (personaSel && $("sa_persona")) {
personaSel.innerHTML = $("sa_persona").innerHTML;
personaSel.value = $("sa_persona").value || "neutral";
}
} catch (e) {
}
}
async function createModelfile() {
setTrainStatus("\u0421\u043E\u0437\u0434\u0430\u044E \u043C\u043E\u0434\u0435\u043B\u044C\u2026");
try {
const data = await SA2.request("AssistentCreateOllamaModel", {
base_url: $("sa_base_url")?.value,
base_model: $("sa_modelfile_base")?.value,
name: $("sa_modelfile_name")?.value,
persona: $("sa_modelfile_persona")?.value,
system: $("sa_modelfile_system")?.value,
shots: Number($("sa_modelfile_shots")?.value) || 8,
num_ctx: Number($("sa_modelfile_num_ctx")?.value) || 16384,
temperature: Number($("sa_modelfile_temp")?.value) || 0.7
});
setTrainStatus(`\u0413\u043E\u0442\u043E\u0432\u043E: ${data.name}`);
SA2.app?.refreshModels?.();
} catch (e) {
setTrainStatus(String(e.message || e));
}
}
function setTrainMode(mode) {
$("sa_train_form_modelfile").hidden = mode !== "modelfile";
$("sa_train_form_qlora").hidden = mode !== "qlora";
}
function setTrainingLock(on, text) {
const root = $("swarm_assistent_root");
const banner = $("sa_train_banner");
if (root) root.classList.toggle("sa-root-training-lock", !!on);
if (banner) {
banner.hidden = !on;
const t = $("sa_train_banner_text");
if (t && text) t.textContent = text;
}
SA2.app?.setTrainingLock?.(!!on);
}
async function pollTrainJob() {
try {
const data = await SA2.request("AssistentGetTrainJob", {});
const prog = data?.progress || (data?.job?.progress_json ? JSON.parse(data.job.progress_json) : null);
const active = data?.training_active || data?.job?.status === "running";
const status = data?.job?.status || prog?.status;
setTrainingLock(active, prog?.status === "running" ? `\u0422\u0440\u0435\u043D\u0438\u0440\u043E\u0432\u043A\u0430 \xB7 ${prog?.percent ?? 0}%` : "\u0418\u0434\u0451\u0442 \u0442\u0440\u0435\u043D\u0438\u0440\u043E\u0432\u043A\u0430\u2026");
const logEl = $("sa_train_log");
const bar = $("sa_train_progress_fill");
const box = $("sa_train_progress");
if (prog) {
if (box) box.hidden = false;
if (bar && prog.percent != null) bar.style.width = `${prog.percent}%`;
if (logEl && prog.log) logEl.textContent = prog.log;
}
if (!active) {
clearInterval(state.polling);
state.polling = null;
$("sa_btn_qlora_cancel").hidden = true;
setTrainingLock(false);
if (status === "completed" || status === "completed_with_warnings") {
const ollama = prog?.ollama;
if (ollama?.success) {
setTrainStatus(`\u0413\u043E\u0442\u043E\u0432\u043E: \u043C\u043E\u0434\u0435\u043B\u044C ${ollama.name} \u0432 Ollama`);
SA2.app?.refreshModels?.();
} else if (ollama?.skipped) {
setTrainStatus(ollama.note || ollama.error || "\u0410\u0434\u0430\u043F\u0442\u0435\u0440 \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D, Ollama \u2014 \u0432\u0440\u0443\u0447\u043D\u0443\u044E");
} else if (ollama?.error) {
setTrainStatus(`\u041E\u0431\u0443\u0447\u0435\u043D\u0438\u0435 OK, Ollama: ${ollama.error}`);
} else if (status === "completed_with_warnings") {
setTrainStatus("\u041E\u0431\u0443\u0447\u0435\u043D\u0438\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043E \u0441 \u043F\u0440\u0435\u0434\u0443\u043F\u0440\u0435\u0436\u0434\u0435\u043D\u0438\u044F\u043C\u0438 \u2014 \u0441\u043C. \u043B\u043E\u0433");
} else {
setTrainStatus("QLoRA \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043E");
}
} else if (status === "failed") {
setTrainStatus(`\u041E\u0448\u0438\u0431\u043A\u0430 \u0442\u0440\u0435\u043D\u0438\u0440\u043E\u0432\u043A\u0438 (exit ${prog?.exit_code ?? "?"})`);
}
}
} catch (e) {
}
}
async function startQlora() {
setTrainStatus("\u0417\u0430\u043F\u0443\u0441\u043A\u2026");
try {
const hfDs = ($("sa_qlora_hf_dataset")?.value || "").trim();
const mapping = hfDs ? buildHfMappingPayload() : void 0;
await SA2.request("AssistentStartTrainJob", {
base_url: $("sa_base_url")?.value,
chat_model: $("sa_model")?.value,
base_model: $("sa_qlora_base")?.value,
ollama_base: $("sa_qlora_ollama_base")?.value,
output_name: $("sa_qlora_name")?.value,
rank: Number($("sa_qlora_rank")?.value) || 16,
alpha: Number($("sa_qlora_alpha")?.value) || 32,
lr: Number($("sa_qlora_lr")?.value) || 2e-4,
epochs: Number($("sa_qlora_epochs")?.value) || 3,
seq_len: Number($("sa_qlora_seq")?.value) || 2048,
max_samples: Number($("sa_qlora_max_samples")?.value) || 0,
four_bit: !!$("sa_qlora_4bit")?.checked,
hf_dataset: hfDs || void 0,
hf_mapping: mapping
});
$("sa_btn_qlora_cancel").hidden = false;
setTrainingLock(true, "\u0418\u0434\u0451\u0442 \u0442\u0440\u0435\u043D\u0438\u0440\u043E\u0432\u043A\u0430\u2026");
if (state.polling) clearInterval(state.polling);
state.polling = setInterval(pollTrainJob, 1500);
pollTrainJob();
setTrainStatus("\u0422\u0440\u0435\u043D\u0438\u0440\u043E\u0432\u043A\u0430 \u0437\u0430\u043F\u0443\u0449\u0435\u043D\u0430");
} catch (e) {
setTrainStatus(String(e.message || e));
}
}
async function cancelQlora() {
try {
await SA2.request("AssistentCancelTrainJob", {});
setTrainingLock(false);
setTrainStatus("\u041E\u0442\u043C\u0435\u043D\u0435\u043D\u043E");
} catch (e) {
setTrainStatus(String(e.message || e));
}
}
async function refreshTrainModels() {
const root = $("sa_train_models_list");
if (!root) return;
try {
const data = await SA2.request("AssistentListModels", { baseUrl: $("sa_base_url")?.value });
const models = data?.models || [];
root.innerHTML = models.length ? models.map((m) => `<div class="sa-hf-row"><strong>${escapeHtml(m)}</strong></div>`).join("") : '<div class="sa-mem-empty">\u041D\u0435\u0442 \u043C\u043E\u0434\u0435\u043B\u0435\u0439</div>';
} catch (e) {
root.innerHTML = `<div class="sa-mem-empty">${escapeHtml(e.message)}</div>`;
}
}
async function saveRunner() {
try {
await SA2.request("AssistentSaveRunnerSettings", {
python: $("sa_runner_python")?.value,
kind: $("sa_runner_kind")?.value || "builtin",
workdir: $("sa_runner_workdir")?.value,
cmd: $("sa_runner_cmd")?.value,
gguf_script: $("sa_runner_gguf_script")?.value,
gguf_base_path: $("sa_runner_gguf_base")?.value,
gguf_cmd: $("sa_runner_gguf_cmd")?.value
});
setTrainStatus("\u0420\u0430\u043D\u043D\u0435\u0440 \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D");
} catch (e) {
setTrainStatus(String(e.message || e));
}
}
async function loadRunner() {
try {
const data = await SA2.request("AssistentGetRunnerSettings", {});
const s = data?.settings || {};
if ($("sa_runner_python") && s.python) $("sa_runner_python").value = s.python;
if ($("sa_runner_kind")) $("sa_runner_kind").value = s.kind || "builtin";
if ($("sa_runner_workdir") && s.workdir) $("sa_runner_workdir").value = s.workdir;
if ($("sa_runner_cmd") && s.cmd) $("sa_runner_cmd").value = s.cmd;
if ($("sa_runner_gguf_script") && s.gguf_script) $("sa_runner_gguf_script").value = s.gguf_script;
if ($("sa_runner_gguf_base") && s.gguf_base_path) $("sa_runner_gguf_base").value = s.gguf_base_path;
if ($("sa_runner_gguf_cmd") && s.gguf_cmd) $("sa_runner_gguf_cmd").value = s.gguf_cmd;
} catch (e) {
}
}
function wireTraining() {
if (window.__saTrainingWired) return;
window.__saTrainingWired = true;
document.querySelectorAll("#sa_training .sa-ttab").forEach((btn) => {
btn.addEventListener("click", () => setTrainingTab(btn.getAttribute("data-ttab")));
});
$("sa_btn_agent_sync")?.addEventListener("click", syncAllToAgent);
$("sa_agent_heard_enabled")?.addEventListener("change", saveAgentHeardSettings);
$("sa_agent_auto_link")?.addEventListener("change", saveAgentHeardSettings);
$("sa_agent_heard_quota")?.addEventListener("change", saveAgentHeardSettings);
loadAgentHeardSettings();
$("sa_btn_train_from_chats")?.addEventListener("click", async () => {
try {
const data = await SA2.request("AssistentBuildDatasetFromChats", {});
setTrainStatus(`\u0418\u0437 \u0447\u0430\u0442\u043E\u0432: +${data.added}`);
await refreshSamples();
} catch (e) {
setTrainStatus(String(e.message || e));
}
});
$("sa_btn_train_import_file")?.addEventListener("click", () => $("sa_train_import_file")?.click());
$("sa_train_import_file")?.addEventListener("change", async (e) => {
const file = e.target?.files?.[0];
if (!file) return;
const text = await file.text();
try {
const data = await SA2.request("AssistentImportDataset", { format: "auto", content: text });
setTrainStatus(`\u0418\u043C\u043F\u043E\u0440\u0442: ${data.imported}`);
await refreshSamples();
} catch (err) {
setTrainStatus(String(err.message || err));
}
e.target.value = "";
});
$("sa_btn_train_export")?.addEventListener("click", async () => {
try {
const data = await SA2.request("AssistentExportDataset", { status: "approved" });
if (data.content) {
const blob = new Blob([data.content], { type: "application/jsonl" });
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = "assistent-dataset.jsonl";
a.click();
}
setTrainStatus(`\u042D\u043A\u0441\u043F\u043E\u0440\u0442: ${data.count} \u043F\u0440\u0438\u043C\u0435\u0440\u043E\u0432`);
} catch (e) {
setTrainStatus(String(e.message || e));
}
});
$("sa_train_filter_status")?.addEventListener("change", refreshSamples);
$("sa_train_filter_persona")?.addEventListener("change", refreshSamples);
$("sa_train_samples")?.addEventListener("click", async (e) => {
const row = e.target.closest(".sa-train-sample");
if (!row) return;
const id = row.dataset.id;
const sample = state.samples.find((s) => s.id === id);
if (!sample) return;
if (e.target.closest("[data-approve]")) {
await upsertSample({ ...sample, status: "approved" });
await loadAgentHeardSettings();
} else if (e.target.closest("[data-reject]")) {
await upsertSample({ ...sample, status: "rejected" });
await loadAgentHeardSettings();
} else if (e.target.closest("[data-link]")) {
try {
const data = await SA2.request("AssistentLinkTrainSampleToAgent", { id });
state.agentLinked = data?.linked ?? state.agentLinked;
setAgentHeardStats(state.agentLinked);
setTrainStatus("\u041F\u0440\u0438\u043C\u0435\u0440 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D \u043A \u0430\u0433\u0435\u043D\u0442\u0443");
await refreshSamples();
} catch (err) {
setTrainStatus(String(err.message || err));
}
} else if (e.target.closest("[data-unlink]")) {
try {
const data = await SA2.request("AssistentUnlinkTrainSampleFromAgent", { id });
state.agentLinked = data?.linked ?? state.agentLinked;
setAgentHeardStats(state.agentLinked);
setTrainStatus("\u041F\u0440\u0438\u043C\u0435\u0440 \u043E\u0442\u043A\u043B\u044E\u0447\u0451\u043D \u043E\u0442 \u0430\u0433\u0435\u043D\u0442\u0430");
await refreshSamples();
} catch (err) {
setTrainStatus(String(err.message || err));
}
} else if (e.target.closest("[data-del]")) {
if (window.confirm("\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u043F\u0440\u0438\u043C\u0435\u0440?")) {
await SA2.request("AssistentDeleteTrainSample", { id });
await refreshSamples();
}
}
});
$("sa_btn_hf_search")?.addEventListener("click", searchHf);
$("sa_hf_show_all")?.addEventListener("change", () => {
renderHfList();
});
$("sa_hf_list")?.addEventListener("click", async (e) => {
const row = e.target.closest(".sa-hf-row");
if (!row || row.classList.contains("sa-hf-rejected")) return;
state.hfSelected = row.dataset.id;
$("sa_hf_link").value = row.dataset.id;
renderHfList();
await checkHfLink();
});
$("sa_btn_hf_check")?.addEventListener("click", checkHfLink);
$("sa_hf_mapping_preset")?.addEventListener("change", () => {
state.hfMapping = buildHfMappingPayload();
});
$("sa_btn_hf_import")?.addEventListener("click", importHf);
document.querySelectorAll('input[name="sa_train_mode"]').forEach((r) => {
r.addEventListener("change", () => setTrainMode(r.value));
});
$("sa_btn_modelfile_create")?.addEventListener("click", createModelfile);
$("sa_btn_qlora_start")?.addEventListener("click", startQlora);
$("sa_btn_qlora_cancel")?.addEventListener("click", cancelQlora);
$("sa_btn_train_models_refresh")?.addEventListener("click", refreshTrainModels);
$("sa_btn_save_runner")?.addEventListener("click", saveRunner);
loadRunner();
setTrainMode("modelfile");
}
SA2.training = {
render() {
wireTraining();
setTrainingTab(state.ttab);
},
async curateFromChat(messages, meta) {
try {
await SA2.request("AssistentUpsertTrainSample", {
source: "chat",
chat_id: meta?.chatId,
persona: meta?.persona,
pack: meta?.pack,
status: meta?.status || "approved",
messages
});
return true;
} catch (e) {
console.warn("curateFromChat", e);
return false;
}
},
setTrainingLock,
pollTrainJob
};
}
// src/main.js
window.SA = window.SA || {};
attachApi(window.SA);
attachPatch(window.SA);
attachPersist(window.SA);
attachSession(window.SA);
attachContext(window.SA);
attachActivity(window.SA);
window.SA.applyConfigPatchKeys = function(config) {
const keys = config?.patch_keys;
if (Array.isArray(keys) && keys.length) {
setPatchKeys(keys);
window.SA.PATCH_KEYS = keys;
}
};
attachTraining(window.SA);
})();