Files
swarm-assistent/Assets/assistent.bundle.js
T
Leonid PershinandCursor 474a674a35 Ship Assistent 0.13.0: real QLoRA pipeline and GGUF Ollama register.
Replace fake train loop with TRL SFTTrainer, HF column mapping with fiction preset,
safetensors to GGUF conversion, and ollama create using ollama_base plus ADAPTER.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-22 14:45:07 +03:00

9793 lines
389 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",
"search_query",
"civitai_query",
"use_init_image",
"clear_init_image",
"init_creativity",
"denoise",
"use_mask_image",
"clear_mask_image",
"mask_blur",
"mask_grow",
"look_at",
"vision_from",
"vision_slots",
"slot_to_init",
"slot_to_mask",
"snapshot_generate",
"select_slot",
"aspect",
"images",
"batch",
"vary",
"lock_seed",
"creativity",
"intensity",
"complexity",
"movement",
"clear_prompt_images",
"slot_to_prompt_image",
"pack",
"memories",
"memory",
"memory_query",
"memory_kind",
"tag_query",
"user_prefs",
"inventory_query",
"skills",
"persona_shelves",
"persona_clone",
"persona",
"controls",
"variants"
];
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 isCardObject(obj) {
if (!obj || typeof obj !== "object") {
return false;
}
const cardish = !!(obj.kind || obj.triggers || obj.when || obj.prompt_hint);
const genish = !!(obj.prompt != null || obj.negative != null || obj.loras || obj.actions || obj.width || obj.height || obj.steps != null || obj.cfg != null || obj.aspect || obj.seed != null || obj.search_query || obj.civitai_query || obj.look_at || obj.controls);
if (cardish && !genish && (obj.name || obj.triggers || obj.when)) {
return true;
}
return !!(obj.kind && obj.name && (obj.triggers || obj.when || obj.prompt_hint || obj.notes != null));
}
function isPatchObject2(obj) {
if (!obj || typeof obj !== "object") {
return false;
}
if (isCardObject(obj)) {
return false;
}
return PATCH_KEYS.some((k) => has(obj, k));
}
function normalizePatch(patch) {
if (!patch || typeof patch !== "object") {
return patch;
}
if (!has(patch, "search_query") && has(patch, "civitai_query")) {
patch.search_query = patch.civitai_query;
}
if (!has(patch, "init_creativity") && has(patch, "denoise")) {
patch.init_creativity = patch.denoise;
}
if (!has(patch, "look_at")) {
if (has(patch, "vision_from")) {
patch.look_at = patch.vision_from;
} else if (has(patch, "vision_slots")) {
patch.look_at = patch.vision_slots;
}
}
return patch;
}
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 (isCardObject(obj)) {
return true;
}
if (Array.isArray(obj.variants) && obj.variants.length) {
return true;
}
if (obj.look_at != null || obj.vision_from != null || obj.vision_slots != null) {
return true;
}
if (obj.search_query != null || obj.civitai_query != null || obj.memory_query != null || obj.tag_query != null || obj.inventory_query != null) {
return true;
}
const acts = Array.isArray(obj.actions) ? obj.actions.map(String) : [];
const hopOrGen = [
"skill_load",
"persona_read",
"memory_get",
"memory_search",
"lookup_tags",
"list_inventory",
"search_civitai",
"interrupt",
"generate",
"memory_upsert",
"user_pref_upsert"
];
if (acts.some((a) => hopOrGen.includes(a))) {
return true;
}
if (String(obj.prompt || "").trim().length >= 48) {
return true;
}
if (obj.loras != null || obj.aspect != null || obj.steps != null || obj.width != null || obj.height != null || obj.cfg != null || obj.seed != null || obj.controls != null || obj.memories != null || obj.user_prefs != null) {
return true;
}
return false;
}
function attachPatch(SA2) {
SA2.PATCH_KEYS = PATCH_KEYS;
SA2.setPatchKeys = setPatchKeys;
SA2.isCardObject = isCardObject;
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/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 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",
card: "catalog_card",
catalog: "catalog_card",
catalog_card: "catalog_card"
};
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
/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|card
/civitai <query> \u2014 \u043F\u043E\u0438\u0441\u043A LoRA (Confirm \u0432 \u0447\u0430\u0442\u0435)
/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: "/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: "/civitai ", hint: "\u0437\u0430\u043F\u0440\u043E\u0441 LoRA" },
{ 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,
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: [],
modelCards: {},
cardsSelection: 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,
wanted: { count: 0, items: [] },
wantedKeys: /* @__PURE__ */ new Set(),
ollamaHealth: "unknown",
trainingLock: false
};
const HOP_BUDGET = 4;
function isContinuationTurn(opts) {
return !!(opts && (opts.fromVisionHop || opts.fromAutoCritique || opts.fromPromptEnRetry || opts.fromEmptyPatchRetry));
}
function isMachineTurn(opts) {
return isContinuationTurn(opts) || !!(opts && (opts.fromCards || opts.fromDownload || 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: "Civitai search done \u2014 refining\u2026"
};
const text = labels[state.busyPhase] || "Working\u2026";
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";
$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 userAsksGenerate(text) {
const t = String(text || "").trim();
if (!t) {
return false;
}
if (/^(gen|generate|go|рисуй|нарисуй)([!.…\s]|$)/i.test(t)) {
return true;
}
if (/^(ещё|еще)(\s+раз)?([!.…\s]|$)/i.test(t)) {
return true;
}
const letter = "[0-9A-Za-z_\u0410-\u042F\u0430-\u044F\u0401\u0451]";
const stem = `${letter}*`;
return cyrTokenRe(
`\u0441\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0439|\u0441\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C|\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0439|generate|\u043D\u0430\u0440\u0438\u0441\u0443\u0439|\u043F\u0435\u0440\u0435\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0439|\u043F\u0435\u0440\u0435\u0440\u0438\u0441\u0443\u0439|\u0441\u0434\u0435\u043B\u0430\u0439\\s+(\u043A\u0430\u0440\u0442\u0438\u043D\u043A${stem}|\u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D${stem}|\u0444\u043E\u0442\u043E${stem})|\u0445\u043E\u0447\u0443\\s+(\u043A\u0430\u0440\u0442\u0438\u043D\u043A${stem}|\u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D${stem}|\u0444\u043E\u0442\u043E${stem})|run\\s+generat|/gen`
).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 synthesizePatchAfterEmptyFence(reply, userText, opts = {}) {
const wantsFrame = !!opts.userWantsGenerate || !isMachineTurn(opts) && userImpliesGenerate(userText);
if (!wantsFrame && !replyMissingJsonPatch(reply)) {
return null;
}
const prompt = extractPromptFromProse(reply) || state.lastPatch?.prompt || null;
if (!prompt) {
return null;
}
const patch = wantsFrame ? { prompt, actions: ["generate"] } : { prompt };
if (Array.isArray(state.lastPatch?.loras) && state.lastPatch.loras.length) {
patch.loras = state.lastPatch.loras;
}
if (state.lastPatch?.aspect) {
patch.aspect = state.lastPatch.aspect;
}
return patch;
}
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 || userAsksGenerate(t)) {
return false;
}
if (/\b(remember|save\s+(this\s+)?(as\s+)?(the\s+)?(base\s+)?(prompt|template)|don'?t\s+generat|do\s+not\s+generat|no\s+generat|without\s+generat)\b/i.test(t)) {
return true;
}
return cyrTokenRe(
"\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|\u0431\u0435\u0437\\s+\u0433\u0435\u043D\u0435\u0440\u0430\u0446|\u043D\u0435\\s+\u043D\u0430\u0434\u043E\\s+\u0433\u0435\u043D\u0435\u0440|\u0442\u043E\u043B\u044C\u043A\u043E\\s+\u0437\u0430\u043F\u043E\u043C\u043D|\u043F\u043E\u043A\u0430\\s+\u0437\u0430\u043F\u043E\u043C\u043D|\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"
).test(t);
}
function userCommandsGenerate(text) {
const t = String(text || "").trim();
if (!t || userAsksNoGenerate(t)) {
return false;
}
return userAsksGenerate(t) || userAsksContinue(t) || isSameButAspectRequest(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 userIsChatNotFrame(text) {
const t = String(text || "").trim();
if (!t) {
return false;
}
if (/^(ок|окей|ok|okay|ладно|хорошо|понял|ясно|спасибо|thanks)([!.…\s]*)$/i.test(t)) {
return true;
}
if (cyrTokenRe(
"\u0447\u0442\u043E\\s+\u0442\u0430\u043A\u043E\u0435|\u043A\u0430\u043A\\s+\u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442|\u0437\u0430\u0447\u0435\u043C\\s+|\u043A\u0430\u043A\u0438\u0435\\s+(\u043B\u043E\u0440|\u043C\u043E\u0434\u0435\u043B|\u0447\u0435\u043A\u043F\u043E\u0438\u043D\u0442)|\u0441\u043F\u0438\u0441\u043E\u043A\\s+\u043B\u043E\u0440|\u0433\u0434\u0435\\s+\u043D\u0430\u0441\u0442\u0440\u043E\u0439|\u0447\u0442\u043E\\s+\u0437\u043D\u0430\u0447\u0438\u0442|\u043D\u0440\u0430\u0432\u0438\u0442|\u0441\u043F\u0430\u0441\u0438\u0431\u043E|\u0431\u043B\u0430\u0433\u043E\u0434\u0430\u0440|\u043F\u043E\u0447\u0435\u043C\u0443\\s+\u0442\u0430\u043A|\u0447\u0442\u043E\\s+\u0442\u044B\\s+(\u0441\u0434\u0435\u043B\u0430\u043B|\u0438\u0437\u043C\u0435\u043D\u0438\u043B)|\u0442\u043E\u043B\u044C\u043A\u043E\\s+(\u043E\u0442\u0432\u0435\u0442\u044C|\u0441\u043A\u0430\u0436\u0438|\u043E\u0431\u044A\u044F\u0441\u043D\u0438)|\u0431\u0435\u0437\\s+(\u043A\u0430\u0434\u0440|\u0433\u0435\u043D\u0435\u0440\u0430\u0446)|\u043D\u0435\\s+\u043D\u0430\u0434\u043E\\s+\u043A\u0430\u0434\u0440"
).test(t) && !userAsksGenerate(t) && !userAsksContinue(t)) {
return true;
}
return false;
}
function userImpliesGenerate(text) {
const t = String(text || "").trim();
if (!t || userAsksNoGenerate(t) || userIsChatNotFrame(t)) {
return false;
}
if (userCommandsGenerate(t)) {
return true;
}
if (t.length < 8) {
return false;
}
const wantsLook = userAsksLook(t);
const wantsRedraw = cyrTokenRe("\u043F\u043E\u043F\u0440\u0430\u0432\u044C|\u0438\u0441\u043F\u0440\u0430\u0432\u044C|\u043F\u0435\u0440\u0435\u0433\u0435\u043D\u0435\u0440\u0438\u0440|\u043F\u0435\u0440\u0435\u0440\u0438\u0441\u0443\u0439|\u0443\u043B\u0443\u0447\u0448\u0438|\u043F\u0435\u0440\u0435\u0434\u0435\u043B\u0430\u0439").test(t) || /\b(fix|redo|redraw|improve)\b/i.test(t);
if (wantsLook && !wantsRedraw) {
return false;
}
if (cyrTokenRe(
"\u043D\u0430\u0440\u0438\u0441\u0443|\u0441\u0433\u0435\u043D\u0435\u0440|\u043F\u0435\u0440\u0435\u0440\u0438\u0441\u0443|\u0441\u0434\u0435\u043B\u0430\u0439\\s+(\u043A\u0430\u0440\u0442\u0438\u043D\u043A|\u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D|\u0444\u043E\u0442\u043E|\u043A\u0430\u0434\u0440)|\u0445\u043E\u0447\u0443\\s+(\u043A\u0430\u0440\u0442\u0438\u043D\u043A|\u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D|\u0444\u043E\u0442\u043E|\u0443\u0432\u0438\u0434\u0435\u0442\u044C|\u0432\u0438\u0434\u0435\u0442\u044C)|\u043F\u043E\u043A\u0430\u0436\u0438\\s+\u043A\u0430\u043A\\s+(\u043E\u043D\u0430|\u043E\u043D|\u044D\u0442\u043E)|\u0441\u0434\u0435\u043B\u0430\u0439\\s+(\u0435\u0451|\u0435\u0435|\u0435\u0433\u043E|\u043C\u043D\u0435)\\s|\u043F\u0443\u0441\u0442\u044C\\s+\u0431\u0443\u0434\u0435\u0442|\u0434\u0440\u0443\u0433\u043E\u0439\\s+(\u0440\u0430\u043A\u0443\u0440\u0441|\u0441\u0432\u0435\u0442|\u043D\u0430\u0440\u044F\u0434|\u043F\u043E\u0437\u0430)|\u043F\u043E\u043C\u0435\u043D\u044F\u0439\\s+(\u043F\u043E\u0437\u0443|\u0441\u0432\u0435\u0442|\u043E\u0434\u0435\u0436\u0434|\u0444\u043E\u043D)|\u0434\u043E\u0431\u0430\u0432\u044C\\s+(\u0441\u0432\u0435\u0442|\u0434\u0435\u0442\u0430\u043B)|\u0435\u0449\u0451\\s+\u043E\u0434\u043D|\u0435\u0449\u0435\\s+\u043E\u0434\u043D"
).test(t)) {
return true;
}
if (/\b(draw|paint|render|make her|make him|another one|new frame)\b/i.test(t)) {
return true;
}
const isQuestion = /[?]\s*$/.test(t);
if (isQuestion) {
return cyrTokenRe("\u043D\u0430\u0440\u0438\u0441\u0443|\u0441\u0433\u0435\u043D\u0435\u0440|\u043C\u043E\u0436\u0435\u0448\u044C\\s+(\u0441\u0434\u0435\u043B\u0430\u0442\u044C|\u043D\u0430\u0440\u0438\u0441\u043E\u0432\u0430\u0442\u044C)|\u043C\u043E\u0436\u043D\u043E\\s+(\u043A\u0430\u0440\u0442\u0438\u043D\u043A|\u0441\u0433\u0435\u043D\u0435\u0440)").test(t);
}
return false;
}
function packBlocksAutoGenerate(pack) {
const p = String(pack || "");
return p === "describe_ref" || p === "catalog_card" || p === "author_persona" || p === "debug_explain";
}
function packWantsVision(pack) {
const p = String(pack || "");
return p === "critique_image" || p === "describe_ref" || p === "compose_scene" || p === "inpaint_edit";
}
function resolveTurnIntent(patch, userText, opts = {}) {
const machine = isMachineTurn(opts);
const vetoed = !machine && userAsksNoGenerate(userText);
const commanded = !!opts.userWantsGenerate || !machine && userCommandsGenerate(userText);
const implied = !machine && userImpliesGenerate(userText);
const modelAsked = Array.isArray(patch?.actions) && patch.actions.map(String).includes("generate");
let generate;
if (vetoed || opts.fromAutoCritique) {
generate = false;
} else if (commanded) {
generate = true;
} else if (packBlocksAutoGenerate($2("sa_pack")?.value || "")) {
generate = false;
} else {
generate = modelAsked || implied;
}
const hasLook = !!patch && (patch.look_at != null || patch.vision_from != null || patch.vision_slots != null);
const honorLook = opts.fromAutoCritique || opts.fromVisionHop || !machine && userAsksLook(userText) || packWantsVision($2("sa_pack")?.value);
const look = !!(hasLook && !vetoed && !generate && honorLook);
return { generate, look, vetoed };
}
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" && !isCardObject2(patch)) {
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",
catalog_card: "card",
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 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") || !!state.pendingSilentGen;
note.textContent = willGen ? "\u041F\u0440\u0438\u043C\u0435\u043D\u0435\u043D\u043E \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438 \xB7 Generate\u2026" : "\u041F\u0440\u0438\u043C\u0435\u043D\u0435\u043D\u043E \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438";
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 genBtn = document.createElement("button");
genBtn.type = "button";
genBtn.className = "basic-button sa-btn-gen";
genBtn.textContent = "\u041F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C + Generate";
genBtn.addEventListener("click", async () => {
if (isGenerateUnavailable()) {
return;
}
startBusyUi("silent_gen");
await applyPatch(patch, "all");
await runGenerateFromPatch({ ...patch, actions: ["generate"] }, { force: 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 (isGenerateUnavailable()) {
setStatus("Generate \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u2014 \u0434\u043E\u0436\u0434\u0438\u0441\u044C SwarmUI");
return;
}
const patch = state.lastPatch;
if (patch) {
startBusyUi("silent_gen");
setStatus("\u0421\u043E\u0431\u0438\u0440\u0430\u044E \u043F\u0430\u0442\u0447 \u2192 Generate\u2026");
await applyPatch(patch, "all");
syncLiveParamsBar();
await runGenerateFromPatch({ ...patch, actions: ["generate"] }, { force: true });
return;
}
startBusyUi("generating");
setStatus("Generate \u0441 \u0442\u0435\u043A\u0443\u0449\u0438\u043C \u043F\u0440\u043E\u043C\u043F\u0442\u043E\u043C\u2026");
await runGenerateFromPatch({ actions: ["generate"] }, { force: 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 snapshotChatParams() {
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 lastPatch = null;
try {
lastPatch = state.lastPatch ? JSON.parse(JSON.stringify(state.lastPatch)) : null;
} catch (e) {
lastPatch = null;
}
let sessionExact = {};
try {
sessionExact = state.sessionExact && typeof state.sessionExact === "object" ? JSON.parse(JSON.stringify(state.sessionExact)) : {};
} catch (e) {
sessionExact = {};
}
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,
persona: $2("sa_persona")?.value || "neutral",
pack: $2("sa_pack")?.value || defaultPackId(),
sessionExact,
lastPatch,
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
};
}
async function restoreChatParams(params) {
state.restoringChat = true;
try {
state.sessionExact = {};
state.lastPatch = null;
state.lastUserParamIntent = false;
if (!params || typeof params !== "object") {
clearGenResults();
syncBuildGenButton();
syncLiveParamsBar();
syncModeBadge();
renderBoard();
return { restored: false };
}
const promptBox = document.getElementById("alt_prompt_textbox") || document.getElementById("input_prompt");
if (promptBox) {
promptBox.value = params.prompt != null ? String(params.prompt) : "";
promptBox.dispatchEvent(new Event("input", { bubbles: true }));
promptBox.dispatchEvent(new Event("change", { bubbles: true }));
}
setVal("input_negativeprompt", params.negative != null ? String(params.negative) : "");
if (document.getElementById("alt_negativeprompt_textbox")) {
setVal("alt_negativeprompt_textbox", params.negative != null ? String(params.negative) : "");
}
if (params.width != null) {
setVal("input_width", String(params.width));
}
if (params.height != null) {
setVal("input_height", String(params.height));
}
if (params.steps != null) {
setVal("input_steps", String(params.steps));
}
if (params.cfg != null) {
if (document.getElementById("input_cfgscale")) {
setVal("input_cfgscale", String(params.cfg));
} else {
setVal("input_cfg", String(params.cfg));
}
}
if (params.sigma_shift != null) {
setVal("input_sigmashift", String(params.sigma_shift));
}
if (params.seed != null && params.seed !== "") {
setVal("input_seed", String(params.seed));
}
if (params.sampler) {
setVal("input_sampler", String(params.sampler));
}
if (params.scheduler) {
setVal("input_scheduler", String(params.scheduler));
}
if (params.batch != null) {
if (document.getElementById("input_images")) {
setVal("input_images", String(params.batch));
} else if (document.getElementById("input_batchsize")) {
setVal("input_batchsize", String(params.batch));
}
}
const loras = Array.isArray(params.loras) ? params.loras : [];
await applyPatch({ loras }, "loras");
if (params.pack) {
setPackValue(params.pack, { flash: false });
}
if (params.persona) {
await applyPersonaForChat(params.persona, { quiet: true });
}
state.sessionExact = params.sessionExact && typeof params.sessionExact === "object" ? { ...params.sessionExact } : {};
state.lastPatch = params.lastPatch || null;
if (Array.isArray(params.genResults) && params.genResults.length) {
state.genResults = params.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 = params.selectedGenResultId || state.genResults.find((r) => r.src)?.id || state.genResults[0]?.id || null;
const selected = state.genResults.find((r) => r.id === state.selectedGenResultId);
const gen = generateSlot();
if (gen && selected?.src) {
gen.src = selected.src;
}
} else {
clearGenResults();
}
syncBuildGenButton();
syncLiveParamsBar();
syncModeBadge();
renderLoraChips();
syncChipHighlight();
renderBoard();
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();
return;
}
for (const m of list) {
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
});
}
}
}
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;
}
syncBuildGenButton();
resetMessagesUi();
persistChatsStore();
updateSessionLabel();
renderBoard();
syncHistoryBadge();
renderChatsList();
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;
}
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();
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();
}
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);
}
}
});
}
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() {
const inv = state.inventory || {};
const initCtx = readInitContext();
const ctx = {
architecture_ok: isKreaSelected(),
checkpoint: null,
prompt: slimPromptForContext(val("alt_prompt_textbox") || val("input_prompt") || ""),
negative: slimPromptForContext(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") || val("input_samplerate") || null,
scheduler: val("input_scheduler") || null,
batch: parseInt(val("input_images") || val("input_batchsize") || "0", 10) || null,
prompt_image_count: countPromptImages(),
selected_loras: [],
available_loras: [],
available_checkpoints: slimInventoryCheckpoints(inv.checkpoints || [], 8),
wildcards: (inv.wildcards || []).map((w) => w.name || w).slice(0, 20),
inventory_at: inv.inventory_at || null,
has_vision_image: visionReadySlots().length > 0,
image_slots: slotCatalog(),
attached_slot_ids: attachableSlots().map((s) => s.id),
gen_results: (state.genResults || []).map((r) => ({
id: r.id,
label: r.label,
has_image: !!r.src,
selected: r.id === state.selectedGenResultId
})),
selected_gen_result: state.selectedGenResultId || null,
has_civitai_key: !!inv.has_civitai_key,
auto_apply: !!$2("sa_auto_apply")?.checked,
auto_generate: !!$2("sa_auto_generate")?.checked,
persona: $2("sa_persona")?.value || localStorage.getItem(LS_PERSONA) || "neutral",
model_cards: [],
user_prefs_count: 0,
...initCtx
};
{
const slim = slimInventoryLoras(inv.loras || [], INVENTORY_PROMPT_NAMES);
ctx.available_loras = slim;
if ((inv.loras || []).length > slim.length) {
ctx.available_loras_truncated = true;
ctx.available_loras_total = (inv.loras || []).length;
}
}
try {
const model = resolveCurrentCheckpoint();
if (model.name || model.architecture) {
ctx.checkpoint = {
name: model.name || model.title || null,
architecture: model.architecture || model.compat_class || model.class || null,
title: model.title || null
};
}
} catch (e) {
}
try {
if (typeof loraHelper !== "undefined" && loraHelper && Array.isArray(loraHelper.selected)) {
const byName = /* @__PURE__ */ new Map();
for (const l of inv.loras || []) {
if (l?.name) {
byName.set(String(l.name).toLowerCase(), l);
}
}
ctx.selected_loras = loraHelper.selected.map((l) => {
const name = l.name || l;
const invRow = byName.get(String(name).toLowerCase()) || {};
const out = {
name,
weight: loraHelper.loraWeightPref && loraHelper.loraWeightPref[name] || invRow.default_weight || 1
};
if (invRow.trigger_phrase) {
out.trigger_phrase = invRow.trigger_phrase;
}
if (Array.isArray(invRow.triggers) && invRow.triggers.length) {
out.triggers = invRow.triggers.slice(0, 8);
}
if (invRow.blurb) {
out.blurb = invRow.blurb;
}
return out;
});
}
} catch (e) {
}
const cardKeys = [];
const seenCard = /* @__PURE__ */ new Set();
const addKey = (kind, name) => {
if (!kind || !name) {
return;
}
const key = `${kind}:${name}`;
if (seenCard.has(key)) {
return;
}
seenCard.add(key);
cardKeys.push({ kind, name });
};
if (ctx.checkpoint?.name) {
addKey("checkpoint", ctx.checkpoint.name);
}
for (const l of ctx.selected_loras || []) {
if (l?.name) {
addKey("lora", l.name);
}
}
for (const k of cardKeys) {
const cached = state.modelCards[`${k.kind}:${k.name}`];
if (!cached) {
continue;
}
const slim = slimCardForContext(cached);
if (!slim) {
continue;
}
if (k.kind === "lora") {
const sel = (ctx.selected_loras || []).find(
(l) => String(l.name || "").toLowerCase() === String(k.name).toLowerCase()
);
const inventoryRich = !!(sel && (sel.triggers?.length || sel.trigger_phrase || sel.blurb));
const cardExtra = !!(slim.when || slim.avoid || slim.prompt_hint || slim.notes);
if (inventoryRich && !cardExtra) {
continue;
}
}
ctx.model_cards.push(slim);
}
if (!ctx.available_loras.length) {
try {
const models = typeof allModels !== "undefined" && allModels || typeof model_list !== "undefined" && model_list || [];
const list = Array.isArray(models) ? models : Object.values(models || {});
for (const m of list) {
if (!m) {
continue;
}
const folder = `${m.folder || m.path || ""}`;
const isLora = /lora/i.test(m.category || m.type || "") || m.name && String(m.name).toLowerCase().includes("lora");
const inLoraFolder = /lora/i.test(folder);
if (!(isLora || inLoraFolder)) {
continue;
}
ctx.available_loras.push({
name: m.name || m.title,
title: m.title || m.name,
trigger_phrase: m.trigger_phrase || m.trigger || m.metadata && (m.metadata.trigger_phrase || m.metadata.trigger) || null,
architecture: m.architecture || null
});
}
if (ctx.available_loras.length > INVENTORY_PROMPT_NAMES) {
ctx.available_loras = ctx.available_loras.slice(0, INVENTORY_PROMPT_NAMES);
}
} catch (e) {
}
}
try {
const model = ctx.checkpoint || {};
const blob = `${model.name || ""} ${model.title || ""}`.toLowerCase();
const hasRaw = /\braw\b/.test(blob);
const hasTurbo = /\bturbo\b/.test(blob);
const profile = hasRaw && !hasTurbo ? "raw" : "turbo";
ctx.krea_profile = profile;
const defaults = mergedGenerationDefaults(profile);
const rec = {
steps: defaults.steps ?? 8,
cfg: defaults.cfg ?? 1,
sigma_shift: defaults.sigma_shift ?? 1.15
};
if (defaults.aspect) {
rec.aspect = defaults.aspect;
}
const liveDiffers = ctx.steps != null && ctx.steps !== rec.steps || ctx.cfg != null && ctx.cfg !== rec.cfg || ctx.sigma_shift != null && ctx.sigma_shift !== rec.sigma_shift;
if (liveDiffers) {
ctx.recommended_params = rec;
}
} catch (e) {
ctx.krea_profile = "turbo";
}
ctx.session_exact = state.sessionExact && Object.keys(state.sessionExact).length ? { ...state.sessionExact } : void 0;
if (!ctx.session_exact) {
delete ctx.session_exact;
}
return ctx;
}
function slimCardForContext(card) {
if (!card || typeof card !== "object") {
return null;
}
const out = {
kind: card.kind || null,
name: card.name || null,
triggers: Array.isArray(card.triggers) ? card.triggers.slice(0, 8) : void 0,
weight: card.weight != null ? card.weight : void 0,
when: card.when ? String(card.when).slice(0, 160) : void 0,
avoid: card.avoid ? String(card.avoid).slice(0, 120) : void 0,
prompt_hint: card.prompt_hint ? String(card.prompt_hint).slice(0, 160) : void 0,
notes: card.notes ? String(card.notes).slice(0, 200) : void 0
};
const clean = {};
for (const [k, v] of Object.entries(out)) {
if (v != null && v !== "") {
clean[k] = v;
}
}
return clean;
}
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 isCardObject2(obj) {
if (window.SA && typeof SA.isCardObject === "function") {
return SA.isCardObject(obj);
}
if (!obj || typeof obj !== "object") {
return false;
}
const cardish = !!(obj.kind || obj.triggers || obj.when || obj.prompt_hint);
const genish = !!(obj.prompt != null || obj.loras || obj.actions || obj.width || obj.height || obj.steps != null || obj.cfg != null || obj.aspect || obj.seed != null || obj.search_query || obj.civitai_query || obj.look_at || obj.controls);
if (cardish && !genish && (obj.name || obj.triggers || obj.when)) {
return true;
}
return !!(obj.kind && obj.name && (obj.triggers || obj.when || obj.prompt_hint || obj.notes != null));
}
function extractCardJson(text) {
if (!text) {
return null;
}
const re = /```(?:json)?\s*([\s\S]*?)```/gi;
let match;
let last = null;
while ((match = re.exec(text)) !== null) {
try {
const obj = JSON.parse(match[1].trim());
if (isCardObject2(obj)) {
last = obj;
}
} catch (e) {
}
}
if (last) {
return last;
}
try {
const obj = JSON.parse(text.trim());
return isCardObject2(obj) ? obj : null;
} catch (e) {
return null;
}
}
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 = {}) {
const force = !!opts.force;
if (!force && !$2("sa_auto_generate")?.checked || !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 (civitaiResults && civitaiResults.length) {
div.appendChild(buildCivitaiCards(civitaiResults));
}
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) : isPatchObject(obj) || isCardObject2(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) : isPatchObject(obj) || isCardObject2(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 card = extractCardJson(fullReply);
const { prose, patch } = extractPatch2(fullReply);
setAssistantBody(el, prose || fullReply || "");
el.querySelectorAll(".sa-patch, .sa-civitai-list").forEach((n) => n.remove());
if (patch && !isCardObject2(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 (civitaiResults && civitaiResults.length) {
el.appendChild(buildCivitaiCards(civitaiResults));
}
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 buildCivitaiCards(results) {
const list = document.createElement("div");
list.className = "sa-civitai-list";
for (const r of results) {
const card = document.createElement("div");
card.className = "sa-civitai-card" + (r.already_installed ? " sa-installed" : "");
const title = document.createElement("div");
title.className = "sa-civitai-title";
title.textContent = r.name || r.file_name || "LoRA";
card.appendChild(title);
const meta = document.createElement("div");
meta.className = "sa-civitai-meta";
const bits = [
r.base_model || "?",
r.krea_likely ? "Krea?" : null,
r.already_installed ? "already installed" : null,
(r.triggers || []).slice(0, 3).join(", ") || null
].filter(Boolean);
meta.textContent = bits.join(" \xB7 ");
card.appendChild(meta);
const actions = document.createElement("div");
actions.className = "sa-civitai-actions";
if (r.already_installed) {
const note = document.createElement("span");
note.textContent = "\u0423\u0436\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430";
actions.appendChild(note);
} else if (r.download_url) {
const btn = document.createElement("button");
btn.type = "button";
btn.className = "basic-button sa-primary";
btn.textContent = "\u041F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044C \u0441\u043A\u0430\u0447\u0438\u0432\u0430\u043D\u0438\u0435";
btn.addEventListener("click", () => downloadCivitaiLoRA(r, btn));
actions.appendChild(btn);
} else {
const note = document.createElement("span");
note.textContent = "\u041D\u0435\u0442 URL \u0441\u043A\u0430\u0447\u0438\u0432\u0430\u043D\u0438\u044F";
actions.appendChild(note);
}
card.appendChild(actions);
list.appendChild(card);
}
return list;
}
function downloadCivitaiLoRA(card, btn) {
if (!card.download_url) {
return;
}
if (btn) {
btn.disabled = true;
btn.textContent = "\u0421\u043A\u0430\u0447\u0438\u0432\u0430\u044E\u2026";
}
setStatus(`\u0421\u043A\u0430\u0447\u0438\u0432\u0430\u044E ${card.file_name || card.name}\u2026`);
setInterruptVisible(true);
const payload = {
url: card.download_url,
type: "LoRA",
name: card.file_name || card.name || "lora"
};
const onDone = (ok, msg) => {
setInterruptVisible(state.busy || state.generating);
if (ok) {
setStatus(`\u0421\u043A\u0430\u0447\u0430\u043D\u043E ${payload.name}`);
if (btn) {
btn.textContent = "\u0421\u043A\u0430\u0447\u0430\u043D\u043E";
}
refreshInventory(async () => {
await maybeWriteCardAfterDownload({
kind: "lora",
name: payload.name,
civitai: card
});
}, { rescan: true });
} else {
setStatus(msg || "\u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u043A\u0430\u0447\u0438\u0432\u0430\u043D\u0438\u044F");
if (btn) {
btn.disabled = false;
btn.textContent = "\u041F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044C \u0441\u043A\u0430\u0447\u0438\u0432\u0430\u043D\u0438\u0435";
}
appendMessage("error", msg || "\u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u043A\u0430\u0447\u0438\u0432\u0430\u043D\u0438\u044F");
}
};
if (typeof makeWSRequest === "function") {
makeWSRequest(
"DoModelDownloadWS",
payload,
(data) => {
if (data.error) {
onDone(false, String(data.error));
return;
}
if (data.success || data.overall_percent >= 1 || data.current_percent >= 1) {
if (data.success || data.overall_percent >= 0.99) {
triggerSwarmModelRefresh(() => onDone(true));
} else if (data.current_percent != null) {
setStatus(`Download ${(data.current_percent * 100).toFixed(0)}%`);
}
}
},
0,
(err) => onDone(false, String(err || "Download failed"))
);
} else {
onDone(false, "makeWSRequest unavailable");
}
}
async function maybeWriteCardAfterDownload({ kind, name, civitai }) {
const display = name || civitai?.file_name || civitai?.name || "model";
appendSystemNote(`Downloaded ${display}. Writing a recommendation card\u2026`);
setPackValue("catalog_card", { flash: true });
const meta = {
triggers: civitai?.triggers || [],
base_model: civitai?.base_model,
civitai_url: civitai?.url || civitai?.civitai_url,
version_id: civitai?.version_id || civitai?.modelVersionId,
name: display
};
if ($2("sa_input")) {
$2("sa_input").value = "";
}
await sendChat({
forcedUserText: `LoRA "${display}" is now installed. Write a recommendation card (JSON) using its triggers/metadata. Then briefly suggest how to enable it in the next generate.`,
skipSlash: true,
skipAutoPack: true,
fromDownload: true,
fromCards: true,
cardTarget: { kind: kind || "lora", name: display, meta }
});
}
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);
}
const 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" || 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: !!$2("sa_auto_generate")?.checked,
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" || 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.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);
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)" : ""}`);
prefetchActiveModelCards();
if (state.view === "cards") {
renderCardsList();
}
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();
refreshWantedQueue();
}
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_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"),
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
});
}
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 refreshWantedQueue() {
if (typeof genericRequest !== "function") {
return;
}
genericRequest(
"AssistentListWanted",
{},
(data) => {
const items = Array.isArray(data?.items) ? data.items : [];
state.wanted = { count: data?.count ?? items.length, items };
const keys = /* @__PURE__ */ new Set();
for (const item of items) {
const leaf = modelKeyLeaf(item?.title);
if (leaf) {
keys.add(leaf);
}
if (item?.version_id) {
keys.add(`v${item.version_id}`);
}
}
state.wantedKeys = keys;
const el = $2("sa_mem_wanted");
if (el) {
el.textContent = state.wanted.count ? `\u041E\u0447\u0435\u0440\u0435\u0434\u044C wanted: ${state.wanted.count} (\u0441\u043A\u0430\u0447\u0430\u0435\u0442\u0441\u044F \u043D\u0430 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u043C up)` : "\u041E\u0447\u0435\u0440\u0435\u0434\u044C wanted: \u043F\u0443\u0441\u0442\u043E";
el.title = items.slice(0, 12).map((i) => `${i.kind}: ${i.title || i.url}`).join("\n");
}
if (state.view === "cards") {
renderCardsList();
}
},
0,
() => {
const el = $2("sa_mem_wanted");
if (el) {
el.textContent = "\u041E\u0447\u0435\u0440\u0435\u0434\u044C wanted: \u2014";
}
}
);
}
function isWantedModel(row) {
const keys = state.wantedKeys;
if (!keys || !keys.size) {
return false;
}
for (const candidate of [row?.name, row?.title]) {
const leaf = modelKeyLeaf(candidate);
if (leaf && keys.has(leaf)) {
return true;
}
}
return false;
}
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 setCardStatus(msg) {
const el = $2("sa_card_status");
if (el) {
el.textContent = msg || "";
}
}
function setView(view) {
if (view === "cards") {
state.view = "cards";
} else if (view === "settings") {
state.view = "settings";
} else if (view === "train") {
state.view = "train";
} else {
state.view = "chat";
}
const chat = $2("sa_view_chat");
const cards = $2("sa_view_cards");
const settings = $2("sa_view_settings");
const train = $2("sa_view_train");
if (chat) {
chat.hidden = state.view !== "chat";
}
if (cards) {
cards.hidden = state.view !== "cards";
}
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_cards", state.view === "cards");
tabActive("sa_tab_settings", state.view === "settings");
tabActive("sa_tab_train", state.view === "train");
saveSettings();
if (state.view === "cards") {
renderCardsList();
} else 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");
}
function prefetchCard(kind, name) {
return new Promise((resolve) => {
if (!kind || !name || typeof genericRequest !== "function") {
resolve(null);
return;
}
const key = `${kind}:${name}`;
genericRequest(
"AssistentGetCard",
{ kind, name },
(data) => {
if (data?.card) {
state.modelCards[key] = data.card;
}
resolve(data?.card || null);
},
0,
() => resolve(null)
);
});
}
async function prefetchActiveModelCards() {
const keys = [];
const seen = /* @__PURE__ */ new Set();
const add = (kind, name) => {
if (!kind || !name) {
return;
}
const key = `${kind}:${name}`;
if (seen.has(key)) {
return;
}
seen.add(key);
keys.push({ kind, name });
};
try {
const ck = resolveCurrentCheckpoint();
if (ck?.name) {
add("checkpoint", ck.name);
}
} catch (e) {
}
try {
if (typeof loraHelper !== "undefined" && Array.isArray(loraHelper?.selected)) {
for (const l of loraHelper.selected) {
add("lora", l?.name || l);
}
}
} catch (e) {
}
for (const l of state.inventory?.loras || []) {
if (l?.has_card) {
add("lora", l.name);
}
if (keys.length >= 14) {
break;
}
}
for (const c of state.inventory?.checkpoints || []) {
if (c?.has_card) {
add("checkpoint", c.name);
}
if (keys.length >= 16) {
break;
}
}
await Promise.all(keys.map((k) => prefetchCard(k.kind, k.name)));
}
function cardsCatalog() {
const kind = $2("sa_cards_kind")?.value || "all";
const inv = state.inventory || {};
const rows = [];
if (kind === "all" || kind === "checkpoint") {
for (const c of inv.checkpoints || []) {
rows.push({
kind: "checkpoint",
name: c.name,
title: c.title || c.name,
has_card: !!c.has_card,
hash: c.hash || "",
preview_url: c.preview_url || null,
has_sidecar: !!c.has_sidecar
});
}
}
if (kind === "all" || kind === "lora") {
for (const l of inv.loras || []) {
rows.push({
kind: "lora",
name: l.name,
title: l.title || l.name,
has_card: !!l.has_card,
trigger: l.trigger_phrase,
hash: l.hash || "",
preview_url: l.preview_url || null,
has_sidecar: !!l.has_sidecar
});
}
}
return rows;
}
function renderCardsList() {
const root = $2("sa_cards_list");
if (!root) {
return;
}
root.innerHTML = "";
const rows = cardsCatalog();
if (!rows.length) {
root.innerHTML = '<div class="sa-chat-empty-hint">Inventory \u043F\u0443\u0441\u0442 \u2014 \u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C.</div>';
return;
}
for (const row of rows) {
const btn = document.createElement("button");
btn.type = "button";
btn.className = "sa-card-row";
if (state.cardsSelection && state.cardsSelection.kind === row.kind && state.cardsSelection.name === row.name) {
btn.classList.add("sa-selected");
}
const thumb = row.preview_url ? `<img class="sa-card-row-thumb" src="${escapeHtml2(row.preview_url)}" alt="" />` : '<div class="sa-card-row-thumb"></div>';
const metaBits = [];
metaBits.push(row.has_card ? "card \u2713" : "\u043D\u0435\u0442 card");
if (row.has_sidecar) {
metaBits.push("sidecar");
}
if (isWantedModel(row)) {
metaBits.push("\u23F3 wanted");
btn.classList.add("sa-card-row-wanted");
}
if (row.trigger) {
metaBits.push(String(row.trigger).slice(0, 40));
}
btn.innerHTML = `${thumb}<div class="sa-card-row-body"><div class="sa-card-row-kind">${escapeHtml2(row.kind)}</div><div class="sa-card-row-name">${escapeHtml2(row.title || row.name)}</div><div class="sa-card-row-meta">${escapeHtml2(metaBits.join(" \xB7 "))}</div></div><button type="button" class="basic-button sa-card-row-chat" data-chat="1" title="\u0412 \u0447\u0430\u0442">\u2197</button>`;
btn.addEventListener("click", (e) => {
if (e.target?.closest?.("[data-chat]")) {
e.preventDefault();
e.stopPropagation();
sendCardToChat(row);
return;
}
selectCardModel(row);
});
root.appendChild(btn);
}
}
function sendCardToChat(row) {
if (!row?.name) {
return;
}
selectCardModel(row);
setView("chat");
const kind = row.kind === "checkpoint" ? "checkpoint" : "LoRA";
const triggers = row.trigger ? ` Triggers: ${row.trigger}.` : "";
if ($2("sa_input")) {
$2("sa_input").value = `\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 ${kind} \xAB${row.name}\xBB.${triggers} \u0423\u0447\u0442\u0438 \u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0443/triggers \u0438 \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0438 \u043F\u0430\u0442\u0447.`;
$2("sa_input").focus();
}
setStatus(`\u0412 \u0447\u0430\u0442 \u2192 ${row.name}`);
}
function wireCardForm() {
const sync = () => {
if ($2("sa_card_show_json")?.checked) {
syncCardJsonFromForm();
}
};
["sa_card_triggers", "sa_card_weight", "sa_card_when", "sa_card_avoid", "sa_card_hint", "sa_card_notes", "sa_card_url"].forEach((id) => $2(id)?.addEventListener("change", sync));
$2("sa_card_show_json")?.addEventListener("change", () => {
const on = !!$2("sa_card_show_json")?.checked;
const ta = $2("sa_card_json");
if (ta) {
ta.hidden = !on;
if (on) {
syncCardJsonFromForm();
}
}
});
$2("sa_card_json")?.addEventListener("change", () => {
if ($2("sa_card_show_json")?.checked) {
applyCardToForm(readCardDraft() || {});
}
});
}
function applyCardToForm(card) {
card = card || {};
const triggers = Array.isArray(card.triggers) ? card.triggers.join(", ") : card.triggers || "";
if ($2("sa_card_triggers")) {
$2("sa_card_triggers").value = triggers;
}
if ($2("sa_card_weight")) {
$2("sa_card_weight").value = card.weight != null ? card.weight : state.cardsSelection?.kind === "lora" ? 0.8 : 1;
}
if ($2("sa_card_when")) {
$2("sa_card_when").value = card.when || "";
}
if ($2("sa_card_avoid")) {
$2("sa_card_avoid").value = card.avoid || "";
}
if ($2("sa_card_hint")) {
$2("sa_card_hint").value = card.prompt_hint || "";
}
if ($2("sa_card_notes")) {
$2("sa_card_notes").value = card.notes || "";
}
if ($2("sa_card_url")) {
$2("sa_card_url").value = card.civitai_url || "";
}
if ($2("sa_card_json")) {
$2("sa_card_json").value = JSON.stringify(card, null, 2);
}
}
function syncCardJsonFromForm() {
const sel = state.cardsSelection || {};
let base = {};
try {
base = JSON.parse($2("sa_card_json")?.value || "{}");
} catch (e) {
base = {};
}
const triggers = String($2("sa_card_triggers")?.value || "").split(/[,;]/).map((s) => s.trim()).filter(Boolean);
const card = {
...base,
kind: sel.kind || base.kind || "lora",
name: sel.name || base.name || "",
triggers,
weight: parseFloat($2("sa_card_weight")?.value || "0.8") || 0.8,
when: $2("sa_card_when")?.value || "",
avoid: $2("sa_card_avoid")?.value || "",
prompt_hint: $2("sa_card_hint")?.value || "",
notes: $2("sa_card_notes")?.value || "",
civitai_url: $2("sa_card_url")?.value || "",
version_id: base.version_id != null ? base.version_id : null
};
if ($2("sa_card_json")) {
$2("sa_card_json").value = JSON.stringify(card, null, 2);
}
return card;
}
function renderCardPreviews(urls) {
const root = $2("sa_card_previews");
if (!root) {
return;
}
const list = (urls || []).filter(Boolean).slice(0, 6);
root.innerHTML = "";
if (!list.length) {
root.hidden = true;
return;
}
root.hidden = false;
for (const url of list) {
const img = document.createElement("img");
img.className = "sa-card-thumb";
img.src = url;
img.alt = "preview";
img.title = "\u041A\u043B\u0438\u043A \u2014 \u043D\u0430 \u0432\u043A\u043B\u0430\u0434\u043A\u0443 Refs";
img.addEventListener("click", () => {
setBoardTab("refs");
addRefFromUrl(url);
setStatus("\u041F\u0440\u0435\u0432\u044C\u044E \u2192 Refs");
});
root.appendChild(img);
}
}
function mergeCivitaiIntoCard(card, data) {
const out = { ...card || {} };
const civ = data?.civitai;
if (!out.triggers?.length && data?.trigger_phrase) {
out.triggers = [data.trigger_phrase];
}
if (civ) {
const trained = civ.trainedWords || civ.trained_words;
if ((!out.triggers || !out.triggers.length) && Array.isArray(trained) && trained.length) {
out.triggers = trained.slice(0, 12);
}
if (!out.civitai_url) {
const mid = civ.modelId || civ.model?.id || civ.model?.modelId;
const vid = civ.id || data.version_id;
if (mid && vid) {
out.civitai_url = `https://civitai.red/models/${mid}?modelVersionId=${vid}`;
} else if (vid) {
out.civitai_url = `https://civitai.red/models/0?modelVersionId=${vid}`;
}
}
if (out.version_id == null && (civ.id || data.version_id)) {
out.version_id = civ.id || data.version_id;
}
if (!out.notes && civ.description) {
out.notes = String(civ.description).replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 400);
}
}
if (out.version_id == null && data?.version_id) {
out.version_id = data.version_id;
}
return out;
}
function formatMetaStatus(data) {
if (!data) {
return "\u041D\u0435\u0442 \u043E\u0442\u0432\u0435\u0442\u0430";
}
if (data.error) {
return String(data.error);
}
const parts = [];
if (data.has_sidecar) {
parts.push(`\u0421\u0438\u0434\u0438\u043A\u0430\u0440\u044C \u2713 \xB7 version ${data.version_id || "?"}`);
} else if (data.fetched) {
parts.push(`Civitai \u2713 \xB7 version ${data.version_id || "?"}`);
} else {
parts.push("\u0421\u0438\u0434\u0438\u043A\u0430\u0440\u044F \u043D\u0435\u0442");
}
const n = (data.example_urls || data.preview_urls || []).length;
if (n) {
parts.push(`${n} \u043A\u0430\u0434\u0440${n === 1 ? "" : "\u0430"}`);
}
if (data.has_card) {
parts.push("\u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0430 Assistent \u2713");
} else {
parts.push("\u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0438 Assistent \u043D\u0435\u0442");
}
if (data.fetch_error) {
parts.push(String(data.fetch_error));
}
return parts.join(" \xB7 ");
}
function applyCardMetaResponse(row, data, { preserveUser } = {}) {
let card = data.card || {
kind: row.kind,
name: row.name,
triggers: data.trigger_phrase ? [data.trigger_phrase] : [],
weight: row.kind === "lora" ? 0.8 : 1,
when: "",
avoid: "",
prompt_hint: "",
notes: "",
civitai_url: "",
version_id: data.version_id || null
};
if (preserveUser) {
const current = syncCardJsonFromForm();
card = {
...mergeCivitaiIntoCard(card, data),
when: current.when || card.when || "",
avoid: current.avoid || card.avoid || "",
prompt_hint: current.prompt_hint || card.prompt_hint || "",
notes: current.notes || card.notes || ""
};
} else {
card = mergeCivitaiIntoCard(card, data);
}
applyCardToForm(card);
state.modelCards[`${row.kind}:${row.name}`] = card;
const urls = [
...data.preview_urls || [],
...data.example_urls || []
];
renderCardPreviews(urls);
const badge = $2("sa_card_badge");
if (badge) {
badge.hidden = false;
badge.textContent = data.has_card ? "card \u2713" : data.has_sidecar || data.fetched ? "meta \u2713" : "\u043D\u0435\u0442 \u043C\u0435\u0442\u044B";
}
setCardStatus(formatMetaStatus(data));
}
function selectCardModel(row) {
state.cardsSelection = row;
renderCardsList();
if ($2("sa_card_title")) {
$2("sa_card_title").textContent = row.title || row.name;
}
const badge = $2("sa_card_badge");
if (badge) {
badge.hidden = false;
badge.textContent = "\u2026";
}
setCardStatus("\u0427\u0438\u0442\u0430\u044E \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u0443\u044E \u043C\u0435\u0442\u0443\u2026");
genericRequest(
"AssistentGetCardMeta",
{ kind: row.kind, name: row.name, fetch: false },
(data) => applyCardMetaResponse(row, data || {}),
0,
(err) => setCardStatus(String(err || "\u041E\u0448\u0438\u0431\u043A\u0430 \u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0438"))
);
}
function fetchCardMetaLive() {
const row = state.cardsSelection;
if (!row) {
setCardStatus("\u0412\u044B\u0431\u0435\u0440\u0438 \u043C\u043E\u0434\u0435\u043B\u044C");
return;
}
setCardStatus("\u0421\u0438\u0434\u0438\u043A\u0430\u0440\u044F \u043D\u0435\u0442 \xB7 \u0438\u0449\u0443 \u043F\u043E SHA\u2026");
genericRequest(
"AssistentGetCardMeta",
{ kind: row.kind, name: row.name, fetch: true },
(data) => applyCardMetaResponse(row, data || {}, { preserveUser: true }),
0,
(err) => setCardStatus(String(err || "Civitai: \u043E\u0448\u0438\u0431\u043A\u0430 \u0437\u0430\u043F\u0440\u043E\u0441\u0430"))
);
}
async function addRefFromUrl(url) {
if (!url) {
return;
}
addRefSlot({ src: url, select: false });
}
function readCardDraft() {
const fromForm = syncCardJsonFromForm();
if ($2("sa_card_show_json")?.checked) {
const raw = $2("sa_card_json")?.value || "";
try {
return JSON.parse(raw);
} catch (e) {
setCardStatus("\u041D\u0435\u0432\u0430\u043B\u0438\u0434\u043D\u044B\u0439 JSON");
return null;
}
}
return fromForm;
}
function saveCurrentCard({ enqueue } = {}) {
const sel = state.cardsSelection;
if (!sel) {
setCardStatus("\u0412\u044B\u0431\u0435\u0440\u0438 \u043C\u043E\u0434\u0435\u043B\u044C");
return;
}
const card = readCardDraft();
if (!card) {
return;
}
card.kind = card.kind || sel.kind;
card.name = card.name || sel.name;
setCardStatus("\u0421\u043E\u0445\u0440\u0430\u043D\u044F\u044E\u2026");
genericRequest(
"AssistentSaveCard",
{ kind: sel.kind, name: sel.name, card, enqueue_wanted: !!enqueue },
(data) => {
if (data.error) {
setCardStatus(data.error);
return;
}
state.modelCards[`${sel.kind}:${sel.name}`] = card;
setCardStatus(data.installed ? `\u041A\u0430\u0440\u0442\u043E\u0447\u043A\u0430 Assistent \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0430 \xB7 ${data.path}` : `\u0427\u0435\u0440\u043D\u043E\u0432\u0438\u043A + wanted \xB7 ${data.path}`);
refreshInventory(() => renderCardsList());
if (enqueue || !data.installed) {
refreshWantedQueue();
}
},
0,
(err) => setCardStatus(String(err || "\u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0438\u044F"))
);
}
function enqueueWantedOnly() {
const sel = state.cardsSelection;
const card = readCardDraft() || {};
if (!sel && !card.civitai_url) {
setCardStatus("\u041D\u0443\u0436\u043D\u0430 \u043C\u043E\u0434\u0435\u043B\u044C \u0438\u043B\u0438 civitai_url");
return;
}
genericRequest(
"AssistentEnqueueWanted",
{
kind: card.kind || sel?.kind || "lora",
url: card.civitai_url || "",
version_id: card.version_id || 0,
title: card.name || sel?.name || "",
card
},
(data) => {
setCardStatus(data.already ? "\u0423\u0436\u0435 \u0432 wanted" : `Wanted \u2192 ${data.path}`);
refreshWantedQueue();
},
0,
(err) => setCardStatus(String(err || "\u041E\u0448\u0438\u0431\u043A\u0430 enqueue"))
);
}
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();
}
async function generateCardWithAssistent() {
const sel = state.cardsSelection;
if (!sel) {
setCardStatus("\u0412\u044B\u0431\u0435\u0440\u0438 \u043C\u043E\u0434\u0435\u043B\u044C");
return;
}
if (state.busy) {
setCardStatus("\u0427\u0430\u0442 \u0437\u0430\u043D\u044F\u0442");
return;
}
setPackValue("catalog_card", { flash: true });
setView("chat");
const meta = await new Promise((resolve) => {
genericRequest(
"AssistentGetCardMeta",
{ kind: sel.kind, name: sel.name },
(data) => resolve(data),
0,
() => resolve(null)
);
});
const forced = `Write a recommendation card for this ${sel.kind}: ${sel.name}. Use metadata/triggers only; output one JSON card.`;
await sendChat({
forcedUserText: forced,
skipSlash: true,
skipAutoPack: true,
fromCards: true,
cardTarget: {
kind: sel.kind,
name: sel.name,
meta
}
});
}
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, fromCards, fromDebug } = opts;
if (fromCards) {
const card = extractCardJson(reply);
if (card) {
if ($2("sa_card_json")) {
$2("sa_card_json").value = JSON.stringify(card, null, 2);
}
if (opts.fromDownload || opts.cardTarget) {
const kind = card.kind || opts.cardTarget?.kind || "lora";
const name = card.name || opts.cardTarget?.name;
if (name && typeof genericRequest === "function") {
genericRequest(
"AssistentSaveCard",
{ kind, name, card, enqueue_wanted: false },
(data) => {
if (data?.path) {
state.modelCards[`${kind}:${name}`] = card;
setCardStatus(data.installed ? `Card saved \u2192 ${data.path}` : `Card draft \u2192 ${data.path}`);
setStatus(`Card saved for ${name}`);
}
},
0,
() => setCardStatus("Card draft ready \u2014 Save manually")
);
}
} else {
setView("cards");
setCardStatus("Draft from Assistent \u2014 review & Save");
}
}
return;
}
const { patch } = extractPatch2(reply);
if (fromDebug) {
state.pendingSilentGen = false;
return;
}
const commanded = !!opts.userWantsGenerate || !isMachineTurn(opts) && userImpliesGenerate(opts.userText || "");
let effective = patch;
if (!effective && !fromVisionHop && !fromAutoCritique) {
const aspect = parseAspectFromUserText(opts.userText || "");
if (aspect && (replyMissingJsonPatch(reply) || isSameButAspectRequest(opts.userText || ""))) {
effective = { aspect, actions: ["generate"] };
if (state.lastPatch?.prompt) {
effective.prompt = state.lastPatch.prompt;
}
appendSystemNote(`\u041F\u0430\u0442\u0447 \u043F\u0443\u0441\u0442\u043E\u0439 \u2014 \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u043B aspect ${aspect} \u0441\u0430\u043C.`);
}
}
if (!effective && !fromVisionHop && !fromAutoCritique) {
const synthesized = synthesizePatchAfterEmptyFence(reply, opts.userText || "", opts);
if (synthesized) {
effective = synthesized;
appendSystemNote(synthesized.actions ? "\u041F\u0430\u0442\u0447 \u043F\u0443\u0441\u0442\u043E\u0439 \u2014 \u0441\u043E\u0431\u0440\u0430\u043B prompt \u0438\u0437 \u043E\u0442\u0432\u0435\u0442\u0430 \u0438 \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u043B Generate." : "\u041F\u0430\u0442\u0447 \u043F\u0443\u0441\u0442\u043E\u0439 \u2014 \u0441\u043E\u0431\u0440\u0430\u043B prompt \u0438\u0437 \u043E\u0442\u0432\u0435\u0442\u0430.");
}
}
if (!effective && !fromVisionHop && !fromAutoCritique && commanded && claimTurnHop("empty_patch")) {
appendSystemNote("\u041D\u0443\u0436\u0435\u043D \u043A\u0430\u0434\u0440 \u2014 \u043F\u0440\u043E\u0448\u0443 JSON \u0441 prompt + generate.");
await sendChat({
skipSlash: true,
skipAutoPack: true,
fromEmptyPatchRetry: true,
userWantsGenerate: true,
forcedUserText: '\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C \u0443\u0436\u0435 \u043F\u0440\u043E\u0441\u0438\u0442 \u043A\u0430\u0434\u0440 (\u044D\u0442\u043E \u0441\u043B\u0435\u0434\u0443\u0435\u0442 \u0438\u0437 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F, \u0434\u0430\u0436\u0435 \u0431\u0435\u0437 \u0441\u043B\u043E\u0432\u0430 \xAB\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0439\xBB). \u041E\u0442\u0432\u0435\u0442\u044C \u0422\u041E\u041B\u042C\u041A\u041E \u043E\u0434\u043D\u0438\u043C fenced JSON: {"prompt":"<English Krea prompt>","negative":"<echo or short quality neg>","actions":["generate"]}. prompt \u2014 \u0430\u043D\u0433\u043B\u0438\u0439\u0441\u043A\u0438\u0439. \u0411\u0435\u0437 \u043F\u0440\u043E\u0437\u044B, \u0431\u0435\u0437 \xAB\u0441\u043A\u0430\u0436\u0438 \u0441\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0439\xBB.'
});
return;
}
if (opts.fromPromptEnRetry && effective) {
effective = mergePromptEnRewrite(effective);
}
if (effective) {
rememberLastPatch(effective);
}
if (Array.isArray(effective?.actions) && effective.actions.map(String).includes("interrupt")) {
doInterruptNow();
}
if (civitaiResults && civitaiResults.length && $2("sa_auto_download")?.checked) {
const pick = civitaiResults.find((r) => !r.already_installed && r.download_url && r.krea_likely) || civitaiResults.find((r) => !r.already_installed && r.download_url);
if (pick) {
downloadCivitaiLoRA(pick, null);
}
}
const intent = resolveTurnIntent(effective, opts.userText || "", opts);
if (effective) {
if (intent.generate) {
const acts = Array.isArray(effective.actions) ? effective.actions.map(String) : [];
if (!acts.includes("generate")) {
effective = { ...effective, actions: acts.concat("generate") };
}
} else {
effective = stripGenerateAction(effective);
}
if (!intent.look) {
effective = stripLookAt(effective);
}
rememberLastPatch(effective);
}
if (intent.vetoed) {
state.pendingSilentGen = false;
}
if (effective && intent.look && !fromVisionHop && !fromAutoCritique) {
const hopped = await maybeVisionHop(effective, opts.attachedSlotIds || []);
if (hopped) {
return;
}
}
if (intent.generate && effective?.prompt && promptNeedsKreaPrep(effective.prompt) && !fromVisionHop && claimTurnHop("krea_prep")) {
state.pendingPromptEnMerge = { ...effective };
appendSystemNote("\u0413\u043E\u0442\u043E\u0432\u043B\u044E \u043F\u0440\u043E\u043C\u043F\u0442 \u0434\u043B\u044F Krea 2 \u0447\u0430\u0442-\u043C\u043E\u0434\u0435\u043B\u044C\u044E (EN + \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430)\u2026");
setBusyPhase("refining");
await sendChat({
skipSlash: true,
skipAutoPack: true,
fromPromptEnRetry: true,
userWantsGenerate: true,
forcedUserText: buildKreaPromptPrepRequest(effective)
});
return;
}
const doApply = !!(effective && (intent.generate || $2("sa_auto_apply")?.checked));
if (doApply) {
if (intent.generate) {
startBusyUi("silent_gen");
} else {
setBusyPhase("applying");
}
await applyPatch(effective, "all");
syncLiveParamsBar();
if (intent.generate) {
if (effective?.prompt && promptNeedsKreaPrep(effective.prompt)) {
setStatus("\u041F\u0440\u043E\u043C\u043F\u0442 \u0432\u0441\u0451 \u0435\u0449\u0451 \u043D\u0435 EN/Krea-ready \u2014 Generate \u0441 \u0442\u0435\u043C \u0447\u0442\u043E \u0435\u0441\u0442\u044C");
}
const src = await runGenerateFromPatch(
{ ...effective, actions: Array.isArray(effective.actions) ? effective.actions : ["generate"] },
{ force: true }
);
if (src) {
await maybeAutoCritique(src);
await maybeAutoVisionLook(src);
}
} else if (!state.generating) {
stopBusyUi(intent.vetoed ? "\u0417\u0430\u043F\u043E\u043C\u043D\u0438\u043B \xB7 \u0431\u0435\u0437 Generate" : "");
}
} else if (replyMissingJsonPatch(reply) && !fromAutoCritique && !fromVisionHop) {
setStatus("\u041E\u0442\u0432\u0435\u0442 \u0431\u0435\u0437 JSON-\u043F\u0430\u0442\u0447\u0430 \u2014 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043F\u0440\u0438\u043C\u0435\u043D\u0435\u043D\u043E");
}
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;
await applyPatch(withActions, "all");
state.lastUserParamIntent = prevIntent;
setStatus(note || "Applied");
if ($2("sa_auto_generate")?.checked) {
await runGenerateFromPatch(withActions);
}
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=${!!$2("sa_auto_generate")?.checked} 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)",
"",
"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 === "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|card|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 === "civitai") {
if (!arg) {
setStatus("/civitai <query>");
return true;
}
if ($2("sa_input")) {
$2("sa_input").value = `Find a Krea 2 LoRA for: ${arg}`;
}
setPackValue(defaultPackId(), { flash: true });
await sendChat({
skipAutoPack: true,
forcedUserText: `Search Civitai for Krea-compatible LoRA: ${arg}. Prefer actions search_civitai.`
});
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 = userImpliesGenerate(text);
}
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 && (opts.fromCards || state.view === "cards")) {
setPackValue("catalog_card", { 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 (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 });
await prefetchActiveModelCards();
} 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();
}
}
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 (opts.cardTarget) {
context.card_target = opts.cardTarget;
}
if (opts.fromCards || pack === "catalog_card") {
context.auto_apply = false;
context.auto_generate = false;
}
await prefetchActiveModelCards();
if (chatEpoch !== state.chatEpoch) {
return;
}
const refreshed = collectLiveContext();
context.model_cards = refreshed.model_cards;
const messages = 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 (opts.skipAppendUser) {
messages.push({ 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;
}
try {
state.lastContextChars = context && JSON.stringify(context).length || 0;
} catch (e) {
state.lastContextChars = 0;
}
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);
}
}
};
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 });
}
},
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 });
},
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 });
},
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(() => {
renderCardsList();
renderLoraChips();
});
});
probeOllamaHealth();
refreshWantedQueue();
}
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();
wireCardForm();
$2("sa_btn_new_chat")?.addEventListener("click", () => startNewChat({ saveCurrent: true }));
$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_cards")?.addEventListener("click", () => setView("cards"));
$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());
$2("sa_cards_kind")?.addEventListener("change", renderCardsList);
$2("sa_btn_cards_refresh")?.addEventListener("click", () => refreshInventory(() => renderCardsList(), { rescan: true }));
$2("sa_btn_card_meta")?.addEventListener("click", () => fetchCardMetaLive());
$2("sa_btn_card_generate")?.addEventListener("click", () => generateCardWithAssistent());
$2("sa_btn_card_save")?.addEventListener("click", () => saveCurrentCard());
$2("sa_btn_card_wanted")?.addEventListener("click", () => enqueueWantedOnly());
document.querySelectorAll("#sa_settings .sa-stab").forEach((btn) => {
btn.addEventListener("click", () => setSettingsTab(btn.getAttribute("data-stab")));
});
$2("sa_btn_mem_refresh")?.addEventListener("click", () => {
refreshMemoryList();
refreshWantedQueue();
});
$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(() => {
renderCardsList();
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();
});
$2("sa_btn_card_to_chat")?.addEventListener("click", () => {
if (state.cardsSelection) {
sendCardToChat(state.cardsSelection);
} else {
setCardStatus("\u0421\u043D\u0430\u0447\u0430\u043B\u0430 \u0432\u044B\u0431\u0435\u0440\u0438 \u043C\u043E\u0434\u0435\u043B\u044C \u0432 \u0441\u043F\u0438\u0441\u043A\u0435");
}
});
document.addEventListener("click", () => {
if (state.chatsPanelOpen) {
setChatsPanelOpen(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) {
refreshWantedQueue();
}
}, 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);
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);
})();