(() => { // src/intent.js 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 userAsksNoGenerate(text) { const t = String(text || "").trim(); if (!t) { return false; } if (/\b(remember|save\s+(this\s+)?(as\s+)?(the\s+)?(base\s+)?(prompt|template)|don'?t\s+generat|do\s+not\s+generat|no\s+generat|without\s+generat)\b/i.test(t)) { return true; } return cyrTokenRe( "\u0437\u0430\u043F\u043E\u043C\u043D|\u0437\u0430\u043F\u043E\u043C\u043D\u0438|\u0437\u0430\u043F\u043E\u043C\u043D\u0438\u043C|\u0441\u043E\u0445\u0440\u0430\u043D\u0438|\u0441\u043E\u0445\u0440\u0430\u043D\u0438\u043C|\u0448\u0430\u0431\u043B\u043E\u043D|\u0431\u0430\u0437\u043E\u0432(\u044B\u0439|\u043E\u0433\u043E|\u043E\u043C\u0443|\u044B\u043C|\u0430\u044F|\u0443\u044E|\u043E\u0435)?\\s+\u043F\u0440\u043E\u043C\u043F\u0442|\u043D\u0435\\s+\u0433\u0435\u043D\u0435\u0440\u0438\u0440[\u0430-\u044F\u0451]*|\u0431\u0435\u0437\\s+\u0433\u0435\u043D\u0435\u0440\u0430\u0446[\u0430-\u044F\u0451]*|\u043D\u0435\\s+\u043D\u0430\u0434\u043E\\s+\u0433\u0435\u043D\u0435\u0440[\u0430-\u044F\u0451]*|\u0442\u043E\u043B\u044C\u043A\u043E\\s+\u0437\u0430\u043F\u043E\u043C\u043D[\u0430-\u044F\u0451]*|\u043F\u043E\u043A\u0430\\s+\u0437\u0430\u043F\u043E\u043C\u043D[\u0430-\u044F\u0451]*|\u043D\u0435\\s+\u0440\u0438\u0441\u0443\u0439|\u043D\u0435\\s+\u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0439\\s+\u0433\u0435\u043D\u0435\u0440[\u0430-\u044F\u0451]*|\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)" ).test(t); } function userAsksGenerate(text) { const t = String(text || "").trim(); if (!t || userAsksNoGenerate(t)) { return false; } if (/^как\s+(сгенерир|нарисов|сделать\s+(фото|кадр|изображ))/i.test(t)) { return false; } if (/\bhow\s+to\s+(generate|draw|make|create)\b/i.test(t)) { return false; } if (/\b(generat(e|ion)|draw|render|make\s+(an?\s+)?image|run\s+generate)\b/i.test(t)) { return true; } if (/\b(create|make)\s+(a\s+)?(photo|picture|image|shot|portrait)\b/i.test(t)) { return true; } if (/\blet['']?s\s+(make|create|generate|draw)\s+(an?\s+)?(image|photo|picture|shot)\b/i.test(t)) { return true; } if (/\bi\s+want\s+(an?\s+)?(image|photo|picture|shot)\b/i.test(t)) { return true; } return cyrTokenRe( "\u0441\u0433\u0435\u043D\u0435\u0440[\u0430-\u044F\u0451]*|\u043D\u0430\u0440\u0438\u0441\u0443\u0439|\u043D\u0430\u0440\u0438\u0441\u0443\u0439\u0442\u0435|\u043D\u0430\u0440\u0438\u0441\u0443\u0435\u043C|\u0437\u0430\u043F\u0443\u0441\u0442\u0438\\s+\u0433\u0435\u043D\u0435\u0440[\u0430-\u044F\u0451]*|\u0441\u043E\u0437\u0434\u0430(\u0439|\u0439\u0442\u0435|\u0434\u0438\u043C|\u0434\u0438\u0442\u0435)\\s+(\u0444\u043E\u0442\u043E|\u0444\u043E\u0442\u043E\u0433\u0440\u0430\u0444[\u0430-\u044F\u0451]*|\u043A\u0430\u0440\u0442\u0438\u043D\u043A[\u0430-\u044F\u0451]*|\u0438\u0437\u043E\u0431\u0440\u0430\u0436[\u0430-\u044F\u0451]*|\u043A\u0430\u0434\u0440|\u043F\u043E\u0440\u0442\u0440\u0435\u0442|\u0441\u043D\u0438\u043C\u043E\u043A)|\u0441\u0434\u0435\u043B\u0430(\u0439|\u0439\u0442\u0435|\u0435\u043C|\u0439\u0442\u0435)\\s+(\u0444\u043E\u0442\u043E|\u0444\u043E\u0442\u043E\u0433\u0440\u0430\u0444[\u0430-\u044F\u0451]*|\u043A\u0430\u0434\u0440|\u043A\u0430\u0440\u0442\u0438\u043D\u043A[\u0430-\u044F\u0451]*|\u0438\u0437\u043E\u0431\u0440\u0430\u0436[\u0430-\u044F\u0451]*|\u043F\u043E\u0440\u0442\u0440\u0435\u0442|\u0441\u043D\u0438\u043C\u043E\u043A)|\u0434\u0430\u0432\u0430\u0439\\s+(\u0444\u043E\u0442\u043E|\u0444\u043E\u0442\u043E\u0433\u0440\u0430\u0444[\u0430-\u044F\u0451]*|\u043A\u0430\u0440\u0442\u0438\u043D\u043A[\u0430-\u044F\u0451]*|\u0438\u0437\u043E\u0431\u0440\u0430\u0436[\u0430-\u044F\u0451]*|\u043A\u0430\u0434\u0440|\u0441\u0433\u0435\u043D\u0435\u0440[\u0430-\u044F\u0451]*|\u043D\u0430\u0440\u0438\u0441\u0443[\u0430-\u044F\u0451]*)|\u0434\u0430\u0432\u0430\u0439\\s+\u0441\u0434\u0435\u043B\u0430\u0435\u043C\\s+(\u0444\u043E\u0442\u043E|\u0444\u043E\u0442\u043E\u0433\u0440\u0430\u0444[\u0430-\u044F\u0451]*|\u043A\u0430\u0440\u0442\u0438\u043D\u043A[\u0430-\u044F\u0451]*|\u0438\u0437\u043E\u0431\u0440\u0430\u0436[\u0430-\u044F\u0451]*|\u043A\u0430\u0434\u0440)|\u0445\u043E\u0447\u0443\\s+(\u0444\u043E\u0442\u043E|\u0444\u043E\u0442\u043E\u0433\u0440\u0430\u0444[\u0430-\u044F\u0451]*|\u043A\u0430\u0440\u0442\u0438\u043D\u043A[\u0430-\u044F\u0451]*|\u0438\u0437\u043E\u0431\u0440\u0430\u0436[\u0430-\u044F\u0451]*|\u043A\u0430\u0434\u0440)|\u043D\u0443\u0436\u043D[\u0430\u043E]?\\s+(\u0444\u043E\u0442\u043E|\u0444\u043E\u0442\u043E\u0433\u0440\u0430\u0444[\u0430-\u044F\u0451]*|\u043A\u0430\u0440\u0442\u0438\u043D\u043A[\u0430-\u044F\u0451]*|\u0438\u0437\u043E\u0431\u0440\u0430\u0436[\u0430-\u044F\u0451]*|\u043A\u0430\u0434\u0440)|\u0441\u0444\u043E\u0442\u043E\u0433\u0440\u0430\u0444\u0438\u0440\u0443\u0439|\u043E\u0442\u0440\u0438\u0441\u0443\u0439|\u043F\u0440\u0438\u0434\u0443\u043C\u0430\u0439\\s+(\u0444\u043E\u0442\u043E|\u043A\u0430\u0434\u0440|\u043A\u0430\u0440\u0442\u0438\u043D\u043A[\u0430-\u044F\u0451]*|\u0441\u0446\u0435\u043D\u0443|\u0438\u0437\u043E\u0431\u0440\u0430\u0436[\u0430-\u044F\u0451]*)|\u0441\u0433\u0435\u043D\u0435\u0440\u0438(\u0440\u0443\u0439|\u0440\u0443\u0439\u0442\u0435)\\s+(\u043C\u043D\u0435\\s+)?(\u0444\u043E\u0442\u043E|\u043A\u0430\u0434\u0440|\u043A\u0430\u0440\u0442\u0438\u043D\u043A[\u0430-\u044F\u0451]*|\u0438\u0437\u043E\u0431\u0440\u0430\u0436[\u0430-\u044F\u0451]*)" ).test(t); } function generateFlagOn(patch) { if (!patch || typeof patch !== "object") { return false; } const g = patch.generate; if (g === true || g === 1) { return true; } if (typeof g === "string" && /^(true|1|yes|on)$/i.test(g.trim())) { return true; } return Array.isArray(patch.actions) && patch.actions.map(String).includes("generate"); } function resolveTurnIntent(patch, userText, opts = {}) { const vetoed = !opts.machineTurn && userAsksNoGenerate(userText); const modelAsked = generateFlagOn(patch); const hasPrompt = !!(String(patch?.prompt || "").trim() || String(opts.sessionPrompt || "").trim()); const userAsked = !opts.machineTurn && userAsksGenerate(userText); const generate = !vetoed && !opts.fromAutoCritique && (userAsked || !!opts.userWantsGenerate); const hasLook = !!patch && (patch.look_at != null || patch.vision_from != null || patch.vision_slots != null); const look = !!(hasLook && !vetoed && !generate); const ask = Array.isArray(patch?.ask) ? patch.ask.map(String) : typeof patch?.ask === "string" && patch.ask ? [patch.ask] : []; return { generate, look, vetoed, ask, modelAsked }; } // src/api.js function createRequest() { return function request(name, body) { return new Promise((resolve, reject) => { if (typeof genericRequest !== "function") { reject(new Error("genericRequest unavailable")); return; } genericRequest( name, body || {}, (data) => { if (data && data.error) { reject(new Error(String(data.error))); } else { resolve(data); } }, 0, (err) => reject(err instanceof Error ? err : new Error(String(err || "request failed"))) ); }); }; } function attachApi(SA2) { SA2.request = createRequest(); } // src/patch.js var DEFAULT_PATCH_KEYS = [ "prompt", "negative", "loras", "width", "height", "steps", "cfg", "seed", "sigma_shift", "sampler", "scheduler", "actions", "generate", "ask", "checkpoint", "use_init_image", "clear_init_image", "init_creativity", "denoise", "use_mask_image", "clear_mask_image", "mask_blur", "mask_grow", "look_at", "vision_from", "vision_slots", "slot_to_init", "slot_to_mask", "snapshot_generate", "select_slot", "aspect", "images", "batch", "vary", "lock_seed", "creativity", "intensity", "complexity", "movement", "clear_prompt_images", "slot_to_prompt_image", "pack", "persona", "controls", "inventory_query", "variants" ]; var PATCH_KEYS = DEFAULT_PATCH_KEYS.slice(); function setPatchKeys(keys) { if (Array.isArray(keys) && keys.length) { PATCH_KEYS = keys.map(String); } } var FENCE_RE = /```(?:json)?\s*([\s\S]*?)```/gi; var OPEN_FENCE_RE = /```(?:json)?/i; function has(obj, key) { return obj[key] !== void 0 && obj[key] !== null; } function generateFlagOn2(obj) { if (!obj || typeof obj !== "object") { return false; } const g = obj.generate; if (g === true || g === 1) { return true; } if (typeof g === "string" && /^(true|1|yes|on)$/i.test(g.trim())) { return true; } const acts = Array.isArray(obj.actions) ? obj.actions.map(String) : []; return acts.includes("generate"); } function isPatchObject2(obj) { if (!obj || typeof obj !== "object") { return false; } return PATCH_KEYS.some((k) => has(obj, k)); } function normalizePatch(patch) { if (!patch || typeof patch !== "object") { return patch; } const acts = Array.isArray(patch.actions) ? patch.actions.map(String) : []; if (generateFlagOn2(patch) || acts.includes("generate")) { patch.generate = true; } if (typeof patch.ask === "string") { patch.ask = [patch.ask]; } if (!has(patch, "init_creativity") && has(patch, "denoise")) { patch.init_creativity = patch.denoise; } if (!has(patch, "look_at")) { if (has(patch, "vision_from")) { patch.look_at = patch.vision_from; } else if (has(patch, "vision_slots")) { patch.look_at = patch.vision_slots; } } return patch; } function tryParsePatchJson(raw) { try { const obj = JSON.parse(String(raw || "").trim()); if (isPatchObject2(obj)) { return normalizePatch(obj); } } catch (e) { } return null; } function stripFenceArtifacts(text) { let t = String(text || "").replace(/\r\n/g, "\n"); t = t.replace(/```(?:json)?\s*[\s\S]*?```/gi, ""); t = t.replace(/```(?:json)?\s*[\s\S]*$/gi, ""); t = t.replace(/(?:^|\n)\s*```(?:json)?\s*(?=\n|$)/gi, "\n"); t = t.replace(/(?:^|\n)#{1,6}\s*JSON\s*Patch\s*(?=\n|$)/gi, "\n"); t = t.replace(/(?:^|\n)\s*JSON\s*Patch\s*:?\s*(?=\n|$)/gi, "\n"); return t.replace(/\n{3,}/g, "\n\n").trim(); } function liveStreamProse(text) { const t = String(text || ""); const open = t.search(OPEN_FENCE_RE); if (open >= 0) { const tail = t.slice(open); if (!/```(?:json)?\s*[\s\S]*?```/i.test(tail)) { return stripFenceArtifacts(t.slice(0, open)); } } return stripFenceArtifacts(visibleProse(t) || t); } function extractPatch(text) { if (!text) { return { prose: text || "", patch: null }; } const re = new RegExp(FENCE_RE.source, "gi"); let match; let lastAny = null; let lastAnyIndex = -1; let lastAnyLen = 0; let lastTerminal = null; let lastTermIndex = -1; let lastTermLen = 0; while ((match = re.exec(text)) !== null) { const parsed = tryParsePatchJson(match[1]); if (!parsed) { continue; } lastAny = parsed; lastAnyIndex = match.index; lastAnyLen = match[0].length; if (isTerminalStreamPatch(parsed)) { lastTerminal = parsed; lastTermIndex = match.index; lastTermLen = match[0].length; } } const chosen = lastTerminal || lastAny; if (chosen) { const idx = lastTerminal ? lastTermIndex : lastAnyIndex; const len = lastTerminal ? lastTermLen : lastAnyLen; const prose = stripFenceArtifacts(text.slice(0, idx) + text.slice(idx + len)); return { prose, patch: chosen }; } const brace = text.lastIndexOf("{"); if (brace >= 0) { const parsed = tryParsePatchJson(text.slice(brace)); if (parsed) { return { prose: stripFenceArtifacts(text.slice(0, brace)), patch: parsed }; } } return { prose: stripFenceArtifacts(text), patch: null }; } function visibleProse(text) { const { prose, patch } = extractPatch(text); if (patch) { return prose || ""; } return stripFenceArtifacts(text); } function isTerminalStreamPatch(obj) { if (!obj || typeof obj !== "object") { return false; } if (generateFlagOn2(obj)) { return true; } if (Array.isArray(obj.ask) && obj.ask.length) { return true; } if (typeof obj.ask === "string" && obj.ask) { return true; } if (Array.isArray(obj.variants) && obj.variants.length) { return true; } if (obj.look_at != null || obj.vision_from != null || obj.vision_slots != null) { return true; } 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.checkpoint != null || obj.negative != null) { return true; } return false; } function attachPatch(SA2) { SA2.PATCH_KEYS = PATCH_KEYS; SA2.setPatchKeys = setPatchKeys; SA2.isPatchObject = isPatchObject2; SA2.isTerminalStreamPatch = isTerminalStreamPatch; SA2.normalizePatch = normalizePatch; SA2.extractPatch = extractPatch; SA2.generateFlagOn = generateFlagOn2; SA2.visibleProse = visibleProse; SA2.stripFenceArtifacts = stripFenceArtifacts; SA2.liveStreamProse = liveStreamProse; } // src/persist.js var LS_CHATS = "swarm_assistent_chats_v1"; var SAVE_DEBOUNCE_MS = 700; var timers = { chats: /* @__PURE__ */ new Map(), ui: null }; function normalizeChat(raw) { if (!raw || !raw.id) { return null; } return { id: String(raw.id), title: String(raw.title || "\u041D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442"), createdAt: Number(raw.createdAt) || Date.now(), updatedAt: Number(raw.updatedAt) || Date.now(), messages: Array.isArray(raw.messages) ? raw.messages : [], messages_count: Number(raw.messages_count) || (Array.isArray(raw.messages) ? raw.messages.length : 0), params: raw.params && typeof raw.params === "object" ? raw.params : null }; } function attachPersist(SA2, request = SA2.request) { async function loadChats() { let chats = []; try { const data = await request("AssistentListChats", { with_messages: true }); chats = (data?.chats || []).map(normalizeChat).filter(Boolean); } catch (e) { console.warn("Assistent: disk chats unavailable", e); return null; } return chats.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0)); } async function getChat(id) { if (!id) { return null; } const data = await request("AssistentGetChat", { id }); return normalizeChat(data?.chat); } async function searchChats(q) { const query = String(q || "").trim(); if (query.length < 2) { return []; } const data = await request("AssistentListChats", { q: query, with_messages: false, limit: 40 }); return (data?.chats || []).map(normalizeChat).filter(Boolean); } function saveChat(chat, { immediate = false } = {}) { const clean = normalizeChat(chat); if (!clean) { return Promise.resolve(null); } const send = () => { timers.chats.delete(clean.id); return request("AssistentSaveChat", { id: clean.id, title: clean.title, messages: clean.messages, params: clean.params, createdAt: clean.createdAt, updatedAt: clean.updatedAt }); }; if (immediate) { const pending2 = timers.chats.get(clean.id); if (pending2) { clearTimeout(pending2); } return send(); } const pending = timers.chats.get(clean.id); if (pending) { clearTimeout(pending); } timers.chats.set(clean.id, setTimeout(() => { send().catch((e) => console.warn("Assistent: chat save failed", clean.id, e)); }, SAVE_DEBOUNCE_MS)); return Promise.resolve(null); } function deleteChat(id) { if (!id) { return Promise.resolve(null); } const pending = timers.chats.get(id); if (pending) { clearTimeout(pending); timers.chats.delete(id); } return request("AssistentDeleteChat", { id }); } async function loadUiState() { try { const data = await request("AssistentGetUiState", {}); const ui = data?.ui_state; return ui && typeof ui === "object" ? ui : null; } catch (e) { return null; } } function saveUiState(uiState, { immediate = false } = {}) { if (!uiState || typeof uiState !== "object") { return Promise.resolve(null); } const send = () => { timers.ui = null; return request("AssistentSaveUiState", { ui_state: uiState }); }; if (timers.ui) { clearTimeout(timers.ui); timers.ui = null; } if (immediate) { return send(); } timers.ui = setTimeout(() => { send().catch((e) => console.warn("Assistent: ui-state save failed", e)); }, SAVE_DEBOUNCE_MS); return Promise.resolve(null); } SA2.persist = { LS_CHATS, loadChats, getChat, searchChats, saveChat, deleteChat, loadUiState, saveUiState }; } // src/session.js var MAX_DATA_URL_CHARS = 35e4; var GEN_KEYS = [ "prompt", "negative", "width", "height", "aspect", "steps", "cfg", "sigma_shift", "seed", "sampler", "scheduler", "batch", "checkpoint", "loras", "controls", "use_init_image", "clear_init_image", "init_creativity", "denoise", "use_mask_image", "clear_mask_image", "mask_blur", "mask_grow" ]; function emptySession() { return { gen: { prompt: "", negative: "", width: null, height: null, aspect: null, steps: null, cfg: null, sigma_shift: null, seed: null, sampler: null, scheduler: null, batch: null, checkpoint: null, loras: [], controls: {}, use_init_image: false, clear_init_image: false, init_creativity: null, denoise: null, use_mask_image: false, clear_mask_image: false, mask_blur: null, mask_grow: null }, board: { slots: [], selectedSlotId: "ref1", genResults: [], selectedGenResultId: null, refSeq: 1 }, persona: "neutral", pack: "ordinary", context_memory: null }; } function normalizeDelta(raw) { if (!raw || typeof raw !== "object") { return null; } const delta = { ...raw }; const acts = Array.isArray(delta.actions) ? delta.actions.map(String) : []; const g = delta.generate; const generateOn = g === true || g === 1 || typeof g === "string" && /^(true|1|yes|on)$/i.test(g.trim()) || acts.includes("generate"); if (generateOn) { delta.generate = true; } if (typeof delta.ask === "string") { delta.ask = [delta.ask]; } if (!Array.isArray(delta.ask)) { delete delta.ask; } else { delta.ask = delta.ask.map(String).filter(Boolean); } return delta; } function patchWantsGenerate(patch) { if (!patch || typeof patch !== "object") { return false; } const n = normalizeDelta(patch); if (n?.generate === true) { return true; } const acts = Array.isArray(patch.actions) ? patch.actions.map(String) : []; return acts.includes("generate"); } function patchAskList(patch) { const n = normalizeDelta(patch); return Array.isArray(n?.ask) ? n.ask : []; } function mergeDelta(session, rawDelta) { const base = session && typeof session === "object" ? structuredCloneSession(session) : emptySession(); const delta = normalizeDelta(rawDelta); if (!delta) { return base; } if (!base.gen) { base.gen = emptySession().gen; } for (const key of GEN_KEYS) { if (delta[key] === void 0 || delta[key] === null) { continue; } if (key === "loras" && Array.isArray(delta.loras)) { base.gen.loras = delta.loras.map((l) => ({ name: l?.name || l, weight: l?.weight != null ? Number(l.weight) : 1, triggers: Array.isArray(l?.triggers) ? l.triggers : void 0, trigger_phrase: l?.trigger_phrase || void 0 })).filter((l) => l.name); continue; } if (key === "controls" && typeof delta.controls === "object") { base.gen.controls = { ...base.gen.controls || {}, ...delta.controls }; continue; } if (key === "checkpoint") { base.gen.checkpoint = typeof delta.checkpoint === "object" ? { ...delta.checkpoint } : { name: String(delta.checkpoint) }; continue; } base.gen[key] = delta[key]; } if (delta.images != null && delta.batch == null) { base.gen.batch = delta.images; } if (delta.pack) { base.pack = String(delta.pack); } if (delta.persona) { base.persona = String(delta.persona); } return base; } function structuredCloneSession(session) { try { return JSON.parse(JSON.stringify(session)); } catch { return emptySession(); } } function slimSrc(src) { if (!src || typeof src !== "string") { return null; } const s = src.trim(); if (!s || s.startsWith("#")) { return null; } if (s.startsWith("data:") && s.length > MAX_DATA_URL_CHARS) { return null; } return s; } function snapshotFromLive({ genFields, board, persona, pack, context_memory }) { const session = emptySession(); if (genFields && typeof genFields === "object") { for (const key of GEN_KEYS) { if (genFields[key] !== void 0) { session.gen[key] = genFields[key]; } } } session.persona = persona || "neutral"; session.pack = pack || "ordinary"; if (context_memory && typeof context_memory === "object") { session.context_memory = context_memory; } if (board && typeof board === "object") { session.board = { slots: (board.slots || []).map((s) => ({ id: s.id, type: s.type, label: s.label, src: slimSrc(s.src), attach: !!s.attach, note: s.note || null })), selectedSlotId: board.selectedSlotId || "ref1", genResults: (board.genResults || []).map((r) => ({ id: r.id, label: r.label, src: slimSrc(r.src), patch: r.patch || null })), selectedGenResultId: board.selectedGenResultId || null, refSeq: board.refSeq || 1 }; } return session; } function sessionFromLegacyParams(params) { if (!params || typeof params !== "object") { return emptySession(); } if (params.gen && typeof params.gen === "object") { const s2 = emptySession(); s2.gen = { ...s2.gen, ...params.gen }; if (params.board && typeof params.board === "object") { s2.board = { ...s2.board, ...params.board }; } s2.persona = params.persona || s2.persona; s2.pack = params.pack || s2.pack; if (params.context_memory && typeof params.context_memory === "object") { s2.context_memory = params.context_memory; } return s2; } const s = emptySession(); for (const key of GEN_KEYS) { if (params[key] !== void 0 && params[key] !== null) { s.gen[key] = params[key]; } } if (Array.isArray(params.loras)) { s.gen.loras = params.loras; } if (params.checkpoint) { s.gen.checkpoint = typeof params.checkpoint === "object" ? params.checkpoint : { name: String(params.checkpoint) }; } s.persona = params.persona || "neutral"; s.pack = params.pack || "ordinary"; if (params.context_memory && typeof params.context_memory === "object") { s.context_memory = params.context_memory; } s.board.genResults = Array.isArray(params.genResults) ? params.genResults : []; s.board.selectedGenResultId = params.selectedGenResultId || null; if (Array.isArray(params.slots)) { s.board.slots = params.slots; } if (params.selectedSlotId) { s.board.selectedSlotId = params.selectedSlotId; } if (params.refSeq) { s.board.refSeq = params.refSeq; } return s; } function toPersistParams(session) { const s = session && typeof session === "object" ? session : emptySession(); const out = { gen: s.gen || emptySession().gen, board: s.board || emptySession().board, persona: s.persona || "neutral", pack: s.pack || "ordinary" }; if (s.context_memory && typeof s.context_memory === "object") { out.context_memory = s.context_memory; } return out; } function slimText(t, max) { const s = String(t || ""); if (s.length <= max) { return s; } return `${s.slice(0, max)}\u2026`; } function compactContext(session, extras = {}) { const s = session && typeof session === "object" ? session : emptySession(); const g = s.gen || {}; const board = s.board || {}; const slots = board.slots || []; const genSlot = slots.find((x) => x.type === "generate" || x.id === "generate"); const refs = slots.filter((x) => x.type === "ref" || String(x.id || "").startsWith("ref")); return { session: true, prompt: slimText(g.prompt, extras.promptMax || 2e3), negative: slimText(g.negative, 500), aspect: g.aspect || null, width: g.width ?? null, height: g.height ?? null, steps: g.steps ?? null, cfg: g.cfg ?? null, seed: g.seed ?? null, sigma_shift: g.sigma_shift ?? null, sampler: g.sampler || null, scheduler: g.scheduler || null, batch: g.batch ?? null, checkpoint: g.checkpoint?.name || g.checkpoint || null, selected_loras: (g.loras || []).map((l) => ({ name: l.name || l, weight: l.weight != null ? l.weight : 1 })), persona: s.persona || "neutral", pack: s.pack || "ordinary", board: { has_generate: !!(genSlot?.src || (board.genResults || []).some((r) => r.src)), refs: refs.map((r) => ({ id: r.id, has_image: !!r.src, attach: !!r.attach })), gen_results: (board.genResults || []).map((r) => ({ id: r.id, label: r.label, has_image: !!r.src, selected: r.id === board.selectedGenResultId })), selected_slot: board.selectedSlotId || null }, architecture_ok: extras.architecture_ok !== false, ...extras.extra }; } function fullSettingsDump(session, extras = {}) { const compact = compactContext(session, extras); const s = session && typeof session === "object" ? session : emptySession(); return { ...compact, detail: "settings", gen: { ...s.gen || {} }, controls: s.gen?.controls || {}, exact: extras.exact || null, krea_profiles: extras.kreaProfiles || null, session_exact: extras.sessionExact || null }; } function resolveTurnIntent2(patch, userText, { vetoFn, askGenerateFn, fromAutoCritique, sessionPrompt, userWantsGenerate } = {}) { const delta = normalizeDelta(patch) || {}; const vetoed = typeof vetoFn === "function" ? !!vetoFn(userText) : false; const modelAsked = patchWantsGenerate(delta); const userAsked = typeof askGenerateFn === "function" && !!askGenerateFn(userText); const generate = !vetoed && !fromAutoCritique && (userAsked || !!userWantsGenerate); const hasLook = delta.look_at != null || delta.vision_from != null || delta.vision_slots != null; const look = !!(hasLook && !generate && !vetoed); const ask = patchAskList(delta); return { generate, look, vetoed, ask, modelAsked }; } var EXACT_GENERATE_PARAM_KEYS = ["steps", "cfg", "sigma_shift"]; var SESSION_PINNED_PARAM_KEYS = ["sampler", "scheduler", "aspect", "seed"]; function resolveExactProfileDefaults({ exact, profiles, profileName } = {}) { const gen = exact?.generation && typeof exact.generation === "object" ? exact.generation : {}; const profile = profileName || gen.profile || "turbo"; const fromProfile = profiles?.[profile] && typeof profiles[profile] === "object" ? profiles[profile] : {}; return { profile, steps: fromProfile.steps ?? gen.steps ?? null, cfg: fromProfile.cfg ?? gen.cfg ?? null, sigma_shift: fromProfile.sigma_shift ?? gen.sigma_shift ?? null }; } function mergeExactParamsForGenerate(patch, { exact, profiles, profileName, sessionExact, userParamIntent } = {}) { if (!patchWantsGenerate(patch)) { return { patch, clearSessionKeys: [], profile: null }; } const defaults = resolveExactProfileDefaults({ exact, profiles, profileName }); const out = { ...patch }; const clearSessionKeys = []; for (const key of EXACT_GENERATE_PARAM_KEYS) { if (out[key] != null) { if (!userParamIntent && defaults[key] != null && String(out[key]) !== String(defaults[key])) { out[key] = defaults[key]; if (sessionExact?.[key] != null && String(sessionExact[key]) !== String(defaults[key])) { clearSessionKeys.push(key); } } continue; } if (userParamIntent && sessionExact?.[key] != null) { out[key] = sessionExact[key]; continue; } if (defaults[key] != null) { out[key] = defaults[key]; if (sessionExact?.[key] != null && String(sessionExact[key]) !== String(defaults[key])) { clearSessionKeys.push(key); } } } for (const key of SESSION_PINNED_PARAM_KEYS) { if (out[key] != null) { continue; } if (sessionExact?.[key] != null) { out[key] = sessionExact[key]; } } return { patch: out, clearSessionKeys, profile: defaults.profile }; } function attachSession(SA2) { SA2.session = { emptySession, normalizeDelta, mergeDelta, patchWantsGenerate, patchAskList, snapshotFromLive, sessionFromLegacyParams, toPersistParams, compactContext, fullSettingsDump, resolveTurnIntent: resolveTurnIntent2, resolveExactProfileDefaults, mergeExactParamsForGenerate, EXACT_GENERATE_PARAM_KEYS, SESSION_PINNED_PARAM_KEYS, GEN_KEYS }; } // src/context.js function emptyContextMemory() { return { summary: "", untilCount: 0, foldedTurns: 0, at: 0, uiCollapsed: false, promptEvalCount: null }; } function normalizeContextMemory(raw) { if (!raw || typeof raw !== "object") { return emptyContextMemory(); } const summary = String(raw.summary || "").trim(); return { summary, untilCount: Math.max(0, Number(raw.untilCount) || 0), foldedTurns: Math.max(0, Number(raw.foldedTurns) || 0), at: Number(raw.at) || 0, uiCollapsed: !!raw.uiCollapsed && !!summary, promptEvalCount: raw.promptEvalCount != null ? Number(raw.promptEvalCount) || null : null }; } function charsToTokens(chars, charsPerToken = 3.2) { const cpt = Math.max(1.5, Number(charsPerToken) || 3.2); const n = Math.max(0, Number(chars) || 0); return Math.ceil(n / cpt); } function estimateBudget(opts = {}) { const numCtx = Math.max(1024, Number(opts.numCtx) || 16384); const numPredict = Math.max(256, Number(opts.numPredict) || 3072); const charsPerToken = Math.max(1.5, Number(opts.charsPerToken) || 3.2); const compressAt = Math.min(0.95, Math.max(0.4, Number(opts.compressAt) || 0.7)); const systemChars = Math.max(0, Number(opts.systemChars) || 0); const historyChars = Math.max(0, Number(opts.historyChars) || 0); const memoryChars = Math.max(0, Number(opts.memoryChars) || 0); const inputChars = systemChars + historyChars + memoryChars; const estimated = charsToTokens(inputChars, charsPerToken); const used = opts.promptEvalCount != null && Number(opts.promptEvalCount) > 0 ? Number(opts.promptEvalCount) : estimated; const headroom = Math.max(1024, numCtx - numPredict); const threshold = Math.floor(headroom * compressAt); const ratio = numCtx > 0 ? used / numCtx : 0; let level = "ok"; if (ratio >= 0.85 || used >= threshold) { level = "hot"; } else if (ratio >= 0.65 || used >= threshold * 0.85) { level = "warn"; } return { numCtx, numPredict, headroom, threshold, systemChars, historyChars, memoryChars, inputChars, estimated, used, fromEval: opts.promptEvalCount != null && Number(opts.promptEvalCount) > 0, ratio, level, charsPerToken, compressAt }; } function shouldCompress(budget, memory, historyLen, opts = {}) { const keep = Math.max(2, Number(opts.keepMessages) || 8); const len = Math.max(0, Number(historyLen) || 0); const until = Math.max(0, Number(memory?.untilCount) || 0); const uncovered = Math.max(0, len - until); if (uncovered <= keep) { return false; } const used = budget?.used ?? 0; const threshold = budget?.threshold ?? Infinity; return used >= threshold; } function assembleModelMessages(history, memory, keepTurns) { const keep = Math.max(1, Number(keepTurns) || 4) * 2; const until = Math.max(0, Number(memory?.untilCount) || 0); const list = (history || []).filter((m) => m && (m.role === "user" || m.role === "assistant") && !m.systemish); const afterSummary = until > 0 ? list.slice(until) : list; const window2 = afterSummary.length > keep ? afterSummary.slice(-keep) : afterSummary; return window2.map((m) => ({ role: m.role, content: String(m.content || "").slice(0, 4e3) })); } function messagesToFold(history, memory, keepTurns) { const keep = Math.max(1, Number(keepTurns) || 4) * 2; const list = (history || []).filter((m) => m && (m.role === "user" || m.role === "assistant") && !m.systemish); const until = Math.max(0, Number(memory?.untilCount) || 0); const foldEnd = Math.max(until, list.length - keep); if (foldEnd <= until) { return []; } return list.slice(until, foldEnd); } function mergeSummary(oldSummary, incoming) { const next = String(incoming || "").trim(); if (!next) { return String(oldSummary || "").trim(); } const prev = String(oldSummary || "").trim(); if (!prev) { return next; } return next; } function formatTokenShort(n) { const v = Math.max(0, Number(n) || 0); if (v >= 1e4) { return `${(v / 1e3).toFixed(1)}k`; } if (v >= 1e3) { return `${(v / 1e3).toFixed(1)}k`; } return String(Math.round(v)); } function conversationMemoryBlock(memory, maxChars = 2400) { const m = normalizeContextMemory(memory); if (!m.summary) { return null; } let text = m.summary; if (text.length > maxChars) { text = `${text.slice(0, maxChars)}\u2026`; } return { summary: text, until_count: m.untilCount, folded_turns: m.foldedTurns }; } function attachContext(SA2) { SA2.context = { emptyContextMemory, normalizeContextMemory, charsToTokens, estimateBudget, shouldCompress, assembleModelMessages, messagesToFold, mergeSummary, formatTokenShort, conversationMemoryBlock }; } // src/activity.js var STEP_ICONS = { think: "\u25C7", stream: "\u270E", delta: "\u21E2", ask: "?", look: "\u25CE", prep: "\u21BB", generate: "\u25B7", merge: "\u2295", warm: "\u25B2", park: "\u25BC", inventory: "\u25A4", compress: "\u25A4", ctx: "\u2699", model: "\u25C9", done: "\u2713", skip: "\u2013", error: "!" }; function trimDetail(s, max = 220) { const t = String(s || "").trim(); if (t.length <= max) { return t; } return `${t.slice(0, max)}\u2026`; } function shortModelName(model) { const s = String(model || "").trim(); if (!s) { return ""; } const slash = s.lastIndexOf("/"); return slash >= 0 ? s.slice(slash + 1) : s; } function buildContextMeta(context = {}) { const parts = []; const model = shortModelName(context.model); if (model) { parts.push(model); } if (context.pack) { parts.push(String(context.pack).replace(/_/g, " ")); } if (context.persona && context.persona !== "neutral") { parts.push(String(context.persona)); } if (Array.isArray(context.skills) && context.skills.length) { parts.push(`skills: ${context.skills.slice(0, 4).join(", ")}`); } if (context.hop) { parts.push(String(context.hop)); } return parts.join(" \xB7 "); } function renderMetaRow(context) { const meta = buildContextMeta(context); if (!meta) { return null; } const row = document.createElement("div"); row.className = "sa-activity-meta"; row.textContent = meta; row.title = meta; return row; } function renderSteps(listEl, steps) { if (!listEl) { return; } listEl.replaceChildren(...steps.map(renderStep)); } function renderStep(step) { const row = document.createElement("div"); row.className = `sa-activity-step sa-activity-${step.status || "done"}`; row.dataset.id = step.id; const icon = document.createElement("span"); icon.className = "sa-activity-icon"; icon.setAttribute("aria-hidden", "true"); icon.textContent = STEP_ICONS[step.kind] || STEP_ICONS.think; const body = document.createElement("div"); body.className = "sa-activity-body"; const label = document.createElement("div"); label.className = "sa-activity-label"; label.textContent = step.label || step.id; body.appendChild(label); if (step.detail) { const detail = document.createElement("div"); detail.className = "sa-activity-detail"; detail.textContent = step.detail; body.appendChild(detail); } row.appendChild(icon); row.appendChild(body); return row; } function buildActivityCard(trace, { live = false, collapsed = false } = {}) { const card = document.createElement("div"); card.className = "sa-activity" + (live ? " sa-activity-live" : " sa-activity-done sa-activity-historical"); const steps = Array.isArray(trace?.steps) ? trace.steps : []; const running = steps.some((s) => s.status === "running"); if (!live && !running) { card.classList.add("sa-activity-done"); } if (running) { card.classList.add("sa-activity-live"); } card.setAttribute("role", "status"); card.setAttribute("aria-live", live ? "polite" : "off"); const head = document.createElement("button"); head.type = "button"; head.className = "sa-activity-head"; head.setAttribute("aria-expanded", collapsed ? "false" : "true"); const spin = document.createElement("span"); spin.className = "sa-activity-spin"; spin.setAttribute("aria-hidden", "true"); const titleEl = document.createElement("span"); titleEl.className = "sa-activity-title"; const runningStep = steps.find((s) => s.status === "running"); const last = steps[steps.length - 1]; titleEl.textContent = trace?.title || runningStep?.label || last?.label || "Assistent"; const chev = document.createElement("span"); chev.className = "sa-activity-chev"; chev.setAttribute("aria-hidden", "true"); chev.textContent = "\u25BE"; head.appendChild(spin); head.appendChild(titleEl); head.appendChild(chev); let open = !collapsed; head.addEventListener("click", () => { open = !open; card.classList.toggle("sa-activity-collapsed", !open); head.setAttribute("aria-expanded", open ? "true" : "false"); }); if (collapsed) { card.classList.add("sa-activity-collapsed"); } const meta = renderMetaRow(trace?.context || {}); const listEl = document.createElement("div"); listEl.className = "sa-activity-steps"; renderSteps(listEl, steps); card.appendChild(head); if (meta) { card.appendChild(meta); } card.appendChild(listEl); return card; } function mountActivityTrace(trace, { box, insertBefore = null, collapsed = true } = {}) { const host = box || null; if (!host || !trace || !Array.isArray(trace.steps) || !trace.steps.length) { return null; } const card = buildActivityCard(trace, { live: false, collapsed }); if (insertBefore && insertBefore.parentNode === host) { host.insertBefore(card, insertBefore); } else { host.appendChild(card); } return card; } function slimActivityTrace(trace) { if (!trace || typeof trace !== "object") { return void 0; } const steps = (trace.steps || []).slice(0, 32).map((s) => ({ id: String(s.id || "").slice(0, 40), kind: s.kind || "think", label: String(s.label || s.id || "").slice(0, 120), detail: trimDetail(s.detail, 180), status: s.status || "done" })); if (!steps.length) { return void 0; } return { title: String(trace.title || "Assistent").slice(0, 80), context: trace.context ? { model: trace.context.model || void 0, pack: trace.context.pack || void 0, persona: trace.context.persona || void 0, skills: Array.isArray(trace.context.skills) ? trace.context.skills.slice(0, 8) : void 0, hop: trace.context.hop || void 0 } : void 0, steps }; } function createActivityController(opts = {}) { const { getMessagesEl, scrollToBottom, hideEmpty } = opts; let card = null; let listEl = null; let titleEl = null; let metaEl = null; let steps = []; let context = {}; let title = "Assistent"; let open = true; function insertCard(el) { const box = typeof getMessagesEl === "function" ? getMessagesEl() : null; if (!box || !el) { return; } const lastUser = box.querySelector(".sa-msg.user:last-of-type"); if (lastUser && lastUser.parentNode === box) { if (lastUser.nextSibling) { box.insertBefore(el, lastUser.nextSibling); } else { box.appendChild(el); } return; } box.appendChild(el); } function ensureCard() { const box = typeof getMessagesEl === "function" ? getMessagesEl() : null; if (!box) { return null; } if (card && card.isConnected) { return card; } if (typeof hideEmpty === "function") { hideEmpty(); } card = document.createElement("div"); card.className = "sa-activity sa-activity-live"; card.setAttribute("role", "status"); card.setAttribute("aria-live", "polite"); const head = document.createElement("button"); head.type = "button"; head.className = "sa-activity-head"; head.setAttribute("aria-expanded", "true"); const spin = document.createElement("span"); spin.className = "sa-activity-spin"; spin.setAttribute("aria-hidden", "true"); titleEl = document.createElement("span"); titleEl.className = "sa-activity-title"; titleEl.textContent = title || "Assistent"; const chev = document.createElement("span"); chev.className = "sa-activity-chev"; chev.setAttribute("aria-hidden", "true"); chev.textContent = "\u25BE"; head.appendChild(spin); head.appendChild(titleEl); head.appendChild(chev); head.addEventListener("click", () => { open = !open; card.classList.toggle("sa-activity-collapsed", !open); head.setAttribute("aria-expanded", open ? "true" : "false"); }); listEl = document.createElement("div"); listEl.className = "sa-activity-steps"; card.appendChild(head); metaEl = renderMetaRow(context); if (metaEl) { card.appendChild(metaEl); } card.appendChild(listEl); insertCard(card); if (typeof scrollToBottom === "function") { scrollToBottom(); } return card; } function paint() { if (!ensureCard() || !listEl) { return; } if (metaEl) { const nextMeta = buildContextMeta(context); metaEl.textContent = nextMeta; metaEl.title = nextMeta; metaEl.hidden = !nextMeta; } renderSteps(listEl, steps); const running = steps.find((s) => s.status === "running"); const last = steps[steps.length - 1]; if (titleEl) { titleEl.textContent = running ? running.label : last?.label || title || "Assistent"; } card.classList.toggle("sa-activity-live", steps.some((s) => s.status === "running")); card.classList.toggle("sa-activity-done", steps.length > 0 && steps.every((s) => s.status === "done" || s.status === "skip")); if (typeof scrollToBottom === "function") { scrollToBottom(); } } function setContext(patch = {}) { context = { ...context, ...patch }; if (card && !metaEl) { metaEl = renderMetaRow(context); if (metaEl && card.firstChild) { card.insertBefore(metaEl, card.firstChild); } else if (metaEl) { card.prepend(metaEl); } } paint(); } function begin(nextTitle, nextContext = {}) { steps = []; card = null; listEl = null; titleEl = null; metaEl = null; open = true; title = nextTitle || "Assistent"; context = nextContext && typeof nextContext === "object" ? { ...nextContext } : {}; ensureCard(); const meta = buildContextMeta(context); if (meta) { upsert("ctx", { kind: "ctx", label: "\u041A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u0445\u043E\u0434\u0430", detail: meta, status: "done" }); } if (context.model) { upsert("model", { kind: "model", label: "\u041C\u043E\u0434\u0435\u043B\u044C", detail: String(context.model), status: "done" }); } paint(); } function upsert(id, patch) { ensureCard(); let step = steps.find((s) => s.id === id); if (!step) { step = { id, kind: "think", label: id, status: "running", detail: "" }; steps.push(step); } Object.assign(step, patch); if (!step.status) { step.status = "running"; } paint(); return step; } function done(id, patch = {}) { return upsert(id, { ...patch, status: "done" }); } function skip(id, patch = {}) { return upsert(id, { ...patch, status: "skip" }); } function fail(id, patch = {}) { return upsert(id, { ...patch, status: "error" }); } function finish(summary) { steps.forEach((s) => { if (s.status === "running") { s.status = "done"; } }); if (summary) { title = summary; } if (summary && titleEl) { titleEl.textContent = summary; } paint(); if (card) { card.classList.remove("sa-activity-live"); card.classList.add("sa-activity-done"); } } function snapshot() { return slimActivityTrace({ title: titleEl?.textContent || title || "Assistent", context, steps: steps.slice() }); } function noteModelCommands(patch) { if (!patch || typeof patch !== "object") { return; } const keys = Object.keys(patch).filter((k) => patch[k] != null && !["actions", "generate", "ask", "look_at", "vision_from", "vision_slots", "notes", "variants"].includes(k)); if (keys.length) { done("delta", { kind: "delta", label: "\u041F\u0430\u0442\u0447 \u0441\u0435\u0441\u0441\u0438\u0438", detail: keys.slice(0, 12).join(", ") }); } const ask = Array.isArray(patch.ask) ? patch.ask.map(String) : patch.ask ? [String(patch.ask)] : []; if (ask.length) { upsert("ask", { kind: "ask", label: `ask: ${ask.join(", ")}`, detail: "\u0437\u0430\u043F\u0440\u043E\u0441 \u0434\u0435\u0442\u0430\u043B\u0435\u0439 \u0443 SwarmUI", status: "running" }); } if (patch.look_at != null || patch.vision_from != null || patch.vision_slots != null) { const slots = [].concat(patch.look_at || patch.vision_from || patch.vision_slots || []); upsert("look", { kind: "look", label: "look_at", detail: slots.map(String).slice(0, 4).join(", "), status: "running" }); } if (patch.generate === true || Array.isArray(patch.actions) && patch.actions.map(String).includes("generate")) { upsert("generate", { kind: "generate", label: "Generate", detail: "\u043A\u043B\u0438\u0435\u043D\u0442 \u2192 SwarmUI", status: "running" }); } if (Array.isArray(patch.variants) && patch.variants.length) { upsert("variants", { kind: "generate", label: `variants \xD7${patch.variants.length}`, status: "running" }); } } return { begin, setContext, upsert, done, skip, fail, finish, snapshot, noteModelCommands, get steps() { return steps.slice(); } }; } function attachActivity(SA2) { SA2.createActivityController = createActivityController; SA2.mountActivityTrace = mountActivityTrace; SA2.slimActivityTrace = slimActivityTrace; } // src/kreaProfile.js function numClose(a, b, eps = 0.051) { const x = Number(a); const y = Number(b); if (!Number.isFinite(x) || !Number.isFinite(y)) { return false; } return Math.abs(x - y) <= eps; } function detectKreaProfileName(modelBlob, exactProfile) { const blob = String(modelBlob || "").toLowerCase(); const hasTurbo = /turbo/.test(blob); const hasRaw = /\braw\b|_raw\b|-raw\b|\/raw\b/.test(blob); if (hasTurbo) { return "turbo"; } if (hasRaw) { return "raw"; } void exactProfile; return "raw"; } function profileParamDefaults(profiles, profileName, generation = {}) { const profile = String(profileName || "raw"); const fromProfile = profiles && typeof profiles[profile] === "object" ? profiles[profile] : {}; const gen = generation && typeof generation === "object" ? generation : {}; return { profile, steps: fromProfile.steps ?? gen.steps ?? null, cfg: fromProfile.cfg ?? gen.cfg ?? null, sigma_shift: fromProfile.sigma_shift ?? gen.sigma_shift ?? null }; } function liveMatchesExactProfile(live, defaults) { if (!defaults || defaults.steps == null || defaults.cfg == null) { return true; } const stepsOk = live?.steps == null || numClose(live.steps, defaults.steps, 0.5); const cfgOk = live?.cfg == null || numClose(live.cfg, defaults.cfg); const sigmaOk = defaults.sigma_shift == null || live?.sigma_shift == null || numClose(live.sigma_shift, defaults.sigma_shift); return stepsOk && cfgOk && sigmaOk; } function exactKeysToForce(live, defaults, { patch = null, sessionExact = null, userParamIntent = false } = {}) { if (!defaults) { return []; } const out = []; for (const key of ["steps", "cfg", "sigma_shift"]) { const want = defaults[key]; if (want == null) { continue; } const eps = key === "steps" ? 0.5 : 0.051; if (userParamIntent && patch && patch[key] != null && !numClose(patch[key], want, eps)) { continue; } if (userParamIntent && sessionExact && sessionExact[key] != null) { continue; } const have = live?.[key]; if (have == null || !numClose(have, want, eps)) { out.push(key); } } return out; } function attachKreaProfile(SA2) { SA2.kreaProfile = { numClose, detectKreaProfileName, profileParamDefaults, liveMatchesExactProfile, exactKeysToForce }; } // src/aspect.js var SWARM_ASPECT_REF_512 = { "1:1": [512, 512], "4:3": [576, 448], "3:2": [608, 416], "8:5": [608, 384], "16:9": [672, 384], "21:9": [768, 320], "2:3": [416, 608], "5:8": [384, 608], "9:16": [384, 672], "9:21": [320, 768] }; var ASPECT_ALIASES = { "2.35:1": "21:9" }; var KREA_EXTRA = { "4:5": [928, 1152] }; function roundTo16(n) { return Math.round(n / 16) * 16; } function swarmSizeFromRef(aspectKey, sideLen = 1024) { const key = ASPECT_ALIASES[aspectKey] || aspectKey; if (KREA_EXTRA[key]) { return [...KREA_EXTRA[key]]; } const ref = SWARM_ASPECT_REF_512[key]; if (!ref) { return null; } const scale = sideLen / 512; return [roundTo16(ref[0] * scale), roundTo16(ref[1] * scale)]; } function buildDefaultAspectTable(sideLen = 1024) { const table = {}; for (const key of Object.keys(SWARM_ASPECT_REF_512)) { table[key] = swarmSizeFromRef(key, sideLen); } table["2.35:1"] = swarmSizeFromRef("21:9", sideLen); for (const [key, size] of Object.entries(KREA_EXTRA)) { table[key] = [...size]; } return table; } var DEFAULT_ASPECT_TABLE = buildDefaultAspectTable(1024); function applyAspectTableFromObject(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])]; } } return Object.keys(next).length ? next : false; } // src/app.js (function() { const LS_BASE = "swarm_assistent_base_url"; const LS_MODEL = "swarm_assistent_model"; const LS_EMBED = "swarm_assistent_embed_model"; const LS_PACK = "swarm_assistent_pack"; const LS_PERSONA = "swarm_assistent_persona"; const LS_VIEW = "swarm_assistent_view"; const LS_AUTO_VISION = "swarm_assistent_auto_vision"; const LS_AUTO_APPLY = "swarm_assistent_auto_apply"; const LS_AUTO_GENERATE = "swarm_assistent_auto_generate"; const LS_AUTO_CRITIQUE = "swarm_assistent_auto_critique"; const LS_AUTO_DOWNLOAD = "swarm_assistent_auto_download"; const LS_PARK_LLM = "swarm_assistent_park_llm"; const LS_PANE_WIDTH = "swarm_assistent_pane_width"; const LS_WELCOMED = "swarm_assistent_welcomed"; const LS_CHATS2 = "swarm_assistent_chats_v1"; const LS_BOARD_TAB = "swarm_assistent_board_tab"; const LS_CHATS_DRAWER = "swarm_assistent_chats_drawer"; const MAX_CHATS = 40; const MAX_CHAT_MSGS = 24; const TAB_BUTTON_ID = "maintab_assistent"; const GEN_ID = "generate"; let MAX_REF_SLOTS = 4; let MAX_GEN_VARIANTS = 4; let CONTEXT_PROMPT_MAX = 2e3; let HISTORY_KEEP_TURNS = 4; let INVENTORY_PROMPT_RICH = 12; let INVENTORY_PROMPT_NAMES = 24; let COMPRESS_AT = 0.7; let CHARS_PER_TOKEN = 3.2; let COMPRESS_AUTO = true; let ASPECT_TABLE = { ...DEFAULT_ASPECT_TABLE }; let PACK_ALIASES = { ordinary: "ordinary", combine: "ordinary", normal: "ordinary", general: "ordinary", default: "ordinary", write: "write_prompt", write_prompt: "write_prompt", critique: "critique_image", critique_image: "critique_image", compose: "compose_scene", compose_scene: "compose_scene", params: "fix_params", fix_params: "fix_params", inpaint: "inpaint_edit", inpaint_edit: "inpaint_edit", describe: "describe_ref", describe_ref: "describe_ref" }; let WELCOME_HTML = `
Assistent \xB7 Krea 2
\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 \u0441\u043F\u0438\u0441\u043A\u0435) /history \u2014 \u043E\u0442\u043A\u0440\u044B\u0442\u044C \u0438\u043B\u0438 \u0441\u043A\u0440\u044B\u0442\u044C \u043F\u0430\u043D\u0435\u043B\u044C \u0447\u0430\u0442\u043E\u0432 /compress \u2014 \u0441\u0436\u0430\u0442\u044C \u0441\u0442\u0430\u0440\u044B\u0435 \u0445\u043E\u0434\u044B \u0432 \u0441\u0430\u043C\u043C\u0430\u0440\u0438 /debug \u2014 \u0441\u0432\u043E\u0434\u043A\u0430 UI/Exact (\u0431\u0435\u0437 LLM) /debug ask \u2014 \u0442\u043E \u0436\u0435 + \u043A\u043E\u0440\u043E\u0442\u043A\u0438\u0439 \u043E\u0442\u0432\u0435\u0442 \u043C\u043E\u0434\u0435\u043B\u0438 /why \u2014 \u0441\u0440\u0430\u0437\u0443 /debug ask /gen \u2014 Generate \u0441\u0435\u0439\u0447\u0430\u0441 /look generate|refN \u2014 \u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u043A\u0430\u0434\u0440 \u043C\u043E\u0434\u0435\u043B\u0438 (vision) /init /mask /clear \u2014 Init / Mask / Clear Init /interrupt \u2014 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u044E /aspect 16:9 \u2014 \u0440\u0430\u0437\u043C\u0435\u0440 \u0438\u0437 \u0442\u0430\u0431\u043B\u0438\u0446\u044B 1K /seed lock|random \u2014 \u0437\u0430\u0444\u0438\u043A\u0441\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0438\u043B\u0438 \u0440\u0430\u043D\u0434\u043E\u043C\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C seed /vary \u2014 \u043D\u043E\u0432\u044B\u0439 seed, \u0442\u043E\u0442 \u0436\u0435 \u043F\u0440\u043E\u043C\u043F\u0442 /pack write|critique|compose|params|inpaint|describe /inventory \u2014 rescan \u043C\u043E\u0434\u0435\u043B\u0435\u0439 + \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u043F\u0438\u0441\u043E\u043A LoRA \u0427\u0438\u043F\u0441\u044B \u043D\u0430\u0434 \u043F\u043E\u043B\u0435\u043C \u0432\u0432\u043E\u0434\u0430 \u0434\u0435\u043B\u0430\u044E\u0442 \u0442\u043E \u0436\u0435 \u0434\u043B\u044F aspect / seed / vary / Turbo\xB7RAW. \u041F\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435 \u0432\u0441\u0435\u0433\u0434\u0430 \u043D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442; \u0441\u043C\u0435\u043D\u0430 \u0447\u0430\u0442\u0430 \u0432 \u043F\u0430\u043D\u0435\u043B\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: "\u043F\u0430\u043D\u0435\u043B\u044C \u0447\u0430\u0442\u043E\u0432" }, { cmd: "/compress", hint: "\u0441\u0436\u0430\u0442\u044C \u0441\u0442\u0430\u0440\u044B\u0435 \u0445\u043E\u0434\u044B" }, { cmd: "/debug", hint: "\u0441\u0432\u043E\u0434\u043A\u0430 \xB7 ask = \u0441 LLM" }, { cmd: "/why", hint: "debug + \u043F\u043E\u044F\u0441\u043D\u0435\u043D\u0438\u0435 LLM" }, { cmd: "/gen", hint: "Generate \u0441\u0435\u0439\u0447\u0430\u0441" }, { cmd: "/look ", hint: "generate|refN" }, { cmd: "/init", hint: "\u043A\u0430\u043A Init" }, { cmd: "/mask", hint: "\u043A\u0430\u043A Mask" }, { cmd: "/clear", hint: "\u0441\u0431\u0440\u043E\u0441 Init/Mask" }, { cmd: "/interrupt", hint: "\u0441\u0442\u043E\u043F" }, { cmd: "/aspect ", hint: "16:9" }, { cmd: "/seed ", hint: "lock|random" }, { cmd: "/vary", hint: "\u043D\u043E\u0432\u044B\u0439 seed" }, { cmd: "/pack ", hint: "write|critique|\u2026" }, { cmd: "/inventory", hint: "rescan \u043C\u043E\u0434\u0435\u043B\u0435\u0439" } ]; const state = { history: [], config: null, exact: null, sessionExact: {}, lastUserParamIntent: false, lastUserControlIntent: false, lastPatch: null, pendingSilentGen: false, /** This user turn asked to draw — hops inherit it; model generate:true does not. */ turnUserWantsGenerate: 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, /** Chat provider mirrored from server settings: 'ollama' | 'openrouter'. */ provider: "ollama", chatModels: [], chatModelsPreferred: "", visionModels: [], openrouterModelSaved: "", inventory: { loras: [], checkpoints: [], wildcards: [], has_civitai_key: false }, inventoryFetchedAt: 0, streamEl: null, streamMeta: null, streamText: "", streamFenceDone: false, turnHops: [], lastSystemChars: 0, lastSystemLayers: null, lastContextChars: 0, lastPromptEvalCount: null, contextMemory: null, ctxPanelOpen: false, compressing: false, busyPhase: "idle", busyStarted: 0, gotDelta: false, busyTimer: null, lastDeltaAt: 0, streamStallTimer: null, turnSettled: false, lastBusyPhaseShown: "", slots: [], selectedSlotId: "ref1", refSeq: 1, genResults: [], selectedGenResultId: null, lightboxIndex: -1, packUserTouched: false, view: "chat", boardTab: "generate", personas: [], chatSession: null, pendingPersonaNote: null, chats: [], activeChatId: null, restoringChat: false, chatsPanelOpen: false, chatsDrawerOpen: false, chatsQuery: "", chatsSearchHits: null, slashIndex: 0, llmParked: false, expectColdLoad: false, memoryRows: [], userPrefs: [], settingsTab: "behavior", settingsPersonaId: null, ollamaHealth: "unknown", trainingLock: false, activity: null, pendingActivityTrace: null }; function getActivity() { if (state.activity) { return state.activity; } if (window.SA && typeof SA.createActivityController === "function") { state.activity = SA.createActivityController({ getMessagesEl: () => $("sa_messages"), scrollToBottom: () => scrollMessagesToBottom(), hideEmpty: () => hideChatEmpty() }); } return state.activity; } function activityBegin(title, context) { const a = getActivity(); if (a) { a.begin(title || "Assistent", context || {}); } } function captureActivityTrace() { const a = getActivity(); if (!a || typeof a.snapshot !== "function") { return null; } const snap = a.snapshot(); if (snap && snap.steps && snap.steps.length) { state.pendingActivityTrace = snap; return snap; } return null; } function activityStep(id, patch) { const a = getActivity(); if (a) { a.upsert(id, patch); } } function activityDone(id, patch) { const a = getActivity(); if (a) { a.done(id, patch); } } function activityFinish(summary) { const a = getActivity(); if (a) { a.finish(summary); captureActivityTrace(); } } const HOP_BUDGET = 4; function isContinuationTurn(opts) { return !!(opts && (opts.fromVisionHop || opts.fromAutoCritique || opts.fromPromptEnRetry || opts.fromAskHop)); } function isMachineTurn(opts) { return isContinuationTurn(opts) || !!(opts && opts.fromDebug); } function resetTurnHops() { state.turnHops = []; state.pendingPromptEnMerge = null; } function turnHopUsed(kind) { return (state.turnHops || []).includes(kind); } function claimTurnHop(kind) { if (!Array.isArray(state.turnHops)) { state.turnHops = []; } if (state.turnHops.includes(kind) || state.turnHops.length >= HOP_BUDGET) { return false; } state.turnHops.push(kind); return true; } function diskPersist() { return window.SA && window.SA.persist || null; } function $(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 = $("sa_chat_empty"); if (empty) { empty.hidden = true; } } let scrollMessagesRaf = 0; function messagesNearBottom(thresholdPx = 96) { const box = $("sa_messages"); if (!box) { return true; } return box.scrollHeight - box.scrollTop - box.clientHeight <= thresholdPx; } function scrollMessagesToBottom({ force = false } = {}) { const box = $("sa_messages"); if (!box) { return; } if (!force && !messagesNearBottom()) { return; } if (scrollMessagesRaf) { return; } scrollMessagesRaf = requestAnimationFrame(() => { scrollMessagesRaf = 0; const el = $("sa_messages"); if (el && (force || messagesNearBottom(120))) { el.scrollTop = el.scrollHeight; } }); } function showChatEmptyIfIdle() { const box = $("sa_messages"); const empty = $("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($("sa_model")?.value); const labels = { encoding: "Encoding image\u2026", waiting: "\u0416\u0434\u0443 Ollama / \u043F\u0435\u0440\u0432\u044B\u0439 \u0442\u043E\u043A\u0435\u043D\u2026", loading: `\u0417\u0430\u0433\u0440\u0443\u0436\u0430\u044E ${model} \u0432 GPU\u2026 \u043E\u0431\u044B\u0447\u043D\u043E 30\u2013120 \u0441 \u043F\u043E\u0441\u043B\u0435 park`, warming: `\u0412\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u044E ${model} \u0432 GPU\u2026`, thinking: "Thinking\u2026", streaming: "Writing\u2026", generating: "Generating image\u2026", parking: "\u041E\u0441\u0432\u043E\u0431\u043E\u0436\u0434\u0430\u044E VRAM (park LLM)\u2026", applying: "Applying patch\u2026", silent_gen: "\u041F\u0440\u0438\u043C\u0435\u043D\u044F\u044E \u043F\u0430\u0442\u0447 \u2192 Generate\u2026", refining: "\u0423\u0442\u043E\u0447\u043D\u044F\u044E \u043E\u0442\u0432\u0435\u0442\u2026", compressing: "\u0421\u0436\u0438\u043C\u0430\u044E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u2026" }; const text = labels[state.busyPhase] || "Working\u2026"; const phaseKind = { thinking: "think", streaming: "stream", waiting: "think", loading: "warm", warming: "warm", parking: "park", encoding: "look", generating: "generate", applying: "merge", silent_gen: "generate", refining: "prep", compressing: "compress" }; if (state.lastBusyPhaseShown !== state.busyPhase) { state.lastBusyPhaseShown = state.busyPhase; if (state.busyPhase === "streaming" || state.busyPhase === "refining") { activityDone("think"); } activityStep(`phase:${state.busyPhase}`, { kind: phaseKind[state.busyPhase] || "think", label: text, status: "running" }); const a = getActivity(); if (a && Array.isArray(a.steps)) { for (const s of a.steps) { if (s.id.startsWith("phase:") && s.id !== `phase:${state.busyPhase}` && s.status === "running") { a.done(s.id); } } } } const barText = $("sa_livebar_text"); if (barText) { barText.textContent = text; } const elapsedEl = $("sa_elapsed"); if (elapsedEl) { elapsedEl.textContent = fmtElapsed(elapsed); } const status = $("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"; $("swarm_assistent_root")?.classList.add("sa-is-busy"); $("sa_composer")?.classList.add("sa-composer-busy"); state.lastBusyPhaseShown = ""; const send = $("sa_btn_send"); if (send) { send.disabled = false; } const input = $("sa_input"); if (input) { input.readOnly = false; input.disabled = false; input.classList.add("sa-input-busy"); } const bar = $("sa_livebar"); if (bar) { bar.hidden = false; } const dot = $("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; } clearStreamStall(); const elapsed = Date.now() - (state.busyStarted || Date.now()); state.busyPhase = "idle"; state.lastBusyPhaseShown = ""; activityFinish(finalStatus || "\u0413\u043E\u0442\u043E\u0432\u043E"); $("swarm_assistent_root")?.classList.remove("sa-is-busy"); $("sa_composer")?.classList.remove("sa-composer-busy"); const send = $("sa_btn_send"); if (send) { send.disabled = false; } const input = $("sa_input"); if (input) { input.classList.remove("sa-input-busy"); } const bar = $("sa_livebar"); if (bar) { bar.hidden = true; } const dot = $("sa_live_dot"); if (dot) { dot.hidden = true; } const status = $("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 = $("sa_status"); if (el) { el.textContent = text || ""; } } function setInterruptVisible(on) { const btn = $("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; } } let lastExactForceCkpt = null; function updateGate() { const ok = isKreaSelected(); const gate = $("sa_gate"); const layout = $("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 Krea 2 models only. Current: ${escapeHtml( seen )} \u2014 pick a checkpoint with architecture krea-2.` : "Swarm Assistent is for Krea 2 models only. Select a Krea 2 checkpoint on Generate to enable the chat."; } } } if (layout) { layout.classList.toggle("sa-disabled", !ok); } try { const ckptName = resolveCurrentCheckpoint()?.name || null; if (ok && ckptName && ckptName !== lastExactForceCkpt) { const defs = typeof exactProfileDefaults === "function" ? exactProfileDefaults(detectKreaProfileName2()) : null; if (defs && defs.steps != null && defs.cfg != null) { lastExactForceCkpt = ckptName; forceExactParamsForGenerate({}); } } else if (!ok) { lastExactForceCkpt = null; } } catch (e) { } return ok; } function escapeHtml(s) { return String(s).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } 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, '$1'); t = t.replace(/\*\*([^*]+)\*\*/g, "$1"); t = t.replace(/(^|[^*])\*([^*\n]+)\*(?!\*)/g, "$1$2"); return t; } function stripChatFenceArtifacts(raw) { if (window.SA && typeof SA.stripFenceArtifacts === "function") { return SA.stripFenceArtifacts(raw); } let text = String(raw || "").replace(/\r\n/g, "\n"); text = text.replace(/```(?:json)?\s*[\s\S]*?```/gi, ""); text = text.replace(/```(?:json)?\s*[\s\S]*$/gi, ""); return text.replace(/\n{3,}/g, "\n\n").trim(); } function liveAssistantText(raw) { if (window.SA && typeof SA.liveStreamProse === "function") { return SA.liveStreamProse(raw); } return stripChatFenceArtifacts(raw) || String(raw || ""); } function formatAssistantProseHtml(raw) { let text = stripChatFenceArtifacts(raw); if (!text) { return ""; } const lines = text.split("\n"); const parts = []; let listItems = []; const flushList = () => { if (!listItems.length) { return; } parts.push( `` ); 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( `
${escapeHtml(title)}
` ); continue; } const bullet = line.match(/^\s*[-*•]\s+(.+)$/); if (bullet) { listItems.push(bullet[1]); continue; } flushList(); if (!line.trim()) { parts.push(''); continue; } parts.push(`

${formatProseInline(escapeHtml(line))}

`); } 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 = liveAssistantText(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; } const next = value == null ? "" : String(value); if (el.value === next) { return; } el.value = next; if ((state._quietParamApply || 0) > 0) { return; } el.dispatchEvent(new Event("input", { bubbles: true })); el.dispatchEvent(new Event("change", { bubbles: true })); } const QUICK_PARAM_KEYS = /* @__PURE__ */ new Set([ "aspect", "width", "height", "steps", "cfg", "sigma_shift", "seed", "vary", "lock_seed", "sampler", "scheduler", "batch", "images" ]); function isParamOnlyQuickPatch(patch) { if (!patch || typeof patch !== "object") { return false; } const keys = Object.keys(patch).filter((k) => patch[k] != null && k !== "notes"); return keys.length > 0 && keys.every((k) => QUICK_PARAM_KEYS.has(k)); } 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 cyrTokenRe2(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 (cyrTokenRe2("\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 (cyrTokenRe2("\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 cyrTokenRe2("\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|sampler|scheduler|семплер|шедулер)\b/i.test(t)) { return true; } return cyrTokenRe2( "\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]*|\u0448\u0430\u0433|\u0448\u0430\u0433\u043E\u043C|\u0448\u0430\u0433\u0430\u043C\u0438|\u0448\u0430\u0433\u043E\u0432|\u0448\u0430\u0433\u0430" ).test(t); } function replyMissingJsonPatch(reply) { const t = String(reply || ""); if (!t.trim()) { return false; } if (/```(?:json)?\s*\{[\s\S]*?\}```/i.test(t)) { return false; } return /###\s*JSON\s*Patch\b/i.test(t) || /JSON\s*Patch\s*:?\s*$/im.test(t); } function userAsksContinue(text) { const t = String(text || "").trim(); if (!t) { return false; } if (/^(давай\s+дальше|продолжай|продолжим|go\s+on|continue|keep\s+going|next(\s+one)?|next\s+frame)([!.…\s]|$)/i.test(t)) { return true; } return cyrTokenRe2( "\u0434\u0430\u0432\u0430\u0439\\s+\u0434\u0430\u043B\u044C\u0448\u0435|\u0441\u043B\u0435\u0434\u0443\u044E\u0449(\u0438\u0439|\u0430\u044F|\u0435\u0435|\u0443\u044E)\\s+\u043A\u0430\u0434\u0440|\u0435\u0449\u0451\\s+\u043A\u0430\u0434\u0440|\u0435\u0449\u0435\\s+\u043A\u0430\u0434\u0440|\u043A\u0430\u0434\u0440\\s*\u2116?\\s*\\d+|\u0441\u0434\u0435\u043B\u0430\u0439\\s+\u0441\u043B\u0435\u0434\u0443\u044E\u0449" ).test(t); } function extractPromptFromProse(reply) { const t = String(reply || "").replace(/\r\n/g, "\n"); if (!t.trim()) { return null; } const bq = []; for (const line of t.split("\n")) { const m = line.match(/^\s{0,3}>\s?(.*)$/); if (m) { bq.push(m[1]); continue; } if (bq.length) { break; } } const fromBq = bq.join("\n").trim(); if (fromBq.length >= 48) { return fromBq.slice(0, 4e3); } const section = t.match( /(?:^|\n)#{1,6}\s*(?:📷\s*)?(?:prompt|промпт|improved\s+prompt|next\s+prompt|кадр[^\n]*)\s*\n+([\s\S]+?)(?=\n#{1,6}\s|\n```|$)/i ); if (section) { const body = section[1].replace(/^\s{0,3}>\s?/gm, "").trim(); if (body.length >= 48) { return body.slice(0, 4e3); } } return null; } function promptLooksKreaReady(prompt) { const t = String(prompt || "").trim(); if (t.length < 80) { return false; } const cyr = (t.match(/[\u0400-\u04FF]/g) || []).length; const lat = (t.match(/[A-Za-z]/g) || []).length; if (cyr >= 12) { return false; } if (lat < 55) { return false; } if (t.length < 120 && (t.match(/[,.;:]/g) || []).length < 2) { return false; } return true; } function promptNeedsKreaPrep(prompt) { return !promptLooksKreaReady(prompt); } function buildKreaPromptPrepRequest(patch) { const keep = { actions: Array.isArray(patch.actions) && patch.actions.length ? patch.actions : ["generate"] }; if (patch.aspect) { keep.aspect = patch.aspect; } if (patch.negative != null && String(patch.negative).trim() !== "") { keep.negative = patch.negative; } else { const liveNeg = liveNegativePrompt() || exactDefaultNegative(); if (liveNeg) { keep.negative = liveNeg; } } if (Array.isArray(patch.loras)) { keep.loras = patch.loras; } if (patch.width != null) { keep.width = patch.width; } if (patch.height != null) { keep.height = patch.height; } return `You are the Krea 2 prompt prep step (chat model). Rewrite SOURCE into the final Swarm Generate box text. HARD RULES: - JSON "prompt": English only (no Cyrillic) \u2014 translate if needed. - Natural photographer/director prose for Qwen3-VL \u2014 not Danbooru tags, not (word:1.5), not masterpiece/best quality/8k. - Structure & front-load: subject \u2192 pose/action \u2192 body/wardrobe \u2192 setting \u2192 materials/textures \u2192 camera/framing \u2192 lighting \u2192 medium/mood. - Expand thin ideas; fix anti-patterns; one coherent scene. - Keep LoRA trigger phrases in English near the subject they affect. - Always include JSON "negative": keep/supplement SOURCE+live negative, or use Exact default if empty. Never drop it. - Put \u201Cno blur / empty street\u201D ideas as positives in prompt, not as a huge negative dump. - One short ack in the user language max, then ONE fenced JSON merging these keys: ${JSON.stringify(keep)} plus the new English "prompt" and "negative". - Include actions:["generate"] when an image was requested. SOURCE: ${patch.prompt}`; } function mergePromptEnRewrite(effective) { const base = state.pendingPromptEnMerge; state.pendingPromptEnMerge = null; if (!base) { return effective; } if (!effective) { return { ...base, generate: true, actions: Array.isArray(base.actions) && base.actions.length ? base.actions : ["generate"] }; } 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 userAsksNoGenerate2(text) { const t = String(text || "").trim(); if (!t) { return false; } if (/\b(remember|save\s+(this\s+)?(as\s+)?(the\s+)?(base\s+)?(prompt|template)|don'?t\s+generat|do\s+not\s+generat|no\s+generat|without\s+generat)\b/i.test(t)) { return true; } return cyrTokenRe2( "\u0437\u0430\u043F\u043E\u043C\u043D|\u0437\u0430\u043F\u043E\u043C\u043D\u0438|\u0437\u0430\u043F\u043E\u043C\u043D\u0438\u043C|\u0441\u043E\u0445\u0440\u0430\u043D\u0438|\u0441\u043E\u0445\u0440\u0430\u043D\u0438\u043C|\u0448\u0430\u0431\u043B\u043E\u043D|\u0431\u0430\u0437\u043E\u0432(\u044B\u0439|\u043E\u0433\u043E|\u043E\u043C\u0443|\u044B\u043C|\u0430\u044F|\u0443\u044E|\u043E\u0435)?\\s+\u043F\u0440\u043E\u043C\u043F\u0442|\u043D\u0435\\s+\u0433\u0435\u043D\u0435\u0440\u0438\u0440[\u0430-\u044F\u0451]*|\u0431\u0435\u0437\\s+\u0433\u0435\u043D\u0435\u0440\u0430\u0446[\u0430-\u044F\u0451]*|\u043D\u0435\\s+\u043D\u0430\u0434\u043E\\s+\u0433\u0435\u043D\u0435\u0440[\u0430-\u044F\u0451]*|\u0442\u043E\u043B\u044C\u043A\u043E\\s+\u0437\u0430\u043F\u043E\u043C\u043D[\u0430-\u044F\u0451]*|\u043F\u043E\u043A\u0430\\s+\u0437\u0430\u043F\u043E\u043C\u043D[\u0430-\u044F\u0451]*|\u043D\u0435\\s+\u0440\u0438\u0441\u0443\u0439|\u043D\u0435\\s+\u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0439\\s+\u0433\u0435\u043D\u0435\u0440[\u0430-\u044F\u0451]*" ).test(t); } function userAsksLook(text) { const t = String(text || "").trim(); if (!t) { return false; } if (/\b(look\s+at|critique|criticize|describe\s+(this|the|ref|image)|what\s+do\s+you\s+see)\b/i.test(t)) { return true; } if (cyrTokenRe2("\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 (cyrTokenRe2("\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 userAsksGenerate2(text) { if (window.SA && typeof SA.userAsksGenerate === "function") { return SA.userAsksGenerate(text); } const t = String(text || "").trim(); if (!t || userAsksNoGenerate2(t)) { return false; } if (/\b(generat(e|ion)|draw|render|make\s+(an?\s+)?image|run\s+generate)\b/i.test(t)) { return true; } return cyrTokenRe2( "\u0441\u0433\u0435\u043D\u0435\u0440[\u0430-\u044F\u0451]*|\u043D\u0430\u0440\u0438\u0441\u0443\u0439|\u043D\u0430\u0440\u0438\u0441\u0443\u0439\u0442\u0435|\u043D\u0430\u0440\u0438\u0441\u0443\u0435\u043C|\u0437\u0430\u043F\u0443\u0441\u0442\u0438\\s+\u0433\u0435\u043D\u0435\u0440[\u0430-\u044F\u0451]*|\u0441\u0434\u0435\u043B\u0430(\u0439|\u0435\u043C|\u0439\u0442\u0435)\\s+(\u043A\u0430\u0434\u0440|\u043A\u0430\u0440\u0442\u0438\u043D\u043A[\u0430-\u044F\u0451]*|\u0438\u0437\u043E\u0431\u0440\u0430\u0436[\u0430-\u044F\u0451]*)" ).test(t); } function packWantsVision(pack) { const p = String(pack || ""); return p === "critique_image" || p === "describe_ref" || p === "compose_scene" || p === "inpaint_edit"; } function resolveTurnIntent3(patch, userText, opts = {}) { const S = window.SA && window.SA.session; if (S && typeof S.resolveTurnIntent === "function") { return S.resolveTurnIntent(patch, userText, { vetoFn: userAsksNoGenerate2, askGenerateFn: userAsksGenerate2, fromAutoCritique: !!opts.fromAutoCritique, sessionPrompt: opts.sessionPrompt || "", userWantsGenerate: !!opts.userWantsGenerate }); } const vetoed = !isMachineTurn(opts) && userAsksNoGenerate2(userText); const modelAsked = !!(patch && (patch.generate === true || patch.generate === 1 || typeof patch.generate === "string" && /^(true|1|yes|on)$/i.test(patch.generate) || Array.isArray(patch.actions) && patch.actions.map(String).includes("generate"))); const userAsked = !isMachineTurn(opts) && userAsksGenerate2(userText); const generate = !vetoed && !opts.fromAutoCritique && (userAsked || !!opts.userWantsGenerate); const hasLook = !!patch && (patch.look_at != null || patch.vision_from != null || patch.vision_slots != null); const look = !!(hasLook && !vetoed && !generate); const ask = Array.isArray(patch?.ask) ? patch.ask.map(String) : typeof patch?.ask === "string" && patch.ask ? [patch.ask] : []; return { generate, look, vetoed, ask, modelAsked }; } function stripGenerateAction(patch) { if (!patch || typeof patch !== "object") { return patch; } if (!Array.isArray(patch.actions)) { return patch; } const next = patch.actions.map(String).filter((a) => a !== "generate"); if (next.length === patch.actions.length) { return patch; } const out = { ...patch }; if (next.length) { out.actions = next; } else { delete out.actions; } return out; } function stripLookAt(patch) { if (!patch || typeof patch !== "object") { return patch; } if (patch.look_at == null && patch.vision_from == null && patch.vision_slots == null) { return patch; } const out = { ...patch }; delete out.look_at; delete out.vision_from; delete out.vision_slots; return out; } function rememberLastPatch(patch) { if (patch && typeof patch === "object") { state.lastPatch = patch; syncBuildGenButton(); } } function syncBuildGenButton() { const btn = $("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 || $("sa_pack")?.querySelector("option")?.value || "ordinary"; } function syncModeBadge() { const badge = $("sa_mode_badge"); const pack = $("sa_pack")?.value || defaultPackId(); if (!badge) { return; } const shortMap = { ordinary: "\u043E\u0431\u044B\u0447\u043D\u044B\u0439", write_prompt: "write", critique_image: "critique", compose_scene: "compose", fix_params: "params", inpaint_edit: "inpaint", describe_ref: "describe", author_persona: "persona" }; const short = shortMap[pack] || pack.replace(/_/g, " ").slice(0, 12); badge.textContent = short; badge.dataset.pack = pack; badge.title = `\u0420\u0435\u0436\u0438\u043C: ${pack}`; badge.classList.toggle("sa-mode-hot", pack === "critique_image" || pack === "inpaint_edit"); } function valuesMatchParam(a, b, key) { if (a == null && b == null) { return true; } if (a == null || b == null) { return false; } const numericKeys = /* @__PURE__ */ new Set([ "steps", "cfg", "sigma_shift", "seed", "batch", "images", "width", "height", "init_creativity" ]); if (numericKeys.has(key)) { const na = parseFloat(String(a).replace(",", ".")); const nb = parseFloat(String(b).replace(",", ".")); if (Number.isFinite(na) && Number.isFinite(nb)) { if (key === "cfg" || key === "sigma_shift" || key === "init_creativity") { return Math.abs(na - nb) < 0.011; } return na === nb; } } return String(a).toLowerCase() === String(b).toLowerCase(); } function canonicalParamDefaults(profileName) { const profile = profileName || detectKreaProfileName2(); const profDefs = exactProfileDefaults(profile); const gen = state.exact?.generation || {}; const defaultAspect = gen.aspect || "1:1"; const defaultSize = sizeFromAspect(defaultAspect) || ASPECT_TABLE["1:1"] || [1024, 1024]; const batchDefault = gen.images ?? gen.batch ?? 1; return { profile, aspect: defaultAspect, width: defaultSize[0], height: defaultSize[1], steps: profDefs.steps, cfg: profDefs.cfg, sigma_shift: profDefs.sigma_shift, seed: "-1", batch: batchDefault, images: batchDefault, sampler: exactDefaultFor("sampler", profile), scheduler: exactDefaultFor("scheduler", profile) }; } function detectAppliedProfileName() { const { profiles } = resolveExactBundle(); const stepsLive = val("input_steps"); const cfgLive = val("input_cfgscale") || val("input_cfg"); for (const name of ["turbo", "raw"]) { const d = profiles[name]; if (!d) { continue; } if (valuesMatchParam(stepsLive, d.steps, "steps") && valuesMatchParam(cfgLive, d.cfg, "cfg")) { return name; } } return null; } function paramDisplayTag(label, displayValue, isCustom, defaultHint) { const cls = isCustom ? "sa-param-tag sa-param-custom" : "sa-param-tag"; const hint = isCustom && defaultHint != null ? ` title="Exact: ${defaultHint}"` : ""; return `${escapeHtml(label)} ${escapeHtml(String(displayValue))}`; } function buildLiveParamsHtml() { const ckptProfile = detectKreaProfileName2(); const defs = canonicalParamDefaults(ckptProfile); 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 stepsRaw = val("input_steps"); const cfgRaw = val("input_cfgscale") || val("input_cfg"); const sigmaRaw = val("input_sigmashift"); const seedRaw = val("input_seed") || "-1"; const batchRaw = val("input_images") || val("input_batchsize") || "1"; const samplerRaw = val("input_sampler") || defs.sampler || "euler"; const schedulerRaw = val("input_scheduler") || defs.scheduler || "normal"; const appliedProfile = detectAppliedProfileName() || ckptProfile; const tags = [ paramDisplayTag( "aspect", aspect, aspect !== "\u2014" && !valuesMatchParam(aspect, defs.aspect, "aspect"), defs.aspect ), paramDisplayTag( "size", `${w || "?"}\xD7${h || "?"}`, !!(w && h && (!valuesMatchParam(w, defs.width, "width") || !valuesMatchParam(h, defs.height, "height"))), `${defs.width}\xD7${defs.height}` ), paramDisplayTag( "profile", appliedProfile, appliedProfile !== ckptProfile, ckptProfile ), paramDisplayTag( "steps", stepsRaw || "\u2014", !!(stepsRaw && !valuesMatchParam(stepsRaw, defs.steps, "steps")), defs.steps ), paramDisplayTag( "cfg", cfgRaw || "\u2014", !!(cfgRaw && !valuesMatchParam(cfgRaw, defs.cfg, "cfg")), defs.cfg ), paramDisplayTag( "\u03C3", sigmaRaw || (defs.sigma_shift != null ? defs.sigma_shift : "\u2014"), !!(sigmaRaw && defs.sigma_shift != null && !valuesMatchParam(sigmaRaw, defs.sigma_shift, "sigma_shift")), defs.sigma_shift ), paramDisplayTag( "seed", seedRaw === "-1" || seedRaw === "" ? "rand" : seedRaw, !valuesMatchParam(seedRaw, defs.seed, "seed"), "rand (\u22121)" ), paramDisplayTag( "batch", batchRaw, !valuesMatchParam(batchRaw, defs.batch, "batch"), defs.batch ), paramDisplayTag( "sampler", samplerRaw, !valuesMatchParam(samplerRaw, defs.sampler, "sampler"), defs.sampler ), paramDisplayTag( "scheduler", schedulerRaw, !valuesMatchParam(schedulerRaw, defs.scheduler, "scheduler"), defs.scheduler ) ]; let initCtx = {}; try { initCtx = readInitContext(); } catch (e) { } if (initCtx.has_init_image) { const cr = initCtx.init_creativity; const crStr = cr != null ? `@${cr}` : ""; tags.push(paramDisplayTag("init", `on${crStr}`, true, "off")); } if (initCtx.has_mask_image) { tags.push(paramDisplayTag("mask", "on", true, "off")); } const negLive = liveNegativePrompt(); const negDef = exactDefaultNegative(); if (negLive && negDef && negLive !== negDef) { tags.push(paramDisplayTag("neg", "\xB1", true, "Exact default")); } else if (negLive && !negDef) { tags.push(paramDisplayTag("neg", "on", true, "empty")); } const picCount = typeof countPromptImages === "function" ? countPromptImages() : 0; if (picCount > 0) { tags.push(paramDisplayTag("prompt img", picCount, true, "0")); } return tags.join(""); } function formatLiveParamsLine() { const el = document.createElement("div"); el.innerHTML = buildLiveParamsHtml(); return el.textContent || "\u2014"; } function syncLiveParamsBar() { const html = buildLiveParamsHtml(); const boardEl = $("sa_live_params"); const composerEl = $("sa_composer_params"); if (boardEl) { boardEl.innerHTML = html; } if (composerEl) { composerEl.innerHTML = html; } } function applyAspectTableFrom(obj) { const next = applyAspectTableFromObject(obj); if (!next) { 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 detectKreaProfileName2() { const KP = window.SA?.kreaProfile; try { const model = resolveCurrentCheckpoint(); const blob = `${model?.name || ""} ${model?.title || ""}`; const exactProfile = state.exact?.generation?.profile; if (KP?.detectKreaProfileName) { return KP.detectKreaProfileName(blob, exactProfile); } const lower = blob.toLowerCase(); if (/turbo/.test(lower)) { return "turbo"; } if (/\braw\b|_raw\b|-raw\b/.test(lower)) { return "raw"; } return "raw"; } catch (e) { return state.exact?.generation?.profile || "raw"; } } function exactProfileDefaults(profileName) { const { exact, profiles } = resolveExactBundle(); const profile = profileName || detectKreaProfileName2(); const KP = window.SA?.kreaProfile; if (KP?.profileParamDefaults) { return KP.profileParamDefaults(profiles, profile, exact.generation); } const gen = exact.generation && typeof exact.generation === "object" ? exact.generation : {}; const fromProfile = profiles[profile] && typeof profiles[profile] === "object" ? profiles[profile] : {}; return { profile, steps: fromProfile.steps ?? gen.steps ?? null, cfg: fromProfile.cfg ?? gen.cfg ?? null, sigma_shift: fromProfile.sigma_shift ?? gen.sigma_shift ?? null }; } function readLiveStepsCfgSigma() { return { steps: parseInt(val("input_steps") || "", 10) || null, cfg: parseFloat(val("input_cfgscale") || val("input_cfg") || "") || null, sigma_shift: parseFloat(val("input_sigmashift") || "") || null }; } function setCfgVal(n) { if (document.getElementById("input_cfgscale")) { setVal("input_cfgscale", String(n)); } else if (document.getElementById("input_cfg")) { setVal("input_cfg", String(n)); } } function forceExactParamsForGenerate({ patch = null, profileName = null } = {}) { if (typeof isKreaSelected === "function" && !isKreaSelected()) { return false; } const profile = profileName || detectKreaProfileName2(); const defaults = exactProfileDefaults(profile); const live = readLiveStepsCfgSigma(); const KP = window.SA?.kreaProfile; const userIntent = !!state.lastUserParamIntent; const keys = KP?.exactKeysToForce ? KP.exactKeysToForce(live, defaults, { patch, sessionExact: state.sessionExact, userParamIntent: userIntent }) : ["steps", "cfg", "sigma_shift"].filter((k) => { const want = defaults[k]; if (want == null) { return false; } if (userIntent && patch?.[k] != null && String(patch[k]) !== String(want)) { return false; } if (userIntent && state.sessionExact?.[k] != null) { return false; } return live[k] == null || String(live[k]) !== String(want); }); if (!keys.length) { return false; } for (const key of keys) { const want = defaults[key]; if (key === "steps") { setVal("input_steps", String(want)); } else if (key === "cfg") { setCfgVal(want); } else if (key === "sigma_shift") { setVal("input_sigmashift", String(want)); } if (state.sessionExact && state.sessionExact[key] != null && String(state.sessionExact[key]) !== String(want)) { delete state.sessionExact[key]; } if (state.chatSession?.gen) { state.chatSession.gen[key] = want; } } syncLiveParamsBar(); return true; } function mergedGenerationDefaults(profileName) { const { exact, profiles } = resolveExactBundle(); const gen = exact.generation && typeof exact.generation === "object" ? { ...exact.generation } : {}; const profile = profileName || detectKreaProfileName2(); 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) { if (key === "sampler") { return state.exact?.sampling?.defaults?.sampler ?? "euler"; } if (key === "scheduler") { return state.exact?.sampling?.defaults?.scheduler ?? "normal"; } const { exact, profiles } = resolveExactBundle(); const profile = profileName || exact.generation?.profile || detectKreaProfileName2(); 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 pinUserSessionParams(patch) { if (!patch || typeof patch !== "object") { return; } const partial = {}; for (const k of QUICK_PARAM_KEYS) { if (patch[k] != null) { partial[k] = patch[k]; } } if (Object.keys(partial).length) { rememberSessionExact(partial); } } 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 ensureExactParamsForGenerate(patch) { const S = window.SA && window.SA.session; if (!patch || !S || typeof S.mergeExactParamsForGenerate !== "function") { return patch; } if (!S.patchWantsGenerate(patch)) { return patch; } const { exact, profiles } = resolveExactBundle(); const { patch: next, clearSessionKeys } = S.mergeExactParamsForGenerate(patch, { exact, profiles, profileName: detectKreaProfileName2(), sessionExact: state.sessionExact, userParamIntent: !!state.lastUserParamIntent }); for (const key of clearSessionKeys || []) { if (state.sessionExact && state.sessionExact[key] != null) { delete state.sessionExact[key]; } } if (state.chatSession && state.chatSession.gen) { for (const key of S.EXACT_GENERATE_PARAM_KEYS || ["steps", "cfg", "sigma_shift"]) { if (next[key] != null) { state.chatSession.gen[key] = next[key]; } } for (const key of S.SESSION_PINNED_PARAM_KEYS || ["sampler", "scheduler", "aspect", "seed"]) { if (next[key] != null) { state.chatSession.gen[key] = next[key]; } } } return next; } 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(() => $("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(() => $("sa_input")?.focus(), 50); return !!tab; } function historyMessageLimit() { const turns = Math.max(1, Number(HISTORY_KEEP_TURNS) || 4); return turns * 2; } function ctxApi() { return window.SA?.context || null; } function getContextMemory() { const C = ctxApi(); if (C?.normalizeContextMemory) { return C.normalizeContextMemory(state.contextMemory); } return state.contextMemory && typeof state.contextMemory === "object" ? state.contextMemory : { summary: "", untilCount: 0, foldedTurns: 0, at: 0, uiCollapsed: false, promptEvalCount: null }; } function setContextMemory(raw, { persist = true } = {}) { const C = ctxApi(); state.contextMemory = C?.normalizeContextMemory ? C.normalizeContextMemory(raw) : raw && typeof raw === "object" ? raw : null; if (state.chatSession && typeof state.chatSession === "object") { state.chatSession.context_memory = state.contextMemory?.summary ? state.contextMemory : null; } if (persist && !state.restoringChat) { persistHistory(); } updateCtxChip(); if (state.ctxPanelOpen) { renderCtxPanel(); } } function resetContextMemory({ persist = true } = {}) { setContextMemory(ctxApi()?.emptyContextMemory?.() || { summary: "", untilCount: 0, foldedTurns: 0, at: 0, uiCollapsed: false, promptEvalCount: null }, { persist }); } function historyCharsForBudget(messages) { return (messages || []).reduce((n, m) => n + String(m?.content || "").length, 0); } function currentBudgetEstimate(modelMessages) { const C = ctxApi(); const mem = getContextMemory(); const memChars = mem.summary ? mem.summary.length : 0; const hist = modelMessages || assembleOutgoingMessages(); const numCtx = Number($("sa_num_ctx")?.value) || state.config?.assistant?.num_ctx || 16384; const numPredict = Number(state.config?.assistant?.num_predict) || 3072; if (!C?.estimateBudget) { return { used: 0, numCtx, level: "ok", estimated: 0, fromEval: false, systemChars: state.lastSystemChars || 0, historyChars: historyCharsForBudget(hist), memoryChars: memChars, threshold: Math.floor((numCtx - numPredict) * COMPRESS_AT) }; } return C.estimateBudget({ systemChars: state.lastSystemChars || 0, historyChars: historyCharsForBudget(hist), memoryChars: memChars, numCtx, numPredict, charsPerToken: CHARS_PER_TOKEN, compressAt: COMPRESS_AT, promptEvalCount: state.lastPromptEvalCount ?? mem.promptEvalCount }); } function assembleOutgoingMessages({ includePendingUser } = {}) { const C = ctxApi(); const mem = getContextMemory(); let msgs; if (C?.assembleModelMessages) { msgs = C.assembleModelMessages(state.history, mem, HISTORY_KEEP_TURNS).map((m) => { let content = String(m.content || ""); if (m.role === "assistant") { content = stripJsonFencesForHistory(content); } return { role: m.role, content: content.slice(0, 4e3) }; }); } else { msgs = state.history.slice(-historyMessageLimit()).map((m) => { let content = String(m.content || ""); if (m.role === "assistant") { content = stripJsonFencesForHistory(content); } return { role: m.role, content: content.slice(0, 4e3) }; }); } if (includePendingUser) { msgs.push({ role: "user", content: String(includePendingUser) }); } return msgs; } function buildCompressUserPrompt() { const C = ctxApi(); const mem = getContextMemory(); const fold = C?.messagesToFold ? C.messagesToFold(state.history, mem, HISTORY_KEEP_TURNS) : []; const lines = []; if (mem.summary) { lines.push("## Previous conversation memory"); lines.push(mem.summary); lines.push(""); } lines.push("## Dialogue chunk to fold"); for (const m of fold) { const role = m.role === "assistant" ? "Assistant" : "User"; let content = String(m.content || ""); if (m.role === "assistant") { content = stripJsonFencesForHistory(content); } content = content.slice(0, 1500); lines.push(`### ${role}`); lines.push(content || "(empty)"); lines.push(""); } lines.push("Compress the chunk into the required heading format. Merge with previous memory when present."); return { prompt: lines.join("\n"), foldCount: fold.length, fold }; } function applyCompressResult(summaryText, foldCount, { uiCollapsed = false } = {}) { const C = ctxApi(); const prev = getContextMemory(); const merged = C?.mergeSummary ? C.mergeSummary(prev.summary, summaryText) : String(summaryText || "").trim() || prev.summary; const nextUntil = prev.untilCount + Math.max(0, foldCount); setContextMemory({ summary: merged, untilCount: nextUntil, foldedTurns: Math.floor(nextUntil / 2), at: Date.now(), uiCollapsed: uiCollapsed || prev.uiCollapsed, promptEvalCount: state.lastPromptEvalCount }); } function callOllamaOnce(payload) { return new Promise((resolve, reject) => { const fail = (err) => reject(new Error(String(err || "Chat failed"))); const ok = (data) => { if (data?.error) { fail(data.error); return; } resolve(data || {}); }; if (typeof makeWSRequest === "function") { let settled = false; makeWSRequest( "AssistentChatWS", payload, (data) => { if (settled) { return; } if (data?.error) { settled = true; fail(data.error); return; } if (data?.done || data?.reply != null) { settled = true; ok(data); } }, 0, (err) => { if (settled) { return; } genericRequest("AssistentChat", payload, (data) => { settled = true; ok(data); }, 0, (err2) => { settled = true; fail(err2 || err); }); } ); return; } genericRequest("AssistentChat", payload, ok, 0, fail); }); } async function runCompressTurn({ uiCollapsed = false, chatEpoch = state.chatEpoch } = {}) { const C = ctxApi(); const mem = getContextMemory(); const fold = C?.messagesToFold ? C.messagesToFold(state.history, mem, HISTORY_KEEP_TURNS) : []; if (!fold.length) { return false; } const model = $("sa_model")?.value; if (!model) { setStatus("\u0412\u044B\u0431\u0435\u0440\u0438 \u043C\u043E\u0434\u0435\u043B\u044C \u0447\u0430\u0442\u0430 \u0432 \u2699"); return false; } const { prompt, foldCount } = buildCompressUserPrompt(); if (!foldCount) { return false; } state.compressing = true; updateCtxChip(); setBusyPhase("compressing"); setStatus("\u0421\u0436\u0438\u043C\u0430\u044E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u2026"); const persona = $("sa_persona")?.value || "neutral"; const baseUrl = $("sa_base_url")?.value || "http://127.0.0.1:11434"; const payload = { baseUrl, model, pack: "compress_history", persona, includeBase: false, messages: [{ role: "user", content: prompt }], context_json: JSON.stringify({ compress: true, previous_memory: mem.summary || null, fold_count: foldCount }), skills: [], embed_model: $("sa_embed_model")?.value || state.preferredEmbed || "" }; try { const data = await callOllamaOnce(payload); if (chatEpoch !== state.chatEpoch) { return false; } if (data.prompt_eval_count != null) { state.lastPromptEvalCount = Number(data.prompt_eval_count) || null; } else if (data.raw?.prompt_eval_count != null) { state.lastPromptEvalCount = Number(data.raw.prompt_eval_count) || null; } const reply = String(data.reply || "").trim(); if (!reply) { setStatus("\u0421\u0436\u0430\u0442\u0438\u0435: \u043F\u0443\u0441\u0442\u043E\u0439 \u043E\u0442\u0432\u0435\u0442 \u043C\u043E\u0434\u0435\u043B\u0438"); return false; } applyCompressResult(reply, foldCount, { uiCollapsed }); if (uiCollapsed) { renderHistoryIntoUi(state.history); } setStatus("\u041A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u0441\u0436\u0430\u0442"); return true; } catch (e) { console.warn("Assistent compress failed", e); setStatus(`\u0421\u0436\u0430\u0442\u0438\u0435 \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C: ${e.message || e}`); return false; } finally { state.compressing = false; updateCtxChip(); } } function maybeAutoCompressBeforeSend(chatEpoch) { if (!COMPRESS_AUTO) { return Promise.resolve(false); } const C = ctxApi(); const mem = getContextMemory(); const msgs = assembleOutgoingMessages(); const budget = currentBudgetEstimate(msgs); const should = C?.shouldCompress ? C.shouldCompress(budget, mem, (state.history || []).filter((m) => m && !m.systemish).length, { keepMessages: historyMessageLimit() }) : false; if (!should) { return Promise.resolve(false); } return runCompressTurn({ uiCollapsed: false, chatEpoch }); } function formatCtxChipLabel(budget) { const C = ctxApi(); const used = C?.formatTokenShort ? C.formatTokenShort(budget.used) : String(budget.used || 0); const cap = C?.formatTokenShort ? C.formatTokenShort(budget.numCtx) : String(budget.numCtx || 0); return `${used} / ${cap}`; } function updateCtxChip() { const chip = $("sa_ctx_chip"); if (!chip) { return; } const budget = currentBudgetEstimate(); const mem = getContextMemory(); const label = formatCtxChipLabel(budget); const textEl = chip.querySelector(".sa-ctx-chip-text"); if (textEl) { textEl.textContent = label; } else { chip.textContent = label; } chip.classList.remove("sa-ctx-ok", "sa-ctx-warn", "sa-ctx-hot", "sa-ctx-compressing", "sa-ctx-has-mem"); if (state.compressing) { chip.classList.add("sa-ctx-compressing"); } else { chip.classList.add(`sa-ctx-${budget.level || "ok"}`); } if (mem.summary) { chip.classList.add("sa-ctx-has-mem"); } const src = budget.fromEval ? "\u0444\u0430\u043A\u0442 Ollama" : "\u043E\u0446\u0435\u043D\u043A\u0430"; chip.title = `\u041A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u043C\u043E\u0434\u0435\u043B\u0438 \xB7 ${src}${mem.summary ? " \xB7 \u0435\u0441\u0442\u044C \u0441\u0430\u043C\u043C\u0430\u0440\u0438" : ""}`; const dot = chip.querySelector(".sa-ctx-dot"); if (dot) { dot.hidden = !mem.summary; } } function toggleCtxPanel(force) { const panel = $("sa_ctx_panel"); const chip = $("sa_ctx_chip"); if (!panel || !chip) { return; } const open = force != null ? !!force : !state.ctxPanelOpen; state.ctxPanelOpen = open; panel.hidden = !open; chip.setAttribute("aria-expanded", open ? "true" : "false"); if (open) { renderCtxPanel(); } } function renderCtxPanel() { const body = $("sa_ctx_panel_body"); const bar = $("sa_ctx_bar_fill"); const auto = $("sa_ctx_auto"); if (!body) { return; } const budget = currentBudgetEstimate(); const mem = getContextMemory(); const layers = state.lastSystemLayers || {}; const layerRows = Object.entries(layers).filter(([k]) => k !== "total").map(([k, v]) => `
${escapeHtml(k)}${Number(v) || 0}
`).join(""); const keep = HISTORY_KEEP_TURNS; const uncovered = Math.max(0, (state.history || []).filter((m) => m && !m.systemish).length - (mem.untilCount || 0)); body.innerHTML = `
${budget.fromEval ? "\u0422\u043E\u043A\u0435\u043D\u044B (prompt_eval)" : "\u041E\u0446\u0435\u043D\u043A\u0430 \u0442\u043E\u043A\u0435\u043D\u043E\u0432"} \xB7 \u043F\u043E\u0440\u043E\u0433 ${budget.threshold || "\u2014"}
system${budget.systemChars || 0}
history${budget.historyChars || 0}
memory${budget.memoryChars || 0}
${layerRows ? `
system_layers
${layerRows}` : ""}
\u041C\u043E\u0434\u0435\u043B\u044C \u0432\u0438\u0434\u0438\u0442: ${mem.summary ? `\u0441\u0430\u043C\u043C\u0430\u0440\u0438 (${mem.foldedTurns || 0} \u0445\u043E\u0434\u043E\u0432) +` : ""} \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0435 ${keep} \u0445\u043E\u0434\u043E\u0432 \xB7 \u0441\u044B\u0440\u044B\u0445 \u0432 \u043E\u043A\u043D\u0435 \u2248 ${Math.min(uncovered, historyMessageLimit())}
${mem.summary ? `
${escapeHtml(mem.summary.slice(0, 800))}${mem.summary.length > 800 ? "\u2026" : ""}
` : '
\u0421\u0430\u043C\u043C\u0430\u0440\u0438 \u0435\u0449\u0451 \u043D\u0435\u0442 \u2014 \u0441\u0442\u0430\u0440\u044B\u0435 \u0445\u043E\u0434\u044B \u043F\u0440\u043E\u0441\u0442\u043E \u043E\u0442\u0431\u0440\u0430\u0441\u044B\u0432\u0430\u044E\u0442\u0441\u044F.
'} `; if (bar) { const pct = Math.max(0, Math.min(100, budget.used / (budget.numCtx || 1) * 100)); bar.style.width = `${pct}%`; bar.dataset.level = budget.level || "ok"; } if (auto) { auto.checked = !!COMPRESS_AUTO; } updateCtxChip(); } async function compressNowFromUi() { if (state.busy || state.generating || state.compressing) { setStatus("\u0417\u0430\u043D\u044F\u0442\u043E \u2014 \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u043A\u043E\u043D\u0446\u0430 \u043E\u0442\u0432\u0435\u0442\u0430"); return; } const model = $("sa_model")?.value; if (!model) { setStatus("\u0412\u044B\u0431\u0435\u0440\u0438 \u043C\u043E\u0434\u0435\u043B\u044C \u0447\u0430\u0442\u0430 \u0432 \u2699"); return; } state.busy = true; setInterruptVisible(true); startBusyUi("compressing"); const epoch = state.chatEpoch; try { const ok = await runCompressTurn({ uiCollapsed: true, chatEpoch: epoch }); if (!ok) { setStatus("\u041D\u0435\u0447\u0435\u0433\u043E \u0441\u0436\u0438\u043C\u0430\u0442\u044C (\u0445\u0432\u043E\u0441\u0442 \u2264 keep)"); } } finally { if (epoch === state.chatEpoch) { state.busy = false; setInterruptVisible(state.generating); stopBusyUi(getContextMemory().summary ? "\u041A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u0441\u0436\u0430\u0442" : "\u0413\u043E\u0442\u043E\u0432\u043E"); } updateCtxChip(); if (state.ctxPanelOpen) { renderCtxPanel(); } } } function resetCompressionFromUi() { resetContextMemory(); renderHistoryIntoUi(state.history); setStatus("\u0421\u0436\u0430\u0442\u0438\u0435 \u0441\u0431\u0440\u043E\u0448\u0435\u043D\u043E \u2014 \u043C\u043E\u0434\u0435\u043B\u044C \u0441\u043D\u043E\u0432\u0430 \u0432\u0438\u0434\u0438\u0442 \u0442\u043E\u043B\u044C\u043A\u043E last-K"); if (state.ctxPanelOpen) { renderCtxPanel(); } } function flashImagePane(slotId) { const el = document.querySelector(`.sa-slot[data-id="${slotId || state.selectedSlotId}"]`); if (!el) { return; } el.classList.remove("sa-flash"); void el.offsetWidth; el.classList.add("sa-flash"); } function ensureBoard() { if (state.slots.length) { return; } state.slots = [ { id: GEN_ID, type: "generate", label: "Generate", src: null, attach: false }, { id: "ref1", type: "ref", label: "Ref 1", src: null, attach: true } ]; state.refSeq = 1; state.selectedSlotId = "ref1"; } function slotById(id) { ensureBoard(); const key = normalizeSlotId(id); return state.slots.find((s) => s.id === key) || null; } function generateSlot() { return slotById(GEN_ID); } function refSlots() { ensureBoard(); return state.slots.filter((s) => s.type === "ref"); } function normalizeSlotId(id) { const raw = String(id || "").trim().toLowerCase(); if (!raw) { return ""; } if (raw === "gen" || raw === "current" || raw === "live" || raw === "generation") { return GEN_ID; } if (raw === "selected" || raw === "sel") { return state.selectedSlotId; } const m = raw.match(/^ref\s*[_-]?\s*(\d+)$/); if (m) { return `ref${m[1]}`; } return raw; } function selectedSlot() { return slotById(state.selectedSlotId) || generateSlot(); } function selectedSrc() { return selectedSlot()?.src || null; } function syncLastImageAlias() { const attached = attachableSlots(); state.lastImageDataUrl = (attached[0] || selectedSlot() || generateSlot())?.src || null; } function attachableSlots() { ensureBoard(); return state.slots.filter((s) => s.attach && s.src); } function visionReadySlots() { ensureBoard(); return state.slots.filter((s) => s && s.src && !looksLikeModelPreview(s.src)); } function setSlotSrc(id, src, { select = true, attach = null, note = null, switchTab = false, allowPreview = false } = {}) { const slot = slotById(id); if (!slot) { return false; } const cleaned = src ? String(src).trim().split(/\s+/)[0] : null; if (cleaned && cleaned.startsWith("#")) { return false; } if (cleaned && !allowPreview && looksLikeModelPreview(cleaned)) { setStatus("\u041F\u0440\u043E\u043F\u0443\u0441\u043A \u043F\u0440\u0435\u0432\u044C\u044E \u043C\u043E\u0434\u0435\u043B\u0438 (\u043D\u0443\u0436\u043D\u0430 \u0440\u0435\u0430\u043B\u044C\u043D\u0430\u044F \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u044F)"); return false; } slot.src = cleaned || null; if (attach != null) { slot.attach = !!attach; } else if (slot.type === "ref" && slot.src) { slot.attach = true; } if (select) { state.selectedSlotId = slot.id; } syncLastImageAlias(); renderBoard(); flashImagePane(slot.id); if (switchTab) { openAssistentTab(); } if (note) { setStatus(note); } return true; } function addRefSlot({ src = null, select = true } = {}) { ensureBoard(); if (refSlots().length >= MAX_REF_SLOTS) { setStatus(`Max ${MAX_REF_SLOTS} reference windows`); const empty = refSlots().find((s) => !s.src); if (empty && src) { return setSlotSrc(empty.id, src, { select, note: `Loaded into ${empty.label}` }); } return empty || null; } state.refSeq += 1; const id = `ref${state.refSeq}`; const slot = { id, type: "ref", label: `Ref ${state.refSeq}`, src: src || null, attach: !!src }; state.slots.push(slot); if (select) { state.selectedSlotId = id; } renderBoard(); return slot; } function clearSlot(id, { silent = false } = {}) { const slot = slotById(id); if (!slot) { return; } if (slot.type === "generate") { if (!silent) { setStatus("Generate window is live \u2014 use Snapshot gen to copy it"); } return; } slot.src = null; slot.attach = true; syncLastImageAlias(); renderBoard(); if (!silent) { setStatus(`${slot.label} cleared`); } } function snapshotGenerateToRef() { const src = generateSlot()?.src && !looksLikeModelPreview(generateSlot().src) ? generateSlot().src : findCurrentGenerateSrc({ allowPreview: false }); if (!src) { setStatus("\u041D\u0435\u0442 \u0442\u0435\u043A\u0443\u0449\u0435\u0433\u043E \u043A\u0430\u0434\u0440\u0430 Generate (\u043F\u0440\u0435\u0432\u044C\u044E \u043C\u043E\u0434\u0435\u043B\u0438 \u043D\u0435 \u0441\u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044F)"); return false; } const empty = refSlots().find((s) => !s.src); let ok = false; if (empty) { ok = setSlotSrc(empty.id, src, { note: `\u0421\u043D\u0438\u043C\u043E\u043A \u2192 ${empty.label}` }); } else { const created = addRefSlot({ src, select: true }); if (created?.src) { setStatus(`\u0421\u043D\u0438\u043C\u043E\u043A \u2192 ${created.label}`); flashImagePane(created.id); ok = true; } else { const last = refSlots()[refSlots().length - 1]; if (last) { ok = setSlotSrc(last.id, src, { note: `\u0421\u043D\u0438\u043C\u043E\u043A \u2192 ${last.label} (\u0437\u0430\u043C\u0435\u043D\u0430)` }); } } } if (ok) { setBoardTab("refs"); } return ok; } function putImageOnBoard(src, { note = null, switchTab = false, preferSelected = true } = {}) { if (!src) { return false; } ensureBoard(); const sel = selectedSlot(); if (preferSelected && sel && sel.type === "ref") { return setSlotSrc(sel.id, src, { note: note || `Loaded into ${sel.label}`, switchTab }); } const empty = refSlots().find((s) => !s.src); if (empty) { return setSlotSrc(empty.id, src, { note: note || `Loaded into ${empty.label}`, switchTab }); } const created = addRefSlot({ src, select: true }); if (created) { if (switchTab) { openAssistentTab(); } if (note) { setStatus(note); } return true; } return false; } function setImageFromSrc(src, opts = {}) { return putImageOnBoard(src, opts); } function clearVisionImage(opts) { clearSlot(state.selectedSlotId, opts); } function slotCatalog() { ensureBoard(); return state.slots.map((s) => ({ id: s.id, type: s.type, label: s.label, has_image: !!s.src, attach: !!s.attach, selected: s.id === state.selectedSlotId })); } function lookAtIdsFromPatch(patch) { if (!patch) { return []; } const raw = patch.look_at || patch.vision_from || patch.vision_slots; const list = Array.isArray(raw) ? raw : raw ? [raw] : []; if (Array.isArray(patch.actions)) { for (const a of patch.actions.map(String)) { const m = a.match(/^look_at[_:]?(generate|ref\d+|selected)$/i); if (m) { list.push(m[1]); } } } return [...new Set(list.map(normalizeSlotId).filter(Boolean))]; } function resolveSlotSrc(id) { if (!id) { return selectedSrc() || generateSlot()?.src || findCurrentGenerateSrc(); } const slot = slotById(id); if (slot?.src) { return slot.src; } if (normalizeSlotId(id) === GEN_ID) { return findCurrentGenerateSrc(); } return null; } function isSwarmGenerateRunning() { try { if (typeof num_live_gens === "number" && num_live_gens > 0) { return true; } if (typeof num_waiting_gens === "number" && num_waiting_gens > 0) { return true; } } catch (e) { } try { if (typeof mainGenHandler !== "undefined" && mainGenHandler) { if (mainGenHandler.isGenerating === true || mainGenHandler.running === true) { return true; } } } catch (e) { } const interrupt = document.getElementById("interrupt_button") || document.getElementById("alt_interrupt_button"); if (interrupt && !interrupt.hidden && interrupt.offsetParent !== null) { return true; } const genBtn = document.getElementById("generate_button") || document.getElementById("alt_generate_button"); if (genBtn && (genBtn.disabled || /interrupt/i.test(genBtn.textContent || ""))) { return true; } return false; } function isGenerateUnavailable() { if (state.generating || state.busy) { return true; } return isSwarmGenerateRunning(); } function syncGenerateBusy() { const overlay = document.querySelector(".sa-slot-gen .sa-slot-busy"); if (overlay) { const stuck = state.generating && !isSwarmGenerateRunning(); overlay.hidden = !state.generating && state.busyPhase !== "generating" || stuck; } const running = state.generating || state.busyPhase === "generating"; document.querySelectorAll(".sa-slot-gen-result").forEach((el) => { const busy = el.querySelector(".sa-slot-busy"); if (!busy) { return; } const hasImg = el.classList.contains("sa-has-image"); busy.hidden = !running || hasImg; }); } function syncPatchActionAvailability() { const bar = document.querySelector(".sa-patch-actions.sa-patch-current"); if (!bar) { return; } const locked = isGenerateUnavailable(); bar.querySelectorAll(".sa-btn-gen").forEach((btn) => { btn.disabled = locked; let spin = btn.querySelector(".sa-spinner"); if (locked) { if (!spin) { spin = document.createElement("span"); spin.className = "sa-spinner sa-spinner-btn"; spin.setAttribute("aria-hidden", "true"); btn.prepend(spin); } } else if (spin) { spin.remove(); } }); } function retireStalePatchActions() { document.querySelectorAll(".sa-patch-actions").forEach((el) => { const note = document.createElement("div"); note.className = "sa-patch-stale"; note.textContent = "Superseded \u2014 use the latest proposal"; el.replaceWith(note); }); } function mountPatchBlock(host, patch, { silent = false } = {}) { if (!host || !patch) { return; } rememberLastPatch(patch); const wrap = document.createElement("div"); wrap.className = "sa-patch" + (silent ? " sa-patch-auto" : ""); const details = document.createElement("details"); details.className = "sa-patch-details"; const summary = document.createElement("summary"); const keys = Object.keys(patch).filter((k) => patch[k] != null && k !== "notes" && k !== "actions"); summary.textContent = silent ? `\u041F\u0430\u0442\u0447 \u043F\u0440\u0438\u043C\u0435\u043D\u0451\u043D \xB7 ${keys.slice(0, 6).join(", ") || "generate"}` : `JSON \u043F\u0430\u0442\u0447 \xB7 ${keys.slice(0, 8).join(", ") || "\u2026"}`; const pre = document.createElement("pre"); pre.textContent = JSON.stringify(patch, null, 2); details.appendChild(summary); details.appendChild(pre); wrap.appendChild(details); mountPatchActions(wrap, patch, { silent }); host.appendChild(wrap); } function mountPatchActions(parent, patch, { silent = false } = {}) { if (!parent || !patch) { return; } rememberLastPatch(patch); retireStalePatchActions(); const wrap = parent.classList.contains("sa-patch") ? parent : null; const host = wrap || parent; if (silent) { const note = document.createElement("div"); note.className = "sa-patch-actions sa-patch-silent sa-patch-current"; const willGen = Array.isArray(patch.actions) && patch.actions.map(String).includes("generate") || !!patch.generate || !!state.pendingSilentGen; note.textContent = willGen ? "\u0412 \u0441\u0435\u0441\u0441\u0438\u044E \xB7 Generate\u2026" : "\u0412 \u0441\u0435\u0441\u0441\u0438\u044E"; host.appendChild(note); return; } const actions = document.createElement("div"); actions.className = "sa-patch-actions sa-patch-current"; for (const [label, which] of [ ["\u041F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C \u0432\u0441\u0451", "all"], ["\u041F\u0440\u043E\u043C\u043F\u0442", "prompt"], ["LoRAs", "loras"], ["\u041F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B", "params"] ]) { const btn = document.createElement("button"); btn.type = "button"; btn.className = "basic-button"; btn.textContent = label; btn.addEventListener("click", () => applyPatch(patch, which)); actions.appendChild(btn); } const toSession = document.createElement("button"); toSession.type = "button"; toSession.className = "basic-button"; toSession.textContent = "\u0412 \u0441\u0435\u0441\u0441\u0438\u044E"; toSession.addEventListener("click", async () => { const S = window.SA && window.SA.session; if (S) { state.chatSession = S.mergeDelta(state.chatSession || S.emptySession(), patch); } await pushSessionToSwarm(state.chatSession); rememberLastPatch(patch); try { const chat = findChat(state.activeChatId); if (chat) { chat.params = snapshotChatParams(); persistChatsStore(); } } catch (e) { } setStatus("\u041F\u0430\u0442\u0447 \u0432 \u0441\u0435\u0441\u0441\u0438\u0438"); }); actions.appendChild(toSession); const genBtn = document.createElement("button"); genBtn.type = "button"; genBtn.className = "basic-button sa-btn-gen sa-btn-gen-primary"; genBtn.textContent = "\u0421\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C"; genBtn.addEventListener("click", async () => { if (isGenerateUnavailable()) { setStatus("Generate \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u2014 \u043F\u043E\u0434\u043E\u0436\u0434\u0438 \u0438\u043B\u0438 \u043D\u0430\u0436\u043C\u0438 \u0421\u0442\u043E\u043F"); return; } startBusyUi("silent_gen"); pullLiveIntoSession(); const S = window.SA && window.SA.session; if (S) { state.chatSession = S.mergeDelta(state.chatSession || S.emptySession(), patch); } await pushSessionToSwarm(state.chatSession); if (typeof appendSystemNote === "function") { appendSystemNote("\u0417\u0430\u043F\u0443\u0441\u043A\u0430\u044E Generate"); } await runGenerateFromPatch({ ...patch, actions: ["generate"] }, { force: true, fromSession: true }); }); actions.appendChild(genBtn); host.appendChild(actions); syncPatchActionAvailability(); } async function buildCurrentAndGenerate() { if (state.busy || state.generating) { setStatus("\u0417\u0430\u043D\u044F\u0442\u043E \u2014 \u043F\u043E\u0434\u043E\u0436\u0434\u0438 \u0438\u043B\u0438 \u043D\u0430\u0436\u043C\u0438 \u0421\u0442\u043E\u043F"); return; } if (typeof isGenerateUnavailable === "function" && isGenerateUnavailable()) { setStatus("Generate \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u2014 \u0434\u043E\u0436\u0434\u0438\u0441\u044C SwarmUI"); return; } pullLiveIntoSession(); const S = window.SA && window.SA.session; if (state.lastPatch && S) { state.chatSession = S.mergeDelta(state.chatSession || S.emptySession(), state.lastPatch); if (state.lastPatch.loras == null && state.chatSession?.gen) { state.chatSession.gen.loras = readLiveGenFields().loras || []; } } if (typeof startBusyUi === "function") startBusyUi(state.lastPatch ? "silent_gen" : "generating"); setStatus(state.lastPatch ? "\u0421\u0435\u0441\u0441\u0438\u044F \u2192 Generate\u2026" : "Generate \u0441 \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0441\u0435\u0441\u0441\u0438\u0435\u0439\u2026"); await pushSessionToSwarm(state.chatSession); await runGenerateFromPatch({ actions: ["generate"] }, { force: true, fromSession: true }); } function renderBoard() { const board = $("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 = '
\u2026
\u0416\u0434\u0443 \u043A\u0430\u0434\u0440
'; 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 = ''; 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 genLightboxList() { const fromResults = (state.genResults || []).filter((r) => r && r.src); if (fromResults.length) { return fromResults; } const slot = generateSlot(); if (slot?.src) { return [{ id: slot.id || GEN_ID, label: slot.label || "Generate", src: slot.src, patch: state.lastPatch || null }]; } return []; } function ensureGenLightbox() { let root = $("sa_gen_lightbox"); if (root) { return root; } const host = $("swarm_assistent_root") || document.body; root = document.createElement("div"); root.id = "sa_gen_lightbox"; root.className = "sa-lightbox"; root.hidden = true; root.innerHTML = `
`; 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 = genLightboxList(); if (!list.length || state.lightboxIndex < 0) { return null; } return list[state.lightboxIndex] || null; } function syncGenLightbox() { const root = ensureGenLightbox(); const list = genLightboxList(); const row = list[state.lightboxIndex]; if (!row) { root.hidden = true; return; } root.hidden = false; const img = $("sa_lb_img"); const title = $("sa_lb_title"); const idx = $("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 = genLightboxList(); 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 = $("sa_gen_lightbox"); if (root) { root.hidden = true; } } function stepGenLightbox(delta) { const list = genLightboxList(); 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"; $("sa_board_tab_gen")?.classList.toggle("sa-board-tab-active", tab === "generate"); $("sa_board_tab_refs")?.classList.toggle("sa-board-tab-active", tab === "refs"); $("sa_board_tab_gen")?.setAttribute("aria-selected", tab === "generate" ? "true" : "false"); $("sa_board_tab_refs")?.setAttribute("aria-selected", tab === "refs" ? "true" : "false"); const addBtn = $("sa_btn_add_ref"); if (addBtn) { addBtn.hidden = tab !== "refs"; } const maskBtn = $("sa_btn_as_mask"); const clearSlotBtn = $("sa_btn_clear_image"); if (maskBtn) { maskBtn.hidden = tab !== "refs"; } if (clearSlotBtn) { clearSlotBtn.hidden = tab !== "refs"; } const badge = $("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 = $("sa_gen_badge"); if (!genBadge) { const genTab = $("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" ? '
Generate
\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
' : '
Reference
Drop \xB7 paste \xB7 \u0421\u043D\u0438\u043C\u043E\u043A gen
'; 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 = ''; el.appendChild(busy); el.addEventListener("click", (e) => { if (slot.type === "generate" && slot.src && e.target?.tagName === "IMG") { openGenLightbox(slot.id || GEN_ID); return; } state.selectedSlotId = slot.id; renderBoard(); }); if (slot.type === "generate") { el.addEventListener("dblclick", (e) => { if (slot.src && e.target?.tagName === "IMG") { e.preventDefault(); openGenLightbox(slot.id || GEN_ID); } }); } 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 (!$("sa_messages")) { return; } localStorage.setItem(LS_WELCOMED, "1"); const box = $("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, activity: window.SA && typeof SA.slimActivityTrace === "function" ? SA.slimActivityTrace(m.activity) : m.activity || void 0 }; }); } function titleFromMessages(messages) { const u = (messages || []).find((m) => m.role === "user" && m.content); const t = String(u?.content || "").replace(/\s+/g, " ").trim(); return t ? t.slice(0, 52) : "\u041D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442"; } function readLiveGenFields() { let loras = []; try { if (typeof loraHelper !== "undefined" && loraHelper && Array.isArray(loraHelper.selected)) { loras = loraHelper.selected.map((l) => ({ name: l.name || l, weight: loraHelper.loraWeightPref && loraHelper.loraWeightPref[l.name || l] || 1 })); } } catch (e) { } let checkpoint = null; try { if (typeof resolveCurrentCheckpoint === "function") { const m = resolveCurrentCheckpoint(); if (m && (m.name || m.title)) { checkpoint = { name: m.name || m.title || null, architecture: m.architecture || m.compat_class || m.class || null, title: m.title || null }; } } } catch (e) { } return { prompt: val("alt_prompt_textbox") || val("input_prompt") || "", negative: val("input_negativeprompt") || val("alt_negativeprompt_textbox") || "", width: parseInt(val("input_width") || "0", 10) || null, height: parseInt(val("input_height") || "0", 10) || null, steps: parseInt(val("input_steps") || "0", 10) || null, cfg: parseFloat(val("input_cfgscale") || val("input_cfg") || "") || null, sigma_shift: parseFloat(val("input_sigmashift") || "") || null, seed: val("input_seed") || null, sampler: val("input_sampler") || null, scheduler: val("input_scheduler") || null, batch: parseInt(val("input_images") || val("input_batchsize") || "0", 10) || null, loras, checkpoint }; } function boardSnapshotForSession() { if (typeof ensureBoard === "function") ensureBoard(); return { slots: (state.slots || []).map((s) => ({ id: s.id, type: s.type, label: s.label, src: s.src || null, attach: !!s.attach, note: s.note || null })), selectedSlotId: state.selectedSlotId || "ref1", genResults: Array.isArray(state.genResults) ? state.genResults.map((r) => ({ id: r.id, label: r.label, src: r.src || null, patch: r.patch || null })) : [], selectedGenResultId: state.selectedGenResultId || null, refSeq: state.refSeq || 1 }; } function pullLiveIntoSession() { const S = window.SA && window.SA.session; if (!S || typeof S.snapshotFromLive !== "function") return state.chatSession; state.chatSession = S.snapshotFromLive({ genFields: readLiveGenFields(), board: boardSnapshotForSession(), persona: $("sa_persona")?.value || "neutral", pack: $("sa_pack")?.value || "ordinary", context_memory: getContextMemory() }); return state.chatSession; } async function pushSessionToSwarm(session) { const S = window.SA && window.SA.session; const sess = session || state.chatSession || S && S.emptySession && S.emptySession(); if (!sess || !sess.gen) return; if (typeof applyPatch === "function") await applyPatch({ ...sess.gen }, "all"); try { const name = sess.gen.checkpoint?.name || (typeof sess.gen.checkpoint === "string" ? sess.gen.checkpoint : null); if (name && typeof currentModelHelper !== "undefined" && currentModelHelper?.setModel) { currentModelHelper.setModel(name); } } catch (e) { } if (sess.pack && typeof setPackValue === "function") setPackValue(sess.pack, { flash: false }); if (sess.persona && typeof applyPersonaForChat === "function") { await applyPersonaForChat(sess.persona, { quiet: true }); } state.chatSession = sess; } function snapshotChatParams() { const S = window.SA && window.SA.session; const session = pullLiveIntoSession(); if (S && typeof S.toPersistParams === "function") return S.toPersistParams(session); return session || {}; } async function restoreChatParams(params) { state.restoringChat = true; try { state.sessionExact = {}; state.lastPatch = null; state.lastUserParamIntent = false; const S = window.SA && window.SA.session; state.chatSession = S && typeof S.sessionFromLegacyParams === "function" ? S.sessionFromLegacyParams(params) : params && params.gen ? params : S && S.emptySession ? S.emptySession() : { gen: {}, board: {} }; if (!params || typeof params !== "object") { resetBoardToDefault(); if (typeof renderBoard === "function") renderBoard(); setContextMemory(null, { persist: false }); return { restored: false }; } resetBoardToDefault(); await pushSessionToSwarm(state.chatSession); const board = state.chatSession.board || {}; if (Array.isArray(board.slots) && board.slots.length) { state.slots = board.slots.map((s) => ({ ...s })); if (board.selectedSlotId) state.selectedSlotId = board.selectedSlotId; if (board.refSeq) state.refSeq = board.refSeq; } if (Array.isArray(board.genResults) && board.genResults.length) { state.genResults = board.genResults.map((r, i) => ({ id: r.id || "var" + (i + 1), label: r.label || "\u0412\u0430\u0440\u0438\u0430\u043D\u0442 " + (i + 1), src: r.src || null, patch: r.patch || null })); state.selectedGenResultId = board.selectedGenResultId || state.genResults.find((x) => x.src)?.id || state.genResults[0]?.id || null; const selected = state.genResults.find((x) => x.id === state.selectedGenResultId); const gen = typeof generateSlot === "function" ? generateSlot() : null; if (gen && selected?.src) gen.src = selected.src; } if (typeof syncBuildGenButton === "function") syncBuildGenButton(); if (typeof syncLiveParamsBar === "function") syncLiveParamsBar(); if (typeof syncModeBadge === "function") syncModeBadge(); if (typeof renderLoraChips === "function") renderLoraChips(); if (typeof renderBoard === "function") renderBoard(); setContextMemory(state.chatSession?.context_memory || null); return { restored: true }; } finally { state.restoringChat = false; } } function applyPersonaForChat(personaId, { quiet = false } = {}) { const id = String(personaId || "neutral").trim() || "neutral"; return new Promise((resolve) => { const sel = $("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 = $("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 = $("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 || '
\u041D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442
\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.
+ \u2014 \u0435\u0449\u0451 \u043E\u0434\u0438\u043D \u0447\u0430\u0442 \xB7 \u043A\u043D\u043E\u043F\u043A\u0430 \u043F\u0430\u043D\u0435\u043B\u0438 \u0441\u043B\u0435\u0432\u0430 \u2014 \u043F\u0440\u043E\u0448\u043B\u044B\u0435 \u0434\u0438\u0430\u043B\u043E\u0433\u0438.
'; box.appendChild(empty); } function renderHistoryIntoUi(messages) { const box = $("sa_messages"); if (!box) { return; } box.innerHTML = ""; const list = slimHistoryMessages(messages); if (!list.length) { resetMessagesUi(); updateCtxChip(); return; } const mem = getContextMemory(); let start = 0; if (mem.uiCollapsed && mem.summary && mem.untilCount > 0) { const folded = list.slice(0, Math.min(mem.untilCount, list.length)); start = folded.length; const details = document.createElement("details"); details.className = "sa-msg sa-msg-compress"; const summary = document.createElement("summary"); summary.textContent = `\u0421\u0436\u0430\u0442\u044B\u0439 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \xB7 ${mem.foldedTurns || Math.floor(folded.length / 2)} \u0445\u043E\u0434\u043E\u0432`; details.appendChild(summary); const body = document.createElement("div"); body.className = "sa-msg-compress-body"; const pre = document.createElement("pre"); pre.className = "sa-ctx-summary"; pre.textContent = mem.summary; body.appendChild(pre); for (const m of folded) { const row = document.createElement("div"); row.className = `sa-msg-compress-row sa-msg-compress-${m.role}`; row.textContent = `${m.role === "assistant" ? "\u0410\u0441\u0441\u0438\u0441\u0442\u0435\u043D\u0442" : "\u0412\u044B"}: ${String(m.content || "").slice(0, 500)}`; body.appendChild(row); } details.appendChild(body); box.appendChild(details); } for (let i = start; i < list.length; i++) { const m = list[i]; if (m.role === "user") { appendMessage("user", m.content, null, null, { historical: true }); } else { appendMessage("assistant", m.content, null, null, { persona: m.persona ? { id: m.persona, title: m.persona } : null, pack: m.pack, historical: true, activity: m.activity }); } } updateCtxChip(); } function updateSessionLabel() { const el = $("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"; } function chatHasTranscript(c) { return (c?.messages || []).length > 0 || (c?.messages_count || 0) > 0; } function savedChatsCount() { return (state.chats || []).filter(chatHasTranscript).length; } function isBlankActiveChat() { if ((state.history || []).length) { return false; } const chat = findChat(state.activeChatId); return !chat || !chatHasTranscript(chat); } function syncHistoryBadge() { const btn = $("sa_btn_chats"); const countEl = $("sa_chats_count"); const n = (state.chats || []).filter(chatHasTranscript).length; const open = !!state.chatsPanelOpen; if (btn) { btn.title = open ? n ? `\u0421\u043A\u0440\u044B\u0442\u044C \u0447\u0430\u0442\u044B (${n})` : "\u0421\u043A\u0440\u044B\u0442\u044C \u0447\u0430\u0442\u044B" : n ? `\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u0447\u0430\u0442\u044B (${n})` : "\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u0447\u0430\u0442\u044B"; btn.setAttribute("aria-label", btn.title); } if (countEl) { countEl.hidden = n < 1; countEl.textContent = n > 99 ? "99+" : String(n); } } function formatChatWhen(ts) { if (!ts) { return ""; } const t = Number(ts) || 0; if (!t) { return ""; } const sec = Math.max(0, Math.floor((Date.now() - t) / 1e3)); if (sec < 45) { return "\u0441\u0435\u0439\u0447\u0430\u0441"; } if (sec < 3600) { return `${Math.max(1, Math.floor(sec / 60))}\u043C`; } if (sec < 86400) { return `${Math.floor(sec / 3600)}\u0447`; } if (sec < 86400 * 14) { return `${Math.floor(sec / 86400)}\u0434`; } try { return new Date(t).toLocaleDateString(void 0, { month: "short", day: "numeric" }); } 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 chatListParamsBits(params) { if (!params || typeof params !== "object") { return ""; } const g = params.gen && typeof params.gen === "object" ? params.gen : params; const bits = []; if (g.width && g.height) { bits.push(`${g.width}\xD7${g.height}`); } else if (g.aspect) { bits.push(String(g.aspect)); } const loras = Array.isArray(g.loras) ? g.loras : []; if (loras.length) { bits.push(`LoRA ${loras.length}`); } return bits.join(" \xB7 "); } function renderChatsList() { const root = $("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.id === state.activeChatId || chatHasTranscript(c)); 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 ? '
\u041D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043D\u0430\u0448\u043B\u043E\u0441\u044C.
' : '
\u041F\u043E\u043A\u0430 \u043F\u0443\u0441\u0442\u043E \u2014 \u043D\u0430\u043F\u0438\u0448\u0438 \u0432 \u0447\u0430\u0442, \u0438 \u043E\u043D \u043F\u043E\u044F\u0432\u0438\u0442\u0441\u044F \u0437\u0434\u0435\u0441\u044C.
'; return; } const ico = ''; 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; row.setAttribute("role", "listitem"); const when = formatChatWhen(c.updatedAt); const title = escapeHtml(c.title || "\u041D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442"); const tipBits = [chatListParamsBits(c.params)].filter(Boolean); const tip = tipBits.length ? ` title="${escapeHtml(tipBits.join(" \xB7 "))}"` : ""; row.innerHTML = ``; root.appendChild(row); } } function setChatsPanelOpen(open) { state.chatsPanelOpen = !!open; state.chatsDrawerOpen = state.chatsPanelOpen; const panel = $("sa_chats_panel"); const btn = $("sa_btn_chats"); const root = $("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"); syncHistoryBadge(); if (state.chatsPanelOpen) { saveActiveChatToStore(); const search = $("sa_chats_search"); if (search) { search.value = state.chatsQuery || ""; search.focus(); } renderChatsList(); } saveUiStateToDisk(); } async function startNewChat({ saveCurrent = true, force = false, openDrawer = false } = {}) { if (!force && isBlankActiveChat()) { setStatus("\u0423\u0436\u0435 \u043D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442"); if (openDrawer) { setChatsPanelOpen(true); } $("sa_input")?.focus(); return; } if (state.busy || state.generating) { abortInFlightWork({ status: "", interruptSwarm: !!state.generating }); } if (saveCurrent) { saveActiveChatToStore({ dropEmpty: true }); } state.sessionExact = {}; state.lastUserParamIntent = false; state.pendingSilentGen = false; state.lastPatch = null; resetBoardToDefault(); const chat = { id: chatUid(), title: "\u041D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442", createdAt: Date.now(), updatedAt: Date.now(), messages: [], params: snapshotChatParams() }; state.chats.unshift(chat); state.activeChatId = chat.id; state.history = []; resetTurnHops(); state.packUserTouched = false; state.pendingPersonaNote = null; if (state.streamEl) { try { state.streamEl.remove(); } catch (e) { } state.streamEl = null; } resetContextMemory({ persist: false }); syncBuildGenButton(); resetMessagesUi(); persistChatsStore(); updateSessionLabel(); renderBoard(); syncHistoryBadge(); renderChatsList(); updateCtxChip(); setStatus("\u041D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442 \u2014 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B Generate \u043A\u0430\u043A \u0441\u0435\u0439\u0447\u0430\u0441"); maybeWelcome(); if (openDrawer) { setChatsPanelOpen(true); } if (openDrawer || !force) { $("sa_input")?.focus(); } } async function switchToChat(id) { if (!id || id === state.activeChatId) { return; } if (state.busy || state.generating) { setStatus("\u0417\u0430\u043D\u044F\u0442\u043E \u2014 \u043D\u0435\u043B\u044C\u0437\u044F \u0441\u043C\u0435\u043D\u0438\u0442\u044C \u0447\u0430\u0442 \u0441\u0435\u0439\u0447\u0430\u0441"); return; } saveActiveChatToStore({ dropEmpty: true }); let chat = findChat(id); if (!chat || !(chat.messages || []).length) { try { const full = await diskPersist()?.getChat?.(id); if (full) { const idx = (state.chats || []).findIndex((c) => c.id === id); if (idx >= 0) { state.chats[idx] = full; } else { state.chats.unshift(full); } chat = full; } } catch (e) { console.warn("Assistent: getChat failed", id, e); } } if (!chat) { setStatus("\u0427\u0430\u0442 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D"); return; } state.activeChatId = chat.id; state.history = slimHistoryMessages(chat.messages); resetTurnHops(); state.packUserTouched = false; state.pendingPersonaNote = null; state.pendingSilentGen = false; state.lastUserParamIntent = false; if (state.streamEl) { try { state.streamEl.remove(); } catch (e) { } state.streamEl = null; } setContextMemory(chat.params?.context_memory || null, { persist: false }); renderHistoryIntoUi(state.history); const result = await restoreChatParams(chat.params); updateSessionLabel(); syncHistoryBadge(); renderChatsList(); setView("chat"); if (result?.restored) { setStatus(`\u0427\u0430\u0442 \xAB${chat.title}\xBB \xB7 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u044B`); } else { setStatus(`\u0427\u0430\u0442 \xAB${chat.title}\xBB \xB7 \u0441\u043D\u0438\u043C\u043E\u043A \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u043E\u0432 \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u2014 Generate \u043D\u0435 \u043C\u0435\u043D\u044F\u043B\u0441\u044F`); } } function deleteChat(id) { if (!id) { return; } const wasActive = id === state.activeChatId; state.chats = state.chats.filter((c) => c.id !== id); if (wasActive) { state.activeChatId = null; } diskPersist()?.deleteChat(id)?.catch?.((e) => console.warn("Assistent: disk delete failed", e)); persistChatsStore(); syncHistoryBadge(); if (wasActive) { startNewChat({ saveCurrent: false, force: true }); } else { renderChatsList(); } } async function initChatSessions() { loadChatsStore(); await loadChatsFromDisk(); startNewChat({ saveCurrent: false, force: true }); syncHistoryBadge(); renderChatsList(); } function persistHistory() { if (state.restoringChat) { return; } if (!state.activeChatId) { const chat2 = { id: chatUid(), title: "\u041D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442", createdAt: Date.now(), updatedAt: Date.now(), messages: [], params: snapshotChatParams() }; state.chats.unshift(chat2); state.activeChatId = chat2.id; } const chat = findChat(state.activeChatId); if (!chat) { return; } chat.messages = slimHistoryMessages(state.history); chat.params = snapshotChatParams(); chat.updatedAt = Date.now(); chat.title = titleFromMessages(chat.messages); persistChatsStore(); updateSessionLabel(); syncHistoryBadge(); } function clearPersistedHistory() { if (state.activeChatId) { const chat = findChat(state.activeChatId); if (chat) { chat.messages = []; chat.title = "\u041D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442"; chat.params = snapshotChatParams(); chat.updatedAt = Date.now(); } persistChatsStore(); } updateSessionLabel(); renderChatsList(); } function clearChatHistory() { abortInFlightWork({ status: "" }); state.history = []; resetTurnHops(); state.packUserTouched = false; state.pendingPersonaNote = null; state.sessionExact = {}; state.lastUserParamIntent = false; state.pendingSilentGen = false; state.lastPatch = null; clearGenResults(); syncBuildGenButton(); clearPersistedHistory(); resetContextMemory({ persist: false }); resetMessagesUi('
\u0427\u0430\u0442 \u043E\u0447\u0438\u0449\u0435\u043D
\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. + \u2014 \u043D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442, \u043A\u043D\u043E\u043F\u043A\u0430 \u043F\u0430\u043D\u0435\u043B\u0438 \u0441\u043B\u0435\u0432\u0430 \u2014 \u043F\u0440\u043E\u0448\u043B\u044B\u0435 \u0434\u0438\u0430\u043B\u043E\u0433\u0438.
'); setStatus("\u0427\u0430\u0442 \u043E\u0447\u0438\u0449\u0435\u043D"); updateSessionLabel(); syncHistoryBadge(); renderBoard(); updateCtxChip(); } function hideSlashMenu() { const menu = $("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 = $("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 = `${escapeHtml(item.cmd.trim())} \u2014 ${escapeHtml(item.hint)}`; btn.addEventListener("mousedown", (e) => { e.preventDefault(); applySlashPick(item); }); menu.appendChild(btn); }); } function applySlashPick(item) { const input = $("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 = $("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 = $("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 && $("sa_pack") && !state.packUserTouched) { const packId = data.assistant.default_pack; if ([...$("sa_pack").options || []].some((o) => o.value === packId)) { $("sa_pack").value = packId; } } fillEmptyParamsFromExact(); renderPersonaControls(data?.controls || {}, data?.control_values || data?.exact?.controls || {}); syncPersonaDeleteButton(data?.persona_source || data?.personas?.find((p) => p.id === id)?.source); if (state.view === "settings") { if (state.settingsTab === "user") { refreshUserPrefs(); } if (state.settingsTab === "craft") { renderMemoryList(); } if (state.settingsTab === "more") { fillKnobsFromConfig(data); updateCtxChip(); } } }); } function countPromptImages() { try { const box = document.getElementById("alt_prompt_textbox") || document.getElementById("input_prompt"); if (!box) { return 0; } const text = box.value || ""; const matches = text.match(/)/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(/]*>[\s\S]*?<\/image>/gi, "[image omitted]"); s = s.replace(/]*>/gi, "[image omitted]"); if (s.length > CONTEXT_PROMPT_MAX) { s = s.slice(0, CONTEXT_PROMPT_MAX) + "\u2026"; } return s; } function collectLiveContext() { pullLiveIntoSession(); const S = window.SA && window.SA.session; let initCtx = {}; try { if (typeof readInitContext === "function") initCtx = readInitContext(); } catch (e) { } const kreaProfile = detectKreaProfileName2(); const exactDefs = exactProfileDefaults(kreaProfile); const extra = { prompt_image_count: typeof countPromptImages === "function" ? countPromptImages() : 0, has_vision_image: typeof visionReadySlots === "function" ? visionReadySlots().length > 0 : false, image_slots: typeof slotCatalog === "function" ? slotCatalog() : [], attached_slot_ids: typeof attachableSlots === "function" ? attachableSlots().map((s) => s.id) : [], auto_apply: !!$("sa_auto_apply")?.checked, auto_generate: true, krea_profile: kreaProfile, recommended_params: { steps: exactDefs.steps, cfg: exactDefs.cfg, sigma_shift: exactDefs.sigma_shift, sampler: val("input_sampler") || state.exact?.sampling?.defaults?.sampler || "euler", scheduler: val("input_scheduler") || state.exact?.sampling?.defaults?.scheduler || "normal" }, sampling_options: state.exact?.sampling || null, ...initCtx }; if (state.sessionExact && Object.keys(state.sessionExact).length) { extra.session_exact = { ...state.sessionExact }; } if (S && typeof S.compactContext === "function") { const ctx = S.compactContext(state.chatSession, { architecture_ok: typeof isKreaSelected === "function" ? isKreaSelected() : true, promptMax: typeof CONTEXT_PROMPT_MAX !== "undefined" ? CONTEXT_PROMPT_MAX : 2e3, extra }); const block = ctxApi()?.conversationMemoryBlock?.(getContextMemory()); if (block) { ctx.conversation_memory = block; } return ctx; } const g = state.chatSession && state.chatSession.gen || {}; return { session: true, prompt: g.prompt || "", negative: g.negative || "", krea_profile: kreaProfile, ...extra }; } function slimInventoryLoras(list, limit) { const selected = /* @__PURE__ */ new Set(); try { if (typeof loraHelper !== "undefined" && loraHelper && Array.isArray(loraHelper.selected)) { for (const l of loraHelper.selected) { selected.add(String(l.name || l || "").toLowerCase()); } } } catch (e) { } const namesCap = Math.min(limit || INVENTORY_PROMPT_NAMES, INVENTORY_PROMPT_NAMES); const richCap = Math.max(4, Math.min(INVENTORY_PROMPT_RICH, namesCap)); const rows = (list || []).map((l) => { const sel = selected.has(String(l.name || "").toLowerCase()); const hasCard = !!l.has_card; const krea = !!l.krea_likely; const blurb = l.blurb || l.usage_hint || null; return { name: l.name, title: l.title || l.name, trigger_phrase: l.trigger_phrase || null, triggers: Array.isArray(l.triggers) ? l.triggers.slice(0, 8) : void 0, architecture: l.architecture || null, compat_class: l.compat_class || null, has_card: hasCard, krea_likely: krea, blurb, default_weight: l.default_weight || void 0, tags: Array.isArray(l.tags) ? l.tags.slice(0, 6) : void 0, _score: (sel ? 1e3 : 0) + (hasCard ? 200 : 0) + (krea ? 50 : 0) + (blurb ? 10 : 0) }; }); rows.sort((a, b) => b._score - a._score || String(a.name).localeCompare(String(b.name))); let richUsed = 0; const out = []; for (const row of rows) { if (out.length >= namesCap) { break; } const sel = selected.has(String(row.name || "").toLowerCase()); let wantRich = sel; if (!wantRich && richUsed < richCap && (row.krea_likely || row.has_card || row.blurb)) { wantRich = true; } if (wantRich) { const rich = { name: row.name, title: row.title }; if (row.trigger_phrase) { rich.trigger_phrase = row.trigger_phrase; } if (row.triggers) { rich.triggers = row.triggers; } if (row.krea_likely) { rich.krea_likely = true; } if (row.has_card) { rich.has_card = true; } if (row.blurb) { rich.blurb = row.blurb; } if (row.default_weight) { rich.default_weight = row.default_weight; } if (row.architecture) { rich.architecture = row.architecture; } out.push(rich); if (!sel) { richUsed++; } } else { const nameOnly = { name: row.name }; if (row.krea_likely) { nameOnly.krea_likely = true; } out.push(nameOnly); } } return out; } function slimInventoryCheckpoints(list, limit) { const rows = (list || []).slice(); rows.sort((a, b) => (b.krea_likely ? 1 : 0) - (a.krea_likely ? 1 : 0) || (b.has_card ? 1 : 0) - (a.has_card ? 1 : 0) || String(a.name).localeCompare(String(b.name))); return rows.slice(0, limit || 8).map((c) => { const out = { name: c.name, title: c.title || c.name }; if (c.architecture) { out.architecture = c.architecture; } if (c.krea_likely) { out.krea_likely = true; } if (c.has_card) { out.has_card = true; } return out; }); } function pushUnique(arr, value, max) { const v = String(value || "").trim(); if (!v || v.length < 2) { return; } const lower = v.toLowerCase(); const next = (arr || []).filter((x) => String(x).toLowerCase() !== lower); next.unshift(v.slice(0, 80)); return next.slice(0, max); } function extractPatch2(text) { if (window.SA && typeof SA.extractPatch === "function") { return SA.extractPatch(text); } return { prose: text || "", patch: null }; } function visibleAssistantProse(text) { if (window.SA && typeof SA.visibleProse === "function") { return SA.visibleProse(text); } const { prose, patch } = extractPatch2(text); return patch ? prose || "" : String(text || ""); } 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(/]*>[\s\S]*?<\/image>/gi, "").replace(/]*\/?>/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 = $(menuId); const btn = $(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 = $("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 = $("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 (cyrTokenRe2("\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 (cyrTokenRe2("\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) || cyrTokenRe2("\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) || cyrTokenRe2("\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) || cyrTokenRe2("\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 = $("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 S = window.SA && window.SA.session; const wantsGen = S && typeof S.patchWantsGenerate === "function" && S.patchWantsGenerate(patch); if (wantsGen) { patch = ensureExactParamsForGenerate(patch); } 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.scheduler != null && document.getElementById("input_scheduler")) { if (!shouldSkipSessionRollback("scheduler", patch.scheduler)) { setVal("input_scheduler", String(patch.scheduler)); if (shouldRememberSessionParam("scheduler", patch.scheduler)) { rememberSessionExact({ scheduler: patch.scheduler }); } } } if (patch.sampler != null) { if (!shouldSkipSessionRollback("sampler", patch.sampler)) { if (document.getElementById("input_sampler")) { setVal("input_sampler", String(patch.sampler)); } if (shouldRememberSessionParam("sampler", patch.sampler)) { rememberSessionExact({ sampler: patch.sampler }); } } } 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)); } } const Sgen = window.SA && window.SA.session; if (Sgen?.patchWantsGenerate?.(patch) || patch.generate === true || Array.isArray(patch.actions) && patch.actions.map(String).includes("generate")) { forceExactParamsForGenerate({ patch }); } } 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 || $("sa_persona")?.value); } if (preferId && $("sa_persona")) { if ([...$("sa_persona").options].some((o) => o.value === preferId)) { $("sa_persona").value = preferId; await applyPersonaForChat(preferId, { quiet: true }); } } else { loadConfig($("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 !!$("sa_park_llm")?.checked; } function parkLlm() { return new Promise((resolve) => { const model = $("sa_model")?.value; if (!shouldParkLlmBeforeGen() || !model || state.llmParked || typeof genericRequest !== "function") { resolve(false); return; } const baseUrl = $("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 = $("sa_model")?.value; if (!model || typeof genericRequest !== "function") { resolve({ ok: false, alreadyResident: false }); return; } if (!force && !state.llmParked) { resolve({ ok: false, alreadyResident: false }); return; } const baseUrl = $("sa_base_url")?.value || "http://127.0.0.1:11434"; let settled = false; const finish = (ok, alreadyResident = false) => { if (settled) { return; } settled = true; state.llmParked = false; if (ok) { state.expectColdLoad = false; } resolve({ ok: !!ok, alreadyResident: !!alreadyResident }); }; setTimeout(() => finish(false), 18e4); genericRequest( "AssistentWarmLlm", { baseUrl, model }, (data) => { const skipped = !!(data && (data.skipped === "already_resident" || data.already_resident === true)); finish(true, skipped); }, 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 clearStreamStall() { if (state.streamStallTimer) { clearInterval(state.streamStallTimer); state.streamStallTimer = null; } } function armStreamStall(chatEpoch, onStall) { clearStreamStall(); state.lastDeltaAt = Date.now(); state.streamStallTimer = setInterval(() => { if (chatEpoch !== state.chatEpoch) { clearStreamStall(); return; } if (!state.gotDelta || !state.busy || state.generating || state.turnSettled) { return; } const waitMs = state.streamFenceDone ? 800 : state.gotDelta ? 2200 : 15e3; if (Date.now() - (state.lastDeltaAt || 0) < waitMs) { return; } clearStreamStall(); const reply = state.streamText || state.streamEl?.querySelector(".sa-msg-body")?.textContent || ""; try { onStall(String(reply || "")); } catch (e) { console.warn("Assistent stream stall", e); } }, 800); } function settlePartialStream() { const text = String(state.streamText || "").trim(); if (!text) { return; } const persona = $("sa_persona")?.value || localStorage.getItem(LS_PERSONA) || "neutral"; const pack = $("sa_pack")?.value || defaultPackId(); const prose = visibleAssistantProse(text); if (state.streamEl) { finalizeStreamMessage(text, []); } else { appendMessage("assistant", prose); } state.history.push({ role: "assistant", content: prose, persona, pack, activity: captureActivityTrace() || state.pendingActivityTrace || void 0 }); state.pendingActivityTrace = null; persistHistory(); state.turnSettled = true; } 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 } = {}) { settlePartialStream(); 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() { settlePartialStream(); 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; const gen = typeof generateSlot === "function" ? generateSlot() : null; if (gen) { gen.src = null; } if (state.lightboxIndex >= 0) { closeGenLightbox(); } } function resetBoardToDefault() { 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"; clearGenResults(); syncLastImageAlias(); } 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 deltaBeforeLive = patch && typeof patch === "object" ? { ...patch } : {}; const S = window.SA && window.SA.session; const exactKeys = S && S.EXACT_GENERATE_PARAM_KEYS || ["steps", "cfg", "sigma_shift"]; if (typeof pullLiveIntoSession === "function") pullLiveIntoSession(); if (state.chatSession?.gen) { state.chatSession.gen.loras = readLiveGenFields().loras || []; } if (state.chatSession && state.chatSession.gen) { for (const key of exactKeys) { const fromDelta = deltaBeforeLive[key] != null; const fromUser = !!state.lastUserParamIntent && state.sessionExact?.[key] != null; if (!fromDelta && !fromUser) { state.chatSession.gen[key] = null; } } } let working = { ...deltaBeforeLive, generate: true, actions: Array.isArray(deltaBeforeLive.actions) && deltaBeforeLive.actions.map(String).includes("generate") ? deltaBeforeLive.actions : [...Array.isArray(deltaBeforeLive.actions) ? deltaBeforeLive.actions : [], "generate"] }; working = ensureExactParamsForGenerate(working); if (state.chatSession && state.chatSession.gen && (opts.fromSession || opts.force)) { const fromSess = { ...state.chatSession.gen, generate: true, actions: ["generate"] }; for (const k of Object.keys(working)) { if (working[k] != null) fromSess[k] = working[k]; } for (const key of exactKeys) { const fromDelta = deltaBeforeLive[key] != null; const fromUser = !!state.lastUserParamIntent && state.sessionExact?.[key] != null; if (!fromDelta && !fromUser && working[key] != null) { fromSess[key] = working[key]; } } patch = ensureExactParamsForGenerate(fromSess); if (typeof pushSessionToSwarm === "function") await pushSessionToSwarm(state.chatSession); } else { patch = working; } const force = !!opts.force; if (!force && false || !patchHasGenTrigger(patch)) { return null; } const variantItems = normalizeVariantList(patch); const jobs = variantItems ? variantItems.map((item, i) => mergeVariantPatch(patch, item, i)) : null; if (jobs) { state.genResults = jobs.map((j) => ({ id: j.id, label: j.label, src: null, patch: j.patch })); state.selectedGenResultId = null; setBoardTab("generate", { persist: true }); renderBoard(); } else { clearGenResults(); } const epoch = state.chatEpoch; if (shouldParkLlmBeforeGen()) { startBusyUi("parking"); setStatus("\u041E\u0441\u0432\u043E\u0431\u043E\u0436\u0434\u0430\u044E VRAM\u2026"); await parkLlm(); if (epoch !== state.chatEpoch) { return null; } } state.generating = true; setInterruptVisible(true); startBusyUi("generating"); let lastSrc = null; const steps = jobs || [{ id: "var1", label: "Generate", patch: { ...stripMetaPatchKeys(patch), actions: ["generate"] } }]; for (let i = 0; i < steps.length; i++) { if (epoch !== state.chatEpoch) { break; } const job = steps[i]; if (jobs) { setStatus(`\u0412\u0430\u0440\u0438\u0430\u043D\u0442 ${i + 1}/${steps.length}: ${job.label}`); } else { setStatus("\u0413\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u044F\u2026"); } startBusyUi("generating"); if (jobs) { await applyPatch(job.patch, "all"); syncLiveParamsBar(); } ensureNegativeForGenerate(job.patch); forceExactParamsForGenerate({ patch: job.patch }); if (typeof pullLiveIntoSession === "function") { pullLiveIntoSession(); } 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); const parkedBeforeWarm = !!state.llmParked; state.expectColdLoad = parkedBeforeWarm; 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 && !!$("sa_auto_critique")?.checked; if (state.view === "chat" && paneVisible && !willAutoCritique && epoch === state.chatEpoch) { if (parkedBeforeWarm || state.expectColdLoad) { startBusyUi("warming"); setStatus("\u0412\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u044E LLM \u0432 GPU\u2026"); } const warmResult = await warmLlm({ force: true }); if (warmResult?.alreadyResident) { state.expectColdLoad = false; } else if (!warmResult?.ok) { state.expectColdLoad = 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 (!$("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 ($("sa_input")) { $("sa_input").value = "Strict QC of this Generate frame vs the live prompt: list concrete defects and artifacts first, then one fenced JSON patch with an improved prompt and any param/LoRA fixes. No empty praise."; } 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() || $("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 ($("sa_input")) { $("sa_input").value = "Strict review of the Generate frame vs the prompt: what failed, what artifacts you see, what to change in prompt and params next. Skip hollow compliments."; } 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 ($("sa_input")) { $("sa_input").value = label ? `\u041A\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043A\u0438 \u0440\u0430\u0437\u0431\u0435\u0440\u0438 \xAB${label}\xBB: \u0447\u0442\u043E \u043D\u0435 \u0441\u0445\u043E\u0434\u0438\u0442\u0441\u044F \u0441 \u043F\u0440\u043E\u043C\u043F\u0442\u043E\u043C, \u0430\u0440\u0442\u0435\u0444\u0430\u043A\u0442\u044B, \u043A\u043E\u043C\u043F\u043E\u0437\u0438\u0446\u0438\u044F/\u0441\u0432\u0435\u0442 \u2014 \u0438 \u043A\u0430\u043A \u043F\u043E\u043F\u0440\u0430\u0432\u0438\u0442\u044C prompt \u0438 params \u0434\u043B\u044F \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0433\u043E \u043A\u0430\u0434\u0440\u0430. \u0411\u0435\u0437 \u043E\u0431\u0449\u0438\u0445 \u043F\u043E\u0445\u0432\u0430\u043B.` : "\u041A\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043A\u0438 \u0440\u0430\u0437\u0431\u0435\u0440\u0438 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442: \u0447\u0442\u043E \u043D\u0435 \u0441\u0445\u043E\u0434\u0438\u0442\u0441\u044F \u0441 \u043F\u0440\u043E\u043C\u043F\u0442\u043E\u043C, \u0430\u0440\u0442\u0435\u0444\u0430\u043A\u0442\u044B, \u043A\u043E\u043C\u043F\u043E\u0437\u0438\u0446\u0438\u044F/\u0441\u0432\u0435\u0442 \u2014 \u0438 \u043A\u0430\u043A \u043F\u043E\u043F\u0440\u0430\u0432\u0438\u0442\u044C prompt \u0438 params \u0434\u043B\u044F \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0433\u043E \u043A\u0430\u0434\u0440\u0430. \u0411\u0435\u0437 \u043E\u0431\u0449\u0438\u0445 \u043F\u043E\u0445\u0432\u0430\u043B."; } await sendChat({ forceSlotIds: [GEN_ID], skipAutoPack: true }); } function currentPersonaInfo() { const id = ($("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\u043E\u0440\u043C\u0430\u043B\u044C\u043D\u044B\u0439", aggressive: "\u0410\u0433\u0440\u0435\u0441\u0441\u0438\u0432\u043D\u044B\u0439", dreamer: "\u041C\u0435\u0447\u0442\u0430\u0442\u0435\u043B\u044C" }[id] || id) }; } function mountAssistantMeta(div, meta = {}) { if (!div || div.querySelector(".sa-msg-meta")) { return; } const persona = meta.persona || currentPersonaInfo(); const pack = meta.pack || $("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 = $("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, finalPatch ? prose || "" : prose || text || ""); } else { div.textContent = prose || text || ""; } if (finalPatch && !(meta && meta.historical)) { const silent = !!(meta && meta.silentPatch); mountPatchBlock(div, finalPatch, { silent }); } if (role === "assistant" && !(meta && meta.historical)) { mountCurateButtons(div, meta); } if (role === "assistant" && meta?.historical && meta?.activity && window.SA && typeof SA.mountActivityTrace === "function") { SA.mountActivityTrace(meta.activity, { box, insertBefore: div, collapsed: true }); } box.appendChild(div); scrollMessagesToBottom({ force: true }); return div; } function beginStreamMessage(meta) { const box = $("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 = 'Waiting for the model\u2026'; div.appendChild(body); box.appendChild(div); scrollMessagesToBottom({ force: true }); state.streamEl = div; state.streamMeta = meta || null; state.streamFenceDone = false; return div; } function parseTerminalPatchObject(raw) { try { const obj = JSON.parse(String(raw || "").trim()); const terminal = window.SA && typeof SA.isTerminalStreamPatch === "function" ? SA.isTerminalStreamPatch(obj) : typeof isPatchObject === "function" && isPatchObject(obj); return terminal ? obj : null; } catch (e) { return null; } } function streamHasClosedPatchFence(text) { const t = String(text || ""); if (/```[\s\S]*```/.test(t)) { const re = /```(?:json)?\s*([\s\S]*?)```/gi; let match; while ((match = re.exec(t)) !== null) { if (parseTerminalPatchObject(match[1])) { return true; } } } const brace = t.lastIndexOf("{"); if (brace >= 0 && parseTerminalPatchObject(t.slice(brace))) { return true; } 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) { if (parseTerminalPatchObject(match[1])) { lastEnd = match.index + match[0].length; } } if (lastEnd > 0) { return t.slice(0, lastEnd).trimEnd(); } const brace = t.lastIndexOf("{"); if (brace >= 0 && parseTerminalPatchObject(t.slice(brace))) { return t.trimEnd(); } return 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.lastDeltaAt = Date.now(); 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, visibleAssistantProse(state.streamText), { live: true }); scrollMessagesToBottom(); const fn = state.onClosedTerminalFence; state.onClosedTerminalFence = null; if (typeof fn === "function") { try { fn(state.streamText); } catch (e) { console.warn("Assistent closed-fence finalize", e); } } return; } 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) { if (state.turnSettled) { return; } appendMessage("assistant", fullReply, null, civitaiResults, meta || void 0); return; } el.classList.remove("sa-streaming", "sa-typing"); mountAssistantMeta(el, meta || void 0); const { prose, patch } = extractPatch2(fullReply); setAssistantBody(el, patch ? prose || "" : prose || fullReply || ""); el.querySelectorAll(".sa-patch, .sa-civitai-list").forEach((n) => n.remove()); if (patch) { const silent = !!(meta && meta.silentPatch) || !!state.pendingSilentGen; mountPatchBlock(el, patch, { silent }); } if (!(meta && meta.historical)) { mountCurateButtons(el, meta); } scrollMessagesToBottom(); } function mountCurateButtons() { } function curateAssistantMessage() { setStatus("\u041E\u0431\u0443\u0447\u0435\u043D\u0438\u0435 \u0441\u043D\u044F\u0442\u043E \u2014 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 books/knowledge"); } function isTrainingLocked() { return false; } function wantsAutoVision() { return !!$("sa_auto_vision")?.checked; } function looksLikeModelPreview(src) { const s = String(src || "").toLowerCase(); if (!s) { return false; } return s.includes(".preview.") || s.includes("placeholder") || s.includes("/viewspecial/") || s.includes("viewspecial/") || s.includes("/view/models/") || /\/view\/models\//.test(s) || /[?&](?:path|file)=[^&]*\.preview\./i.test(s); } function findCurrentGenerateSrc({ allowPreview = false } = {}) { let src = null; try { const cur = document.getElementById("current_image_img") || document.querySelector("#current_image img") || document.querySelector(".current-image img") || document.querySelector("#current_image_batch img"); if (cur) { src = cur.dataset?.src || cur.getAttribute?.("data-src") || cur.src || null; } } catch (e) { } if (!src) { try { if (typeof currentMetadataMap !== "undefined" && currentMetadataMap && currentMetadataMap.image) { src = currentMetadataMap.image; } } catch (e) { } } if (!src) { return null; } if (!allowPreview && looksLikeModelPreview(src)) { return null; } return src; } function scrubPreviewFromGenerateSlot() { const slot = generateSlot(); if (!slot?.src) { return false; } if (!looksLikeModelPreview(slot.src)) { return false; } slot.src = null; slot.attach = false; syncLastImageAlias(); return true; } function refreshImagePreview() { scrubPreviewFromGenerateSlot(); if (wantsAutoVision()) { const gen = generateSlot(); if (gen) { const src = findCurrentGenerateSrc(); if (src) { gen.attach = true; gen.src = src; } else { gen.attach = false; } renderBoard(); } } syncGenerateSlot(); } function fileToDataUrl(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(String(reader.result || "")); reader.onerror = reject; reader.readAsDataURL(file); }); } async function acceptImageFile(file, slotId) { if (!file || !String(file.type || "").startsWith("image/")) { setStatus("Not an image file"); return false; } const dataUrl = await fileToDataUrl(file); if (slotId) { return setSlotSrc(slotId, dataUrl, { note: `Loaded ${file.name || "image"}` }); } return putImageOnBoard(dataUrl, { note: `Loaded ${file.name || "image"}` }); } async function handleDropDataTransfer(dt, slotId) { if (!dt) { return false; } if (dt.files && dt.files.length) { for (const file of dt.files) { if (String(file.type || "").startsWith("image/")) { return acceptImageFile(file, slotId); } } } const uri = (dt.getData("text/uri-list") || dt.getData("text/plain") || "").trim(); if (uri) { const first = uri.split("\n").map((l) => l.trim()).find((l) => l && !l.startsWith("#")); if (first) { if (slotId) { return setSlotSrc(slotId, first, { note: "Image from drag" }); } return putImageOnBoard(first, { note: "Image from drag" }); } } const html = dt.getData("text/html") || ""; const m = html.match(/src=["']([^"']+)["']/i); if (m && m[1]) { if (slotId) { return setSlotSrc(slotId, m[1], { note: "Image from drag" }); } return putImageOnBoard(m[1], { note: "Image from drag" }); } return false; } async function imageToBase64ForOllama(src, maxEdge = 1024) { if (!src) { return null; } const dataUrl = await srcToDataUrl(src); if (!dataUrl) { return null; } try { const img = await new Promise((resolve, reject) => { const el = new Image(); el.onload = () => resolve(el); el.onerror = reject; el.src = dataUrl; }); const w = img.naturalWidth || img.width || 0; const h = img.naturalHeight || img.height || 0; const edge = Math.max(w, h); const canvas = document.createElement("canvas"); if (!edge || edge <= maxEdge) { canvas.width = Math.max(w, 1); canvas.height = Math.max(h, 1); canvas.getContext("2d").drawImage(img, 0, 0); } else { const scale = maxEdge / edge; canvas.width = Math.max(1, Math.round(w * scale)); canvas.height = Math.max(1, Math.round(h * scale)); canvas.getContext("2d").drawImage(img, 0, 0, canvas.width, canvas.height); } const jpeg = canvas.toDataURL("image/jpeg", 0.85); const i = jpeg.indexOf(","); return i >= 0 ? jpeg.slice(i + 1) : null; } catch (e) { console.warn("Assistent: vision resize failed", e); const i = dataUrl.indexOf(","); return i >= 0 ? dataUrl.slice(i + 1) : null; } } async function srcToDataUrl(src) { if (src.startsWith("data:")) { return src; } try { const resp = await fetch(src); const blob = await resp.blob(); return await new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(String(reader.result || "")); reader.onerror = reject; reader.readAsDataURL(blob); }); } catch (e) { console.warn("Assistent: vision fetch failed", e); return null; } } function loadSettings() { const base = localStorage.getItem(LS_BASE); const model = localStorage.getItem(LS_MODEL); const pack = localStorage.getItem(LS_PACK); let persona = localStorage.getItem(LS_PERSONA); if (persona === "terse") { persona = "aggressive"; localStorage.setItem(LS_PERSONA, persona); } let view = localStorage.getItem(LS_VIEW); const auto = localStorage.getItem(LS_AUTO_VISION); const autoApply = localStorage.getItem(LS_AUTO_APPLY); const autoGen = localStorage.getItem(LS_AUTO_GENERATE); const autoCrit = localStorage.getItem(LS_AUTO_CRITIQUE); const autoDl = localStorage.getItem(LS_AUTO_DOWNLOAD); const parkLlm2 = localStorage.getItem(LS_PARK_LLM); const paneW = localStorage.getItem(LS_PANE_WIDTH); if (base && $("sa_base_url")) { $("sa_base_url").value = base; } if (pack && $("sa_pack")) { $("sa_pack").value = pack; } if (persona && $("sa_persona")) { $("sa_persona").value = persona; } if (auto != null && $("sa_auto_vision")) { $("sa_auto_vision").checked = auto === "1"; } if ($("sa_auto_apply")) { $("sa_auto_apply").checked = autoApply == null ? true : autoApply === "1"; } if ($("sa_auto_generate")) { $("sa_auto_generate").checked = autoGen == null ? true : autoGen === "1"; } if ($("sa_auto_critique") && autoCrit != null) { $("sa_auto_critique").checked = autoCrit === "1"; } if ($("sa_auto_download") && autoDl != null) { $("sa_auto_download").checked = autoDl === "1"; } if ($("sa_park_llm")) { $("sa_park_llm").checked = parkLlm2 === "1"; } if (model) { state.preferredModel = model; } const embed = localStorage.getItem(LS_EMBED); if (embed) { state.preferredEmbed = embed; } if (paneW) { document.documentElement.style.setProperty("--sa-image-width", paneW); } if (view === "cards") { view = "chat"; } if (view === "chat" || view === "settings") { 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: $("sa_pack")?.value || defaultPackId(), persona: $("sa_persona")?.value || "neutral", auto_vision: !!$("sa_auto_vision")?.checked, auto_apply: !!$("sa_auto_apply")?.checked, auto_generate: true, auto_critique: !!$("sa_auto_critique")?.checked, auto_download: !!$("sa_auto_download")?.checked, park_llm: !!$("sa_park_llm")?.checked, pane_width: localStorage.getItem(LS_PANE_WIDTH) || "", embed_model: $("sa_embed_model")?.value || state.preferredEmbed || "", base_url: $("sa_base_url")?.value || "", model: $("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 ($("sa_base_url")) { $("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 ($("sa_pack")) { $("sa_pack").value = v; } }); fill(LS_PERSONA, ui.persona, (v) => { if ($("sa_persona")) { $("sa_persona").value = v; } }); fill(LS_PANE_WIDTH, ui.pane_width, (v) => document.documentElement.style.setProperty("--sa-image-width", v)); if (ui.view === "cards") { ui.view = "chat"; } if (ui.view === "chat" || ui.view === "settings") { 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 = $(id); if (el) { el.checked = on; } } } function saveUiStateToDisk() { diskPersist()?.saveUiState(collectUiState()); } function saveSettings() { localStorage.setItem(LS_BASE, $("sa_base_url")?.value || ""); localStorage.setItem(LS_MODEL, $("sa_model")?.value || ""); localStorage.setItem(LS_EMBED, $("sa_embed_model")?.value || state.preferredEmbed || ""); localStorage.setItem(LS_PACK, $("sa_pack")?.value || defaultPackId()); localStorage.setItem(LS_PERSONA, $("sa_persona")?.value || "neutral"); localStorage.setItem(LS_VIEW, state.view || "chat"); localStorage.setItem(LS_AUTO_VISION, $("sa_auto_vision")?.checked ? "1" : "0"); localStorage.setItem(LS_AUTO_APPLY, $("sa_auto_apply")?.checked ? "1" : "0"); localStorage.setItem(LS_AUTO_GENERATE, $("sa_auto_generate")?.checked ? "1" : "0"); localStorage.setItem(LS_AUTO_CRITIQUE, $("sa_auto_critique")?.checked ? "1" : "0"); localStorage.setItem(LS_AUTO_DOWNLOAD, $("sa_auto_download")?.checked ? "1" : "0"); localStorage.setItem(LS_PARK_LLM, $("sa_park_llm")?.checked ? "1" : "0"); persistOpenRouterModel(); 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 = $("sa_persona")?.value || "neutral"; const settings = { embed_model: $("sa_embed_model")?.value || state.preferredEmbed || "", base_url: $("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 || $("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 && $("sa_pack") && !localStorage.getItem(LS_PACK)) { $("sa_pack").value = data.assistant.default_pack; } if (data.assistant?.embed_model && !state.preferredEmbed) { state.preferredEmbed = data.assistant.embed_model; } const asst = data.assistant || {}; if (asst.history_keep_turns != null) { HISTORY_KEEP_TURNS = Math.max(1, Number(asst.history_keep_turns) || 4); } if (asst.compress_at != null) { COMPRESS_AT = Math.min(0.95, Math.max(0.4, Number(asst.compress_at) || 0.7)); } if (asst.chars_per_token != null) { CHARS_PER_TOKEN = Math.max(1.5, Number(asst.chars_per_token) || 3.2); } if (asst.compress_auto != null) { COMPRESS_AUTO = !!asst.compress_auto; } if (asst.max_ref_slots != null) { MAX_REF_SLOTS = Math.max(1, Number(asst.max_ref_slots) || 4); } if (asst.max_gen_variants != null) { MAX_GEN_VARIANTS = Math.max(2, Math.min(8, Number(asst.max_gen_variants) || 4)); } if (asst.context_prompt_max != null) { CONTEXT_PROMPT_MAX = Math.max(200, Number(asst.context_prompt_max) || 2e3); } if (asst.inventory_prompt_rich != null) { INVENTORY_PROMPT_RICH = Math.max(4, Number(asst.inventory_prompt_rich) || 12); } if (asst.inventory_prompt_names != null) { INVENTORY_PROMPT_NAMES = Math.max(INVENTORY_PROMPT_RICH, Number(asst.inventory_prompt_names) || 24); } fillKnobsFromConfig(data); updateCtxChip(); if (applyDefaults || data.exact) { lastExactForceCkpt = null; fillEmptyParamsFromExact(); if (typeof isKreaSelected === "function" && isKreaSelected()) { forceExactParamsForGenerate({}); lastExactForceCkpt = resolveCurrentCheckpoint()?.name || null; } } const nextPersona = data.persona || $("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 || $("sa_persona")?.value))?.source); } function isDeletablePersonaSource(source) { const src = String(source || ""); return src === "overlay" || src === "overlay+bundled" || src === "overlay+pack"; } function syncPersonaDeleteButton(source) { const btn = $("sa_persona_delete"); if (!btn) { return; } const canDelete = isDeletablePersonaSource(source); btn.hidden = !canDelete; btn.disabled = !canDelete; } let controlSaveTimer = null; let controlsPointerDown = false; let pendingControlsRender = null; function renderPersonaControls(schema, values) { const box = $("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 schema = state.config?.controls || {}; if (!schema.horny) { setStatus("/\u043E\u0441\u0442\u044B\u043D\u044C: \u0443 \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; } state.lastUserControlIntent = true; 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 schema = state.config?.controls || {}; if (!schema.horny) { setStatus("/horny-game: \u0443 \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 cur = getControlValue("horny", Number(schema.horny.default ?? 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": }. \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 = $("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 = $("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 = $("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" && src !== "overlay+pack") { setStatus("\u041C\u043E\u0436\u043D\u043E \u0443\u0434\u0430\u043B\u0438\u0442\u044C \u0442\u043E\u043B\u044C\u043A\u043E overlay-\u0441\u043B\u043E\u0439 (bundled/pack \u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u044E\u0442\u0441\u044F)"); return; } if (!window.confirm(`\u0423\u0434\u0430\u043B\u0438\u0442\u044C \xAB${title}\xBB? \u041F\u043E\u0441\u0442\u0430\u0432\u043A\u0430 (bundled/pack) \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 ($("sa_persona")) { $("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 = $("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 = $("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 = $("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); } else if (action === "sampler") { btn.setAttribute("data-sampler", value); } else if (action === "scheduler") { btn.setAttribute("data-scheduler", value); } box.appendChild(btn); } } function renderSkillChecks(skills, enabled) { const box = $("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 || $("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 ls = state.preferredModel || localStorage.getItem(LS_MODEL) || ""; if (isOpenRouter()) { if (apiPreferred && list.includes(apiPreferred)) { return apiPreferred; } return ls && list.includes(ls) ? ls : list[0]; } const preferred = apiPreferred && list.includes(apiPreferred) ? apiPreferred : pickSeniorChatModel(list); if (ls && list.includes(ls)) { return ls; } return preferred || list[0]; } function isOpenRouter() { return state.provider === "openrouter"; } function providerLabel() { return isOpenRouter() ? "OpenRouter" : "Ollama"; } function renderChatModelOptions() { const current = $("sa_model")?.value || ""; const filter = isOpenRouter() ? String($("sa_openrouter_filter")?.value || "").trim().toLowerCase() : ""; let names = state.chatModels; if (filter) { names = names.filter((n) => n.toLowerCase().includes(filter) || n === current); } setModelOptions(names, { preferred: current || state.chatModelsPreferred }); } function setModelOptions(models, { error, preferred } = {}) { const sel = $("sa_model"); const sel2 = $("sa_settings_chat_model"); const vision = new Set(state.visionModels || []); const apply = (target) => { if (!target) { return; } let names = (models || []).map((n) => String(n || "").trim()).filter(Boolean); names = isOpenRouter() ? [...names].sort((a, b) => a.localeCompare(b)) : [...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 = isOpenRouter() ? "\u041D\u0435\u0442 \u043C\u043E\u0434\u0435\u043B\u0435\u0439 OpenRouter \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u043A\u043B\u044E\u0447 / \u0444\u0438\u043B\u044C\u0442\u0440" : "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 = vision.has(name) ? `${name} \u{1F441}` : name; target.appendChild(opt); } const pick = resolveChatModel(names, preferred); if (pick) { target.value = pick; } }; apply(sel); apply(sel2); } function setEmbedModelOptions(models) { const sel = $("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 = $("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) => { if (data.provider) { setProviderUi(data.provider); } const models = data.models || []; const memoryModels = data.memory_models || []; const preferred = (data.preferred || "").trim(); state.chatModels = models; state.visionModels = data.vision_models || []; state.chatModelsPreferred = preferred; setModelOptions(models, { preferred }); renderChatModelOptions(); setEmbedModelOptions(memoryModels); const pick = resolveChatModel(models, preferred); if (pick && $("sa_model")) { $("sa_model").value = pick; if ($("sa_settings_chat_model")) { $("sa_settings_chat_model").value = pick; } state.preferredModel = pick; localStorage.setItem(LS_MODEL, pick); } const label = providerLabel(); setStatus(models.length ? `${label}: ${models.length} chat \xB7 ${memoryModels.length} memory` : isOpenRouter() ? "OpenRouter: \u043D\u0435\u0442 \u043C\u043E\u0434\u0435\u043B\u0435\u0439" : "No Ollama models (gpu-rent: ollama pull)"); if (models.length) { setOllamaHealth("ok", `${label} \xB7 ${models.length}`, `\u0427\u0430\u0442-\u043C\u043E\u0434\u0435\u043B\u0435\u0439: ${models.length}, \u043F\u0430\u043C\u044F\u0442\u044C: ${memoryModels.length}${data.memory_error ? ` (Ollama: ${data.memory_error})` : ""}`); } else { setOllamaHealth("warn", `${label} \xB7 0 \u043C\u043E\u0434\u0435\u043B\u0435\u0439`, isOpenRouter() ? "\u041D\u0435\u0442 \u043C\u043E\u0434\u0435\u043B\u0435\u0439 \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u043A\u043B\u044E\u0447" : "\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 || `${providerLabel()} unreachable`); state.chatModels = []; setStatus(msg); setModelOptions([], { error: msg }); setOllamaHealth("down", `${providerLabel()} \u2715`, msg); appendMessage("error", msg); } ); } function setProviderUi(provider) { state.provider = provider === "openrouter" ? "openrouter" : "ollama"; if ($("sa_provider")) { $("sa_provider").value = state.provider; } const box = $("sa_openrouter_box"); if (box) { box.hidden = !isOpenRouter(); } } function applyProviderInfo(data) { if (!data || data.error) { return; } setProviderUi(data.provider); state.openrouterModelSaved = data.openrouter_model || ""; const status = $("sa_openrouter_key_status"); if (status) { status.textContent = data.openrouter_key_set ? `\u041A\u043B\u044E\u0447 \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D \u043D\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 ${data.openrouter_key_hint || ""}`.trim() : "\u041A\u043B\u044E\u0447 \u043D\u0435 \u0437\u0430\u0434\u0430\u043D"; } } function providerRequest(name, body) { return new Promise((resolve) => { if (typeof genericRequest !== "function") { resolve(null); return; } genericRequest(name, body || {}, (data) => resolve(data), 0, (err) => { setStatus(String(err || `${name} failed`)); resolve(null); }); }); } async function loadProviderState() { applyProviderInfo(await providerRequest("AssistentGetProvider", {})); } async function setChatProvider(provider) { const data = await providerRequest("AssistentSetProvider", { provider }); applyProviderInfo(data); state.chatModels = []; refreshModels(); probeOllamaHealth(); } async function saveOpenRouterKey(key) { const data = await providerRequest("AssistentSetProvider", { api_key: key }); if (!data) { return; } applyProviderInfo(data); if ($("sa_openrouter_key")) { $("sa_openrouter_key").value = ""; } setStatus(key ? "\u041A\u043B\u044E\u0447 OpenRouter \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D" : "\u041A\u043B\u044E\u0447 OpenRouter \u0443\u0434\u0430\u043B\u0451\u043D"); if (isOpenRouter()) { refreshModels(); } } function persistOpenRouterModel() { const model = $("sa_model")?.value || ""; if (!isOpenRouter() || !model || model === state.openrouterModelSaved) { return; } state.openrouterModelSaved = model; providerRequest("AssistentSetProvider", { model }); } function refreshInventory(done, opts = {}) { if (typeof genericRequest !== "function") { if (done) { done(); } return; } const rescan = !!opts.rescan; genericRequest( "AssistentListInventory", { rescan }, (data) => { state.inventory = { loras: data.loras || [], checkpoints: data.checkpoints || [], wildcards: data.wildcards || [], has_civitai_key: !!data.has_civitai_key, inventory_at: data.inventory_at || Math.floor(Date.now() / 1e3), rescanned: !!data.rescanned }; state.inventoryFetchedAt = Date.now(); const n = state.inventory.loras.length; const ck = state.inventory.checkpoints.length; setStatus(`Inventory: ${n} LoRAs, ${ck} ckpts${rescan ? " (rescanned)" : ""}`); if (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 $("sa_mem_kind")?.value || "all"; } function memoryScopeFilter() { return $("sa_mem_scope")?.value || "all"; } function memorySearchFilter() { return ($("sa_mem_search")?.value || "").trim().toLowerCase(); } function renderMemoryKinds(kinds) { const sel = $("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 = $("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 = $("sa_mem_list"); if (!root) { return; } const rows = filteredMemoryRows(); root.innerHTML = ""; if (!rows.length) { root.innerHTML = '
\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 memory_upsert.
'; 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 = `
${escapeHtml(row.kind || "note")}${escapeHtml(row.key || "")}
${escapeHtml(clipDebug(row.text, 220))}
${escapeHtml([scope, row.source || "user", when].filter(Boolean).join(" \xB7 "))}
`; 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 = $("sa_mem_list"); if (list && !state.memoryRows.length) { list.innerHTML = '
\u0427\u0438\u0442\u0430\u044E \u043F\u0430\u043C\u044F\u0442\u044C\u2026
'; } genericRequest( "AssistentListMemory", { limit: 200 }, (data) => { state.memoryRows = Array.isArray(data?.memories) ? data.memories : []; renderMemoryKinds(data?.kinds || []); renderMemoryList(); const foot = $("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 = `
\u041F\u0430\u043C\u044F\u0442\u044C \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0430: ${escapeHtml(String(err || "\u043E\u0448\u0438\u0431\u043A\u0430"))}
`; } } ); } function forgetMemory(row) { if (!row?.kind || !row?.key || typeof genericRequest !== "function") { return; } genericRequest( "AssistentForgetMemory", { kind: row.kind, key: row.key, source: row.source || "", scope: row.scope || "", persona: row.scope === "personal" ? row.persona || "" : "" }, () => { state.memoryRows = (state.memoryRows || []).filter((m) => !(m.kind === row.kind && m.key === row.key && m.source === row.source && m.persona === row.persona)); renderMemoryList(); setStatus(`\u0417\u0430\u0431\u044B\u0442\u043E: ${row.kind}/${row.key}`); }, 0, (err) => setStatus(String(err || "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0431\u044B\u0442\u044C")) ); } function clearCraftMemory(opts = {}) { if (typeof genericRequest !== "function") { return; } const label = opts.label || "\u043A\u0440\u0430\u0444\u0442-\u043F\u0430\u043C\u044F\u0442\u044C"; if (!window.confirm(`\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C ${label}? Bundled seed \u043E\u0441\u0442\u0430\u043D\u0435\u0442\u0441\u044F.`)) { return; } const body = { scope: opts.scope || "", kind: opts.kind || "", persona: opts.persona || "" }; genericRequest( "AssistentClearMemory", body, (data) => { setStatus(`\u0423\u0434\u0430\u043B\u0435\u043D\u043E \u043A\u0440\u0430\u0444\u0442-\u0437\u0430\u043F\u0438\u0441\u0435\u0439: ${data?.deleted ?? 0}`); refreshMemoryList(); }, 0, (err) => setStatus(String(err || "\u041E\u0447\u0438\u0441\u0442\u043A\u0430 \u043D\u0435 \u0443\u0434\u0430\u043B\u0430\u0441\u044C")) ); } function setSettingsTab(id) { state.settingsTab = id || "behavior"; document.querySelectorAll("#sa_settings .sa-stab").forEach((btn) => { const on = btn.getAttribute("data-stab") === state.settingsTab; btn.classList.toggle("sa-stab-active", on); btn.setAttribute("aria-selected", on ? "true" : "false"); }); document.querySelectorAll("#sa_settings .sa-spane").forEach((pane) => { pane.hidden = pane.getAttribute("data-spane") !== state.settingsTab; }); if (state.settingsTab === "craft") { refreshMemoryList(); } if (state.settingsTab === "user") { refreshUserPrefs(); } if (state.settingsTab === "personas") { renderPersonaSettingsList(); } if (state.settingsTab === "models") { syncSettingsHealthLine(); const m = $("sa_model")?.value; if (m && $("sa_settings_chat_model")) { $("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 = $(id); if (el && v != null && Number.isFinite(Number(v))) { el.value = String(v); } }; setNum("sa_num_ctx", asst.num_ctx); setNum("sa_history_keep", asst.history_keep_turns); setNum("sa_compress_at", asst.compress_at != null ? asst.compress_at : COMPRESS_AT); setNum("sa_chars_per_token", asst.chars_per_token != null ? asst.chars_per_token : CHARS_PER_TOKEN); const autoEl = $("sa_compress_auto"); if (autoEl) { autoEl.checked = asst.compress_auto != null ? !!asst.compress_auto : COMPRESS_AUTO; } setNum("sa_memory_top_k", asst.memory_top_k); const w = asst.user_prefs_weight != null ? Number(asst.user_prefs_weight) : 1; const weightEl = $("sa_user_prefs_weight"); if (weightEl) { weightEl.value = String(Math.max(0, Math.min(1.5, w))); const lab = $("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($(id)?.value); return Number.isFinite(v) ? v : null; }; const assistant = { num_ctx: num("sa_num_ctx"), history_keep_turns: num("sa_history_keep"), compress_at: num("sa_compress_at"), chars_per_token: num("sa_chars_per_token"), compress_auto: $("sa_compress_auto") ? !!$("sa_compress_auto").checked : null, memory_top_k: num("sa_memory_top_k"), user_prefs_weight: num("sa_user_prefs_weight") }; Object.keys(assistant).forEach((k) => { if (assistant[k] == null) { delete assistant[k]; } }); const exact = { profiles: { turbo: { steps: num("sa_exact_turbo_steps"), cfg: num("sa_exact_turbo_cfg"), sigma_shift: num("sa_exact_turbo_sigma") }, raw: { steps: num("sa_exact_raw_steps"), cfg: num("sa_exact_raw_cfg"), sigma_shift: num("sa_exact_raw_sigma") } } }; genericRequest( "AssistentSaveKnobs", { assistant, exact }, (data) => { if (data?.assistant || data?.exact) { applyConfigPayload({ ...state.config, assistant: data.assistant || state.config?.assistant, exact: data.exact || state.config?.exact }); } if (assistant.compress_at != null) { COMPRESS_AT = Math.min(0.95, Math.max(0.4, Number(assistant.compress_at) || 0.7)); } if (assistant.chars_per_token != null) { CHARS_PER_TOKEN = Math.max(1.5, Number(assistant.chars_per_token) || 3.2); } if (assistant.compress_auto != null) { COMPRESS_AUTO = !!assistant.compress_auto; } updateCtxChip(); setStatus("Knobs \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u044B \u0432 overlay"); }, 0, (err) => setStatus(String(err || "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C knobs")) ); } function syncSettingsHealthLine() { const line = $("sa_settings_health_line"); const badge = $("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"; } if (source === "overlay+pack") { return "\u043F\u0430\u043A+\u043F\u0440\u0430\u0432\u043A\u0430"; } if (source === "pack") { return "\u043F\u0430\u043A"; } return "\u0432\u0441\u0442\u0440\u043E\u0435\u043D\u043E"; } function renderPersonaSettingsList() { const root = $("sa_persona_list"); if (!root) { return; } const list = state.personas || state.config?.personas || []; const cur = state.settingsPersonaId || $("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 = `
${escapeHtml(p.title || p.id)}
${escapeHtml(personaSourceLabel(p.source))}
`; 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 && isDeletablePersonaSource(p.source); const del = $("sa_btn_persona_delete_panel"); if (del) { del.disabled = !canDelete; } } function loadPersonaPreview(id) { const box = $("sa_persona_preview"); const knowPanel = $("sa_knowledge_panel"); if (!box || typeof genericRequest !== "function") { return; } box.innerHTML = '
\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430\u2026
'; if (knowPanel) { knowPanel.hidden = true; } 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(); loadPersonaKnowledge(id); }, 0, (err) => { box.innerHTML = `
${escapeHtml(String(err || "\u043E\u0448\u0438\u0431\u043A\u0430"))}
`; } ); } function loadPersonaKnowledge(personaId) { const panel = $("sa_knowledge_panel"); const list = $("sa_knowledge_list"); const saveBtn = $("sa_btn_knowledge_save"); const hint = $("sa_knowledge_hint"); if (!panel || !list || typeof genericRequest !== "function") { return; } list.innerHTML = '
\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u043A\u043D\u0438\u0433\u2026
'; panel.hidden = false; genericRequest( "AssistentListKnowledgeCatalog", { persona: personaId }, (data) => { const cat = data?.knowledge || {}; const attach = new Set((cat.attach || []).map(String)); const books = cat.books || []; const editable = !!data?.editable; if (hint) { hint.textContent = editable ? "\u041E\u0442\u043C\u0435\u0442\u044C \u043A\u043D\u0438\u0433\u0438 \u0434\u043B\u044F FTS-\u043F\u043E\u0438\u0441\u043A\u0430 (ask:knowledge). \u0421\u043E\u0445\u0440\u0430\u043D\u044F\u0435\u0442\u0441\u044F \u0432 overlay knowledge.json." : "Bundled/pack \u2014 attach \u0437\u0430\u0434\u0430\u043D \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u0438\u043B\u0438 assistent-pack.yaml. \u041A\u043B\u043E\u043D\u0438\u0440\u0443\u0439 \u0432 overlay \u0434\u043B\u044F \u043F\u0440\u0430\u0432\u043E\u043A."; } if (saveBtn) { saveBtn.hidden = !editable; } list.innerHTML = ""; if (!books.length) { list.innerHTML = '
\u041A\u043D\u0438\u0433\u0438 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u044B \u043D\u0430 \u0434\u0438\u0441\u043A\u0435. gpu-rent seed-books + up.
'; return; } for (const b of books) { const row = document.createElement("label"); row.className = "sa-check sa-knowledge-row"; const cb = document.createElement("input"); cb.type = "checkbox"; cb.value = b.id; cb.checked = attach.has(String(b.id)); cb.disabled = !editable; row.appendChild(cb); const title = b.title || b.id; const kind = b.content_kind ? ` \xB7 ${b.content_kind}` : ""; const idx = b.indexed ? "" : " \xB7 \u043D\u0435\u0442 search.jsonl"; row.appendChild(document.createTextNode(` ${title} (${b.id})${kind}${idx}`)); list.appendChild(row); } if (saveBtn && editable) { saveBtn.onclick = () => { const picked = [...list.querySelectorAll("input[type=checkbox]:checked")].map((el) => el.value); genericRequest( "AssistentSaveKnowledgeAttach", { persona: personaId, attach: picked }, () => { setStatus("Knowledge attach \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D"); loadPersonaKnowledge(personaId); }, 0, (err) => setStatus(String(err || "save failed")) ); }; } }, 0, (err) => { list.innerHTML = `
${escapeHtml(String(err || "\u043E\u0448\u0438\u0431\u043A\u0430"))}
`; } ); } function exportSelectedPersona() { const id = state.settingsPersonaId || $("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 === "pack" || p.source === "overlay+bundled" || p.source === "overlay+pack"))) { newId = window.prompt("Id \u0437\u0430\u043D\u044F\u0442 bundled/pack \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 || $("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 || !isDeletablePersonaSource(p.source)) { 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 = $("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 = $("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 = $(rootId); if (!root) { return; } root.innerHTML = ""; if (!rows.length) { root.innerHTML = '
\u041F\u0443\u0441\u0442\u043E
'; return; } for (const row of rows) { const el = document.createElement("div"); el.className = "sa-mem-row"; const pin = row.pinned ? " \u2605" : ""; el.innerHTML = `
${escapeHtml(row.key || "")}${pin}
${escapeHtml(clipDebug(row.text, 200))}
`; 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 || $("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 || $("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: $("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 || $("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: $("sa_persona")?.value || "neutral" }, (data) => { setStatus(`\u0423\u0434\u0430\u043B\u0435\u043D\u043E: ${data?.deleted ?? 0}`); refreshUserPrefs(); }, 0, (err) => setStatus(String(err || "\u041E\u0447\u0438\u0441\u0442\u043A\u0430 \u043D\u0435 \u0443\u0434\u0430\u043B\u0430\u0441\u044C")) ); } function resetUiState() { if (!window.confirm("\u0421\u0431\u0440\u043E\u0441\u0438\u0442\u044C UI-state (local + disk)? \u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 Ollama \u0438 prefs \u043E\u0441\u0442\u0430\u043D\u0443\u0442\u0441\u044F.")) { return; } const keys = Object.keys(localStorage).filter((k) => k.startsWith("swarm_assistent_")); for (const k of keys) { localStorage.removeItem(k); } diskPersist()?.saveUiState?.({}); setStatus("UI-state \u0441\u0431\u0440\u043E\u0448\u0435\u043D \u2014 \u043E\u0431\u043D\u043E\u0432\u0438 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0443"); } function modelKeyLeaf(name) { return String(name || "").replace(/\\/g, "/").split("/").pop().replace(/\.(safetensors|ckpt|pt|pth|gguf|bin)$/i, "").trim().toLowerCase(); } function setOllamaHealth(level, text, title) { state.ollamaHealth = level; const el = $("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 = $("sa_base_url")?.value || "http://127.0.0.1:11434"; genericRequest( "AssistentListModels", { baseUrl }, (data) => { if (data?.error) { setOllamaHealth("down", `${providerLabel()} \u2715`, String(data.error)); return; } const chat = (data.models || []).length; const mem = (data.memory_models || []).length; if (!chat) { setOllamaHealth("warn", `${providerLabel()} \xB7 0 \u043C\u043E\u0434\u0435\u043B\u0435\u0439`, isOpenRouter() ? "\u041D\u0435\u0442 \u043C\u043E\u0434\u0435\u043B\u0435\u0439 \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u043A\u043B\u044E\u0447" : "\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", `${providerLabel()} \xB7 ${chat}`, `\u0427\u0430\u0442-\u043C\u043E\u0434\u0435\u043B\u0435\u0439: ${chat}, \u043F\u0430\u043C\u044F\u0442\u044C: ${mem} \xB7 ${data.base_url || baseUrl}`); }, 0, (err) => setOllamaHealth("down", `${providerLabel()} \u2715`, `\u041D\u0435\u0442 \u0441\u0432\u044F\u0437\u0438: ${String(err || "")}`) ); } function setView(view) { if (view === "cards") { view = "chat"; } if (view === "settings") { state.view = "settings"; } else { state.view = "chat"; } const chat = $("sa_view_chat"); const settings = $("sa_view_settings"); if (chat) { chat.hidden = state.view !== "chat"; } if (settings) { settings.hidden = state.view !== "settings"; } const tabActive = (id, on) => { $(id)?.classList.toggle("sa-subtab-active", on); $(id)?.classList.toggle("sa-app-tab-active", on); $(id)?.setAttribute("aria-selected", on ? "true" : "false"); }; tabActive("sa_tab_chat", state.view === "chat"); tabActive("sa_tab_settings", state.view === "settings"); saveSettings(); if (state.view === "settings") { setSettingsTab(state.settingsTab || "behavior"); } else if ((state.llmParked || state.expectColdLoad) && !state.generating) { warmLlm({ force: true }); } } function openSettings(tab) { if (tab) { state.settingsTab = tab; } setView("settings"); } function closeSettings() { setView("chat"); } async function addRefFromUrl(url) { if (!url) { return; } addRefSlot({ src: url, select: false }); } function shortLoraName(name) { const s = String(name || ""); const base = s.split(/[/\\]/).pop() || s; return base.replace(/\.safetensors$/i, "").slice(0, 28); } function readSelectedLoras() { try { if (typeof loraHelper !== "undefined" && loraHelper && Array.isArray(loraHelper.selected)) { return loraHelper.selected.map((l) => ({ name: l.name || l, weight: loraHelper.loraWeightPref && loraHelper.loraWeightPref[l.name || l] || 1 })); } } catch (e) { } return []; } function syncLorasToSession(loras) { const S = window.SA && window.SA.session; if (!S) { return; } const list = Array.isArray(loras) ? loras : readSelectedLoras(); state.chatSession = S.mergeDelta(state.chatSession || S.emptySession(), { loras: list }); } async function applyLorasList(loras) { await applyPatch({ loras }, "loras"); syncLorasToSession(loras); renderLoraChips(); } function renderLoraChips() { const root = $("sa_lora_chips"); if (!root) { return; } root.innerHTML = ""; const selected = readSelectedLoras(); for (const l of selected) { const chip = document.createElement("div"); chip.className = "sa-lora-chip"; chip.title = l.name; const main = document.createElement("button"); main.type = "button"; main.className = "sa-lora-chip-main"; main.textContent = `${shortLoraName(l.name)} ${Number(l.weight).toFixed(2)}`; main.title = `${l.name} \u2014 \u043A\u043B\u0438\u043A \u0438\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u0441\u0438\u043B\u0443`; main.addEventListener("click", (e) => { e.stopPropagation(); openLoraWeightEditor(main, l.name, l.weight); }); const rm = document.createElement("button"); rm.type = "button"; rm.className = "sa-lora-chip-rm"; rm.textContent = "\xD7"; rm.title = "\u0421\u043D\u044F\u0442\u044C LoRA"; rm.addEventListener("click", async (e) => { e.stopPropagation(); const next = selected.filter((x) => x.name !== l.name); await applyLorasList(next); }); chip.appendChild(main); chip.appendChild(rm); root.appendChild(chip); } 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 () => { const cur = readSelectedLoras(); const next = [ ...cur.filter((x) => x.name !== name), { name, weight: l.default_weight ? parseFloat(l.default_weight) : 0.8, triggers: l.triggers || (l.trigger_phrase ? [l.trigger_phrase] : []) } ]; await applyLorasList(next); picker.remove(); }); list.appendChild(btn); if (++n >= 40) { break; } } if (!n) { list.innerHTML = '
\u041D\u0435\u0442 LoRA
'; } }; filter.addEventListener("input", draw); draw(); const composer = $("sa_composer") || document.body; composer.style.position = composer.style.position || "relative"; composer.appendChild(picker); const onDoc = (ev) => { if (!picker.contains(ev.target) && ev.target !== anchor) { picker.remove(); document.removeEventListener("mousedown", onDoc); } }; setTimeout(() => document.addEventListener("mousedown", onDoc), 0); filter.focus(); } function openLoraWeightEditor(anchor, name, weight) { document.querySelectorAll(".sa-lora-weight-pop").forEach((n) => n.remove()); const pop = document.createElement("div"); pop.className = "sa-lora-weight-pop"; const label = document.createElement("label"); label.textContent = shortLoraName(name); const input = document.createElement("input"); input.type = "number"; input.min = "0"; input.max = "2"; input.step = "0.05"; input.value = String(Number(weight) || 1); input.title = "\u0421\u0438\u043B\u0430 LoRA (0\u20132)"; label.appendChild(input); pop.appendChild(label); const row = document.createElement("div"); row.className = "sa-lora-weight-actions"; const ok = document.createElement("button"); ok.type = "button"; ok.className = "basic-button sa-primary"; ok.textContent = "OK"; const cancel = document.createElement("button"); cancel.type = "button"; cancel.className = "basic-button"; cancel.textContent = "\u041E\u0442\u043C\u0435\u043D\u0430"; row.appendChild(ok); row.appendChild(cancel); pop.appendChild(row); const apply = async () => { const w = Math.min(2, Math.max(0, parseFloat(input.value) || 0)); const next = readSelectedLoras().map((l) => l.name === name ? { ...l, weight: w } : l); await applyLorasList(next); pop.remove(); }; ok.addEventListener("click", apply); cancel.addEventListener("click", () => pop.remove()); input.addEventListener("keydown", (e) => { if (e.key === "Enter") { e.preventDefault(); apply(); } if (e.key === "Escape") { pop.remove(); } }); const composer = $("sa_composer") || document.body; composer.style.position = composer.style.position || "relative"; composer.appendChild(pop); const rect = anchor.getBoundingClientRect(); const cRect = composer.getBoundingClientRect(); pop.style.left = `${Math.max(4, rect.left - cRect.left)}px`; pop.style.top = `${rect.bottom - cRect.top + 4}px`; input.focus(); input.select(); const onDoc = (ev) => { if (!pop.contains(ev.target) && ev.target !== anchor) { pop.remove(); document.removeEventListener("mousedown", onDoc, true); } }; setTimeout(() => document.addEventListener("mousedown", onDoc, true), 0); } function inventoryIsStale(maxAgeMs = 2e4) { if (!state.inventoryFetchedAt) { return true; } return Date.now() - state.inventoryFetchedAt > maxAgeMs; } async function ensureFreshInventory({ forceRescan } = {}) { const rescan = forceRescan || inventoryIsStale(2e4); await refreshInventoryAsync({ rescan }); } function triggerSwarmModelRefresh(done) { if (typeof genericRequest !== "function") { if (done) { done(); } return; } genericRequest( "TriggerRefresh", { strong: true }, () => { if (done) { done(); } }, 0, () => { if (done) { done(); } } ); } async function handleReplySideEffects(reply, civitaiResults, opts = {}) { const { fromAutoCritique, fromVisionHop, fromDebug } = opts; void civitaiResults; if (fromDebug) { state.pendingSilentGen = false; return; } const extracted = typeof extractPatch2 === "function" ? extractPatch2(reply) : { patch: null }; let effective = extracted && extracted.patch ? extracted.patch : null; if (opts.fromPromptEnRetry && typeof mergePromptEnRewrite === "function" && (effective || state.pendingPromptEnMerge)) { effective = mergePromptEnRewrite(effective); } const S = window.SA && window.SA.session; const act = getActivity(); if (effective && act && typeof act.noteModelCommands === "function") { act.noteModelCommands(effective); } const sessionPrompt = String(state.chatSession?.gen?.prompt || "").trim(); const userWantsGenerate = !!opts.userWantsGenerate || !!state.turnUserWantsGenerate; let intent = resolveTurnIntent3(effective, opts.userText || "", { ...opts, sessionPrompt, userWantsGenerate }); if (userWantsGenerate && !intent.vetoed && !fromAutoCritique) { intent = { ...intent, generate: true }; } if (!effective && intent.generate && sessionPrompt) { effective = { generate: true }; } const promptChanged = !!(effective && String(effective.prompt || "").trim()); if (Array.isArray(effective?.actions) && effective.actions.map(String).includes("interrupt")) { if (typeof doInterruptNow === "function") doInterruptNow(); } if (effective) { if (intent.generate) { const acts = Array.isArray(effective.actions) ? effective.actions.map(String) : []; effective = { ...effective, actions: acts.includes("generate") ? acts : acts.concat("generate"), generate: true }; } if (!intent.look && typeof stripLookAt === "function") effective = stripLookAt(effective); if (typeof rememberLastPatch === "function") rememberLastPatch(effective); } if (intent.generate && effective && S) { state.chatSession = S.mergeDelta(state.chatSession || S.emptySession(), effective); activityDone("delta", { kind: "delta", label: promptChanged ? "\u041F\u0440\u043E\u043C\u043F\u0442 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D" : "\u041E\u0431\u043D\u043E\u0432\u0438\u043B \u0441\u0435\u0441\u0441\u0438\u044E", detail: Object.keys(effective).filter((k) => effective[k] != null && !["actions", "notes"].includes(k)).slice(0, 10).join(", ") }); try { const chat = typeof findChat === "function" ? findChat(state.activeChatId) : null; if (chat && typeof snapshotChatParams === "function") { chat.params = snapshotChatParams(); if (typeof persistChatsStore === "function") persistChatsStore(); } } catch (e) { } } if (intent.vetoed) { state.pendingSilentGen = false; const a = getActivity(); if (a) { a.skip("generate", { kind: "generate", label: "Generate \u043E\u0442\u043C\u0435\u043D\u0451\u043D", detail: "\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C \u043F\u043E\u043F\u0440\u043E\u0441\u0438\u043B \u043D\u0435 \u0433\u0435\u043D\u0435\u0440\u0438\u0440\u043E\u0432\u0430\u0442\u044C" }); } } const askList = Array.isArray(intent.ask) ? intent.ask : []; if (askList.length && typeof claimTurnHop === "function" && claimTurnHop("ask")) { activityStep("ask", { kind: "ask", label: `\u0417\u0430\u043F\u0440\u043E\u0441\u0438\u043B ${askList.join(", ")}`, detail: "\u043F\u043E\u0434\u0433\u0440\u0443\u0436\u0430\u044E \u0434\u0435\u0442\u0430\u043B\u0438\u2026", status: "running" }); pullLiveIntoSession(); const bits = []; if (askList.some((a) => /settings/i.test(String(a)))) { const dump = S && typeof S.fullSettingsDump === "function" ? S.fullSettingsDump(state.chatSession, { exact: state.exact, kreaProfiles: state.kreaProfiles, sessionExact: state.sessionExact }) : collectLiveContext(); bits.push("SETTINGS_JSON:\n" + JSON.stringify(dump)); } if (askList.some((a) => /inventory/i.test(String(a)))) { const inv = state.inventory || {}; bits.push("INVENTORY_JSON:\n" + JSON.stringify({ loras: (inv.loras || []).slice(0, 40).map((l) => ({ name: l.name || l, trigger_phrase: l.trigger_phrase || null })), checkpoints: (inv.checkpoints || []).slice(0, 16).map((c) => c.name || c), wildcards: (inv.wildcards || []).slice(0, 20).map((w) => w.name || w) })); } if (bits.length) { activityDone("ask", { detail: "\u0434\u0435\u0442\u0430\u043B\u0438 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u044B \u043C\u043E\u0434\u0435\u043B\u0438" }); await sendChat({ skipSlash: true, skipAutoPack: true, fromAskHop: true, forcedUserText: bits.join("\n\n") }); return; } } if (effective && intent.look && !fromVisionHop && !fromAutoCritique) { activityStep("look", { kind: "look", label: "\u0421\u043C\u043E\u0442\u0440\u0438\u0442 \u043D\u0430 \u043A\u0430\u0434\u0440", status: "running" }); if (typeof maybeVisionHop === "function") { const hopped = await maybeVisionHop(effective, opts.attachedSlotIds || []); if (hopped) { activityDone("look", { detail: "vision hop" }); return; } activityDone("look", { detail: "\u043A\u0430\u0434\u0440 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D" }); } } if (intent.generate && effective?.prompt && typeof promptNeedsKreaPrep === "function" && promptNeedsKreaPrep(effective.prompt) && !fromVisionHop && typeof claimTurnHop === "function" && claimTurnHop("krea_prep")) { state.pendingPromptEnMerge = { ...effective }; activityStep("prep", { kind: "prep", label: "\u0413\u043E\u0442\u043E\u0432\u043B\u044E \u043F\u0440\u043E\u043C\u043F\u0442 \u0434\u043B\u044F Krea", detail: "EN + \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0430", status: "running" }); if (typeof appendSystemNote === "function") appendSystemNote("\u0413\u043E\u0442\u043E\u0432\u043B\u044E \u043F\u0440\u043E\u043C\u043F\u0442 \u0434\u043B\u044F Krea\u2026"); await sendChat({ skipSlash: true, skipAutoPack: true, fromPromptEnRetry: true, userWantsGenerate: true, forcedUserText: typeof buildKreaPromptPrepRequest === "function" ? buildKreaPromptPrepRequest(effective) : String(effective.prompt || "") }); return; } if (intent.generate) { activityStep("generate", { kind: "generate", label: intent.vetoed ? "Generate \u043E\u0442\u043C\u0435\u043D\u0451\u043D (\u0432\u0435\u0442\u043E)" : "Generate", status: intent.vetoed ? "skip" : "running" }); if (typeof startBusyUi === "function") startBusyUi("silent_gen"); if (effective) { effective = ensureExactParamsForGenerate({ ...effective, generate: true, actions: Array.isArray(effective.actions) && effective.actions.map(String).includes("generate") ? effective.actions : [...Array.isArray(effective.actions) ? effective.actions : [], "generate"] }); } if (S && effective) state.chatSession = S.mergeDelta(state.chatSession || S.emptySession(), effective); if (effective?.loras == null && state.chatSession?.gen) { state.chatSession.gen.loras = readLiveGenFields().loras || []; } await pushSessionToSwarm(state.chatSession); if (typeof syncLiveParamsBar === "function") syncLiveParamsBar(); if (typeof appendSystemNote === "function") { appendSystemNote(promptChanged ? "\u041F\u0440\u043E\u043C\u043F\u0442 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D \xB7 Generate" : "\u0417\u0430\u043F\u0443\u0441\u043A\u0430\u044E Generate"); } setStatus(promptChanged ? "\u041F\u0440\u043E\u043C\u043F\u0442 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D \xB7 Generate" : "Generate"); const srcOut = await runGenerateFromPatch( { ...effective || {}, actions: ["generate"], generate: true }, { force: true, fromSession: true } ); activityDone("generate", { detail: srcOut ? "\u043A\u0430\u0434\u0440 \u0433\u043E\u0442\u043E\u0432" : "\u0431\u0435\u0437 \u043A\u0430\u0434\u0440\u0430" }); if (srcOut) { if (typeof maybeAutoCritique === "function") await maybeAutoCritique(srcOut); if (typeof maybeAutoVisionLook === "function") await maybeAutoVisionLook(srcOut); } } else if (effective) { if (!state.generating && typeof stopBusyUi === "function") { stopBusyUi(intent.vetoed ? "\u0417\u0430\u043F\u043E\u043C\u043D\u0438\u043B \xB7 \u0431\u0435\u0437 Generate" : ""); } } state.pendingSilentGen = false; reportDebugClientTurn({ user: opts.userText || "", reply: String(reply || "").slice(0, 8e3), patch: effective, intent, generating: !!intent.generate, swarm_prompt: val("alt_prompt_textbox") || val("input_prompt") || "", persona: $("sa_persona")?.value || "", pack: $("sa_pack")?.value || "" }); } function reportDebugClientTurn(payload) { try { const body = JSON.stringify({ ts: Date.now(), ...payload }); fetch("http://127.0.0.1:17821/assistent/client-event", { method: "POST", headers: { "Content-Type": "application/json" }, body, mode: "cors" }).catch(() => { }); } catch (e) { } } async function applyQuickPatch(patch, note, opts = {}) { const wantGenerate = opts.generate === true; let withActions = { ...patch }; delete withActions.generate; if (Array.isArray(withActions.actions)) { withActions.actions = withActions.actions.filter((a) => String(a) !== "generate"); if (!withActions.actions.length) { delete withActions.actions; } } if (wantGenerate) { withActions.generate = true; withActions.actions = [ ...Array.isArray(withActions.actions) ? withActions.actions : [], "generate" ]; } const prevIntent = state.lastUserParamIntent; state.lastUserParamIntent = true; const S = window.SA && window.SA.session; if (wantGenerate && S && S.patchWantsGenerate && S.patchWantsGenerate(withActions)) { withActions = ensureExactParamsForGenerate(withActions); } if (S) { state.chatSession = S.mergeDelta(state.chatSession || S.emptySession(), withActions); if (withActions.loras == null && state.chatSession?.gen) { state.chatSession.gen.loras = readLiveGenFields().loras || []; } } state._quietParamApply = (state._quietParamApply || 0) + 1; try { if (wantGenerate) { await pushSessionToSwarm(state.chatSession); } else if (isParamOnlyQuickPatch(withActions)) { await applyPatch(withActions, "params"); } else { await pushSessionToSwarm(state.chatSession); } } finally { state._quietParamApply = Math.max(0, (state._quietParamApply || 1) - 1); } pinUserSessionParams(withActions); state.lastUserParamIntent = prevIntent; setStatus(note || (wantGenerate ? "Applied" : "\u041F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B (\u0431\u0435\u0437 Generate)")); if (wantGenerate) { await runGenerateFromPatch(withActions, { force: true, fromSession: true }); } syncChipHighlight(); syncLiveParamsBar(); } function syncChipHighlight() { const bar = $("sa_chips"); if (!bar) { return; } const defs = canonicalParamDefaults(); const ckptProfile = detectKreaProfileName2(); const cur = guessAspectFromSize(val("input_width"), val("input_height")); const seed = val("input_seed"); bar.querySelectorAll("[data-aspect]").forEach((btn) => { const aspectVal = btn.getAttribute("data-aspect"); const active = aspectVal === cur; btn.classList.toggle("sa-chip-active", active); btn.classList.toggle("sa-chip-custom", active && !valuesMatchParam(aspectVal, defs.aspect, "aspect")); }); 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); btn.classList.toggle("sa-chip-custom", mode === "lock" && active); }); bar.querySelectorAll("[data-krea-profile]").forEach((btn) => { const p = btn.getAttribute("data-krea-profile"); const profDefs = exactProfileDefaults(p); const stepsMatch = valuesMatchParam(val("input_steps"), profDefs.steps, "steps"); const cfgMatch = valuesMatchParam(val("input_cfgscale") || val("input_cfg"), profDefs.cfg, "cfg"); const active = stepsMatch && cfgMatch; btn.classList.toggle("sa-chip-active", active); btn.classList.toggle("sa-chip-custom", active && p !== ckptProfile); }); const curSampler = (val("input_sampler") || defs.sampler || "").toLowerCase(); const curScheduler = (val("input_scheduler") || defs.scheduler || "").toLowerCase(); bar.querySelectorAll("[data-sampler]").forEach((btn) => { const v = btn.getAttribute("data-sampler").toLowerCase(); const active = v === curSampler; btn.classList.toggle("sa-chip-active", active); btn.classList.toggle("sa-chip-custom", active && !valuesMatchParam(v, defs.sampler, "sampler")); }); bar.querySelectorAll("[data-scheduler]").forEach((btn) => { const v = btn.getAttribute("data-scheduler").toLowerCase(); const active = v === curScheduler; btn.classList.toggle("sa-chip-active", active); btn.classList.toggle("sa-chip-custom", active && !valuesMatchParam(v, defs.scheduler, "scheduler")); }); } function appendSystemNote(text) { const box = $("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 = $("sa_persona")?.value || "neutral"; const pack = $("sa_pack")?.value || defaultPackId(); const chatModel = $("sa_model")?.value || "\u2014"; const embed = $("sa_embed_model")?.value || state.preferredEmbed || "\u2014"; const profile = detectKreaProfileName2(); 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 (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=${!!$("sa_auto_apply")?.checked} gen=${true} vision=${!!$("sa_auto_vision")?.checked} critique=${!!$("sa_auto_critique")?.checked}`, "", "Live SwarmUI:", ` ckpt=${ctx.checkpoint?.name || "\u2014"} \xB7 krea_profile=${ctx.krea_profile || profile}`, ` ${ctx.width || "?"}\xD7${ctx.height || "?"} (${aspect}) \xB7 steps=${ctx.steps ?? "\u2014"} \xB7 cfg=${ctx.cfg ?? "\u2014"} \xB7 sigma=${ctx.sigma_shift ?? "\u2014"} \xB7 seed=${ctx.seed ?? "\u2014"} \xB7 batch=${ctx.batch ?? "\u2014"}`, ` loras: ${formatDebugLoras(ctx.selected_loras || ctx.enabled_loras)}`, ` available_loras=${(ctx.available_loras || []).length}${ctx.available_loras_truncated ? ` truncated/${ctx.available_loras_total || "?"}` : ""}`, ` prompt: ${clipDebug(ctx.prompt, 220)}`, ` negative: ${clipDebug(ctx.negative, 120)}`, ` init=${!!ctx.has_init_image} mask=${!!ctx.has_mask_image} prompt_images=${ctx.prompt_image_count || 0}`, ` has_vision_image=${!!ctx.has_vision_image} \xB7 images_in_request=${!!ctx.images_in_request} \xB7 vision_ready=${visionReadySlots().length}`, ` context_json_chars\u2248${JSON.stringify(ctx).length} \xB7 last_system_chars=${state.lastSystemChars || "\u2014"} \xB7 last_context_chars=${state.lastContextChars || "\u2014"}`, state.lastSystemLayers ? ` system_layers: ${Object.entries(state.lastSystemLayers).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : " system_layers: \u2014 (\u043E\u0442\u043F\u0440\u0430\u0432\u044C \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435, \u0447\u0442\u043E\u0431\u044B \u0437\u0430\u043F\u043E\u043B\u043D\u0438\u0442\u044C)", (() => { const b = currentBudgetEstimate(); const mem = getContextMemory(); return ` budget\u2248${b.used}/${b.numCtx} (${b.fromEval ? "eval" : "est"}) \xB7 level=${b.level} \xB7 memory_until=${mem.untilCount || 0} \xB7 auto_compress=${COMPRESS_AUTO}`; })(), "", "Exact defaults (merged):", ` generation=${JSON.stringify(exactGen)}`, ` effective=${JSON.stringify({ steps: defaults.steps, cfg: defaults.cfg, sigma_shift: defaults.sigma_shift, aspect: defaults.aspect, images: defaults.images, profile: defaults.profile })}`, ` session_exact=${Object.keys(session).length ? JSON.stringify(session) : "{}"}`, "", "\u041F\u043E\u0447\u0435\u043C\u0443 \u0442\u0430\u043A:", ...why.map((w) => ` \xB7 ${w}`) ]; if (state.lastPatch) { lines.push("", `last_patch: ${clipDebug(JSON.stringify(state.lastPatch), 360)}`); } return lines.join("\n"); } async function handleSlashCommand(raw) { const text = String(raw || "").trim(); if (!text.startsWith("/")) { return false; } const parts = text.slice(1).split(/\s+/); const cmd = (parts[0] || "").toLowerCase(); const arg = parts.slice(1).join(" ").trim(); if (cmd === "help" || cmd === "?") { appendSystemNote(HELP_TEXT); setStatus("/help"); return true; } if (cmd === "new" || cmd === "newchat") { await startNewChat({ saveCurrent: true, openDrawer: true }); return true; } if (cmd === "history" || cmd === "chats" || cmd === "sessions") { setChatsPanelOpen(!state.chatsPanelOpen); setStatus(state.chatsPanelOpen ? "/history" : "\u0427\u0430\u0442\u044B \u0441\u043A\u0440\u044B\u0442\u044B"); return true; } if (cmd === "compress" || cmd === "compact" || cmd === "\u0441\u0436\u0430\u0442\u044C") { await compressNowFromUi(); return true; } if (cmd === "debug" || cmd === "dbg" || cmd === "why") { const dump = buildDebugSummary(); appendSystemNote(dump); const argL = String(arg || "").toLowerCase().trim(); const wantLlm = cmd === "why" || /^(ask|llm|explain|поясни|почему|модель)(\s|$)/i.test(argL); if (wantLlm) { setStatus("/debug ask\u2026"); await sendChat({ forcedUserText: "\u041E\u0442\u043B\u0430\u0434\u043A\u0430 Assistent. \u041D\u0438\u0436\u0435 \u0444\u0430\u043A\u0442\u044B UI (\u0443\u0436\u0435 \u0441\u043E\u0431\u0440\u0430\u043D\u044B \u043A\u043B\u0438\u0435\u043D\u0442\u043E\u043C). \u041A\u0440\u0430\u0442\u043A\u043E \u0441\u0432\u043E\u0438\u043C\u0438 \u0441\u043B\u043E\u0432\u0430\u043C\u0438 (5\u201310 \u0441\u0442\u0440\u043E\u043A, \u044F\u0437\u044B\u043A \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F): \u043A\u0430\u043A\u0438\u0435 \u043F\u0440\u043E\u043C\u043F\u0442/params \u0441\u0435\u0439\u0447\u0430\u0441, \u0447\u0442\u043E \u0438\u0437 Exact vs session_exact vs live, \u0447\u0442\u043E \u0441\u0434\u0435\u043B\u0430\u043B \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0439 \u043F\u0430\u0442\u0447 \u0438 \u043F\u043E\u0447\u0435\u043C\u0443 \u0442\u0430\u043A \u043B\u043E\u0433\u0438\u0447\u043D\u043E. \u0411\u0435\u0437 JSON patch, \u0431\u0435\u0437 generate, \u0431\u0435\u0437 look_at.\n\n" + dump, skipSlash: true, skipAutoPack: true, fromDebug: true, skipAppendUser: true }); } else { setStatus("/debug"); } return true; } if (cmd === "gen" || cmd === "generate") { const prev = findCurrentGenerateSrc(); startBusyUi("generating"); state.generating = true; setInterruptVisible(true); if (!triggerGenerate()) { state.generating = false; stopBusyUi("Could not start Generate"); return true; } const src = await waitForNewImage(prev); state.generating = false; setInterruptVisible(state.busy); if (src) { const gen = generateSlot(); if (gen) { gen.src = src; renderBoard(); } stopBusyUi("Generate done"); } else { stopBusyUi("Generate finished"); } return true; } if (cmd === "look") { const id = normalizeSlotId(arg || GEN_ID) || GEN_ID; const slot = slotById(id); if (!slot) { setStatus(`\u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u044B\u0439 \u0441\u043B\u043E\u0442: ${arg || GEN_ID}`); return true; } if (slot.type !== "generate") { setBoardTab("refs"); } else { setBoardTab("generate"); } if (!slot.src && id === GEN_ID) { const src = findCurrentGenerateSrc(); if (src) { slot.src = src; } } if (!slot.src) { setStatus(`Slot ${id} is empty`); return true; } slot.attach = true; renderBoard(); if ($("sa_input")) { const critical = id === GEN_ID || slot.type === "generate"; $("sa_input").value = critical ? "Strict QC of this Generate frame vs the live prompt: concrete defects first, then JSON patch with prompt/param fixes. No empty praise." : `Critically review ${id} vs the intended scene: defects and how to fix prompt/params.`; } 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 }, `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 }, "Seed random"); } return true; } if (cmd === "vary") { await applyQuickPatch({ vary: true, seed: -1 }, "Vary (new seed)", { generate: true }); return true; } if (cmd === "sampler" || cmd === "\u0441\u0435\u043C\u043F\u043B\u0435\u0440") { const name = (arg || "").trim().toLowerCase(); if (!name) { const opts = (state.exact?.sampling?.samplers || ["euler", "heun", "dpmpp_2m"]).join(", "); setStatus(`Usage: /sampler ${opts}`); return true; } await applyQuickPatch({ sampler: name }, `Sampler ${name}`); return true; } if (cmd === "scheduler" || cmd === "\u0448\u0435\u0434\u0443\u043B\u0435\u0440") { const name = (arg || "").trim().toLowerCase(); if (!name) { const opts = (state.exact?.sampling?.schedulers || ["normal", "simple", "karras"]).join(", "); setStatus(`Usage: /scheduler ${opts}`); return true; } await applyQuickPatch({ scheduler: name }, `Scheduler ${name}`); return true; } if (cmd === "inventory" || cmd === "inv") { setStatus("Rescanning models\u2026"); triggerSwarmModelRefresh(async () => { await refreshInventoryAsync({ rescan: true }); const n = state.inventory?.loras?.length || 0; const ck = state.inventory?.checkpoints?.length || 0; appendSystemNote(`Inventory refreshed: ${n} LoRAs, ${ck} checkpoints.`); setStatus(`Inventory: ${n} LoRAs, ${ck} ckpts (rescanned)`); }); return true; } if (cmd === "pack") { if (!setPackValue(arg, { flash: true, user: true })) { setStatus("Pack: write|ordinary|critique|compose|params|inpaint|describe|persona"); } else { setStatus(`Pack \u2192 ${$("sa_pack")?.value}`); } return true; } if (cmd === "\u043E\u0441\u0442\u044B\u043D\u044C" || cmd === "ostyn" || cmd === "cool" || cmd === "cooldown") { coolDownHorny(); return true; } if (cmd === "horny-game" || cmd === "hornygame" || cmd === "horny_game") { await startHornyGame(); return true; } if (cmd === "persona") { const sub = (parts[1] || "new").toLowerCase(); const rest = parts.slice(2).join(" ").trim(); setPackValue("author_persona", { flash: true, user: true }); if (sub === "save") { await sendChat({ skipAutoPack: true, forcedUserText: "\u0421\u043E\u0445\u0440\u0430\u043D\u0438 \u0441\u043E\u0433\u043B\u0430\u0441\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A \u043B\u0438\u0447\u043D\u043E\u0441\u0442\u0438 \u0441\u0435\u0439\u0447\u0430\u0441 (persona_clone / persona_write). \u041D\u0435 \u0443\u0434\u0430\u043B\u044F\u0439 \u043B\u0438\u0447\u043D\u043E\u0441\u0442\u0438." }); return true; } const fromId = sub === "clone" && rest ? rest.split(/\s+/)[0] : $("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 ($("sa_input")) { $("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 (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 = ($("sa_input")?.value || "").trim(); const text = (opts.forcedUserText || rawInput).trim(); if (!text) { return; } if (state.generating && !isContinuationTurn(opts)) { setStatus("\u0418\u0434\u0451\u0442 Generate \u2014 \u043D\u0430\u0436\u043C\u0438 \u0421\u0442\u043E\u043F, \u043F\u043E\u0442\u043E\u043C \u043E\u0442\u043F\u0440\u0430\u0432\u044C"); return; } if (state.busy && !isContinuationTurn(opts)) { abortInFlightWork({ status: "\u041D\u043E\u0432\u043E\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435", interruptSwarm: false }); } if (!isMachineTurn(opts)) { state.lastUserParamIntent = userTextMentionsParams(text); state.lastUserControlIntent = userTextMentionsControls(text); state.pendingSilentGen = false; state.turnUserWantsGenerate = userAsksGenerate2(text); } if (!isMachineTurn(opts) && !opts.skipSlash) { if (rawInput.startsWith("/")) { if ($("sa_input")) { $("sa_input").value = ""; } const handled = await handleSlashCommand(rawInput); if (handled) { return; } } } if (!isMachineTurn(opts) && isSameButAspectRequest(text)) { const aspect = parseAspectFromUserText(text); if (aspect) { if ($("sa_input")) { $("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}`, { generate: true }); return; } } if (!updateGate()) { setStatus("\u0412\u044B\u0431\u0435\u0440\u0438 \u043C\u043E\u0434\u0435\u043B\u044C Krea 2"); return; } if (!opts.skipAutoPack && !isMachineTurn(opts)) { const guessed = autoSelectPack(text); if (guessed) { setPackValue(guessed, { flash: true }); } } if (!opts.fromDebug && false) { setPackValue("ordinary", { flash: false }); } const pack = opts.fromDebug ? "debug_explain" : $("sa_pack")?.value || defaultPackId(); const persona = $("sa_persona")?.value || localStorage.getItem(LS_PERSONA) || "neutral"; const model = $("sa_model")?.value; if (!model) { setStatus("\u0412\u044B\u0431\u0435\u0440\u0438 \u043C\u043E\u0434\u0435\u043B\u044C \u0447\u0430\u0442\u0430 \u0432 \u2699"); refreshModels(); return; } const chatEpoch = bumpChatEpoch(); state.busy = true; state.turnSettled = false; 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 }); } 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 ($("sa_input")) { $("sa_input").value = ""; } persistHistory(); } else { const marker = opts.historyUserText || (opts.fromDebug ? "/debug ask" : ""); if (marker) { state.history.push({ role: "user", content: marker }); persistHistory(); } } if (!isContinuationTurn(opts) && !opts.fromAskHop) { state.pendingActivityTrace = null; const hopLabel = opts.fromVisionHop ? "vision hop" : opts.fromPromptEnRetry ? "krea prep" : opts.fromAutoCritique ? "critique" : opts.fromDebug ? "debug" : ""; activityBegin(opts.fromDebug ? "Debug" : "\u0425\u043E\u0434 Assistent", { model, pack, persona, skills: opts.fromDebug ? [] : state.enabledSkills || [], hop: hopLabel || void 0 }); activityStep("think", { kind: "think", label: "\u0414\u0443\u043C\u0430\u044E\u2026", status: "running" }); } else if (opts.fromAskHop) { getActivity()?.setContext?.({ hop: "ask hop", pack, model }); activityStep("think", { kind: "think", label: "\u041E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0441 \u0434\u0435\u0442\u0430\u043B\u044F\u043C\u0438\u2026", status: "running" }); } else if (opts.fromVisionHop) { getActivity()?.setContext?.({ hop: "vision hop" }); activityStep("look", { kind: "look", label: "\u0421\u043C\u043E\u0442\u0440\u0438\u0442 \u0438\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435\u2026", status: "running" }); } else if (opts.fromPromptEnRetry) { getActivity()?.setContext?.({ hop: "krea prep" }); activityStep("prep", { kind: "prep", label: "\u0414\u043E\u043F\u0438\u0441\u044B\u0432\u0430\u044E EN-\u043F\u0440\u043E\u043C\u043F\u0442\u2026", status: "running" }); } if (!opts.fromDebug && !opts.fromCompress && !opts.fromAskHop && !opts.fromVisionHop && !opts.fromAutoCritique && !isContinuationTurn(opts)) { try { await maybeAutoCompressBeforeSend(chatEpoch); } catch (e) { console.warn("Assistent auto-compress", e); } if (chatEpoch !== state.chatEpoch) { return; } startBusyUi(state.expectColdLoad ? "loading" : "thinking"); } const context = collectLiveContext(); context.prior_assistant_turns = (state.history || []).filter((m) => m && m.role === "assistant" && !m.systemish).length; context.do_not_greet = context.prior_assistant_turns > 0; context.has_vision_image = visionReadySlots().length > 0; context.images_in_request = !!(images && images.length); context.attached_slot_ids = attachableSlots().map((s) => s.id); context.vision_slot_ids = visionSlots.map((s) => s.id); context.persona = persona; if (chatEpoch !== state.chatEpoch) { return; } let messages = assembleOutgoingMessages( opts.skipAppendUser ? { includePendingUser: text } : void 0 ); if (!messages.length && text) { messages = [{ role: "user", content: text }]; } if (images && messages.length) { messages[messages.length - 1].images = images; } startBusyUi("thinking"); const baseUrl = $("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: $("sa_embed_model")?.value || state.preferredEmbed || "" }; const finishOk = async (reply, civitaiResults, meta = {}) => { if (chatEpoch !== state.chatEpoch) { return; } if (state.turnSettled) { return; } state.turnSettled = true; state.onClosedTerminalFence = null; clearStreamStall(); if (meta.system_chars != null) { state.lastSystemChars = Number(meta.system_chars) || 0; } if (meta.system_layers && typeof meta.system_layers === "object") { state.lastSystemLayers = meta.system_layers; } if (meta.prompt_eval_count != null) { state.lastPromptEvalCount = Number(meta.prompt_eval_count) || null; const mem = getContextMemory(); if (mem.summary) { setContextMemory({ ...mem, promptEvalCount: state.lastPromptEvalCount }, { persist: true }); } } try { state.lastContextChars = context && JSON.stringify(context).length || 0; } catch (e) { state.lastContextChars = 0; } updateCtxChip(); const prose = visibleAssistantProse(reply); setBusyPhase(state.pendingSilentGen ? "silent_gen" : "thinking"); try { await handleReplySideEffects(reply, civitaiResults, { ...opts, userText: text, userWantsGenerate: !!opts.userWantsGenerate || !!state.turnUserWantsGenerate || !isMachineTurn(opts) && state.pendingSilentGen, attachedSlotIds: visionSlots.map((s) => s.id) }); if (meta.system_layers && typeof meta.system_layers === "object") { const layers = Object.keys(meta.system_layers).filter((k) => meta.system_layers[k]); if (layers.length) { activityDone("layers", { kind: "ctx", label: "system_layers", detail: layers.slice(0, 12).join(", ") }); } } if (meta.knowledge?.hops?.length) { const hopLabels = meta.knowledge.hops.map((h) => `${h.tool || "hop"}:${h.count ?? "?"}`).slice(0, 6); activityDone("knowledge", { kind: "ask", label: "knowledge hops", detail: hopLabels.join(" \xB7 ") }); } captureActivityTrace(); state.history.push({ role: "assistant", content: prose, persona, pack, activity: state.pendingActivityTrace || void 0 }); state.pendingActivityTrace = null; persistHistory(); } finally { if (chatEpoch !== state.chatEpoch) { return; } if (!state.generating) { state.busy = false; setInterruptVisible(false); stopBusyUi("\u0413\u043E\u0442\u043E\u0432\u043E"); } else { state.busy = false; setInterruptVisible(true); } updateCtxChip(); } }; const finishErr = (msg) => { if (chatEpoch !== state.chatEpoch) { return; } if (state.turnSettled) { return; } state.turnSettled = true; state.onClosedTerminalFence = null; clearStreamStall(); 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); const settleStreamReply = (reply) => { if (state.turnSettled || chatEpoch !== state.chatEpoch) { return; } const raw = String(reply || state.streamText || "").trim() || (state.streamEl?.querySelector(".sa-msg-body")?.textContent || ""); if (state.streamEl) { finalizeStreamMessage(raw, []); } finishOk(raw, [], {}); }; state.onClosedTerminalFence = (text2) => settleStreamReply(text2); armStreamStall(chatEpoch, (reply) => { settleStreamReply(reply); }); 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 = '\u0423\u0442\u043E\u0447\u043D\u044F\u044E\u2026'; } setBusyPhase("refining"); return; } if (data.delta) { appendStreamDelta(data.delta); } if (data.done || typeof data.reply === "string" && data.reply.length > 0 && !data.delta) { if (state.turnSettled) { return; } const reply = String(state.streamText || data.reply || "").trim() || state.streamEl?.querySelector(".sa-msg-body")?.textContent || ""; const civitai = data.civitai_results || []; state.onClosedTerminalFence = null; if (state.streamEl) { finalizeStreamMessage(reply, civitai); } finishOk(reply, civitai, { system_chars: data.system_chars, system_layers: data.system_layers, prompt_eval_count: data.prompt_eval_count ?? data.raw?.prompt_eval_count, knowledge: data.knowledge }); } }, 0, (err) => { if (chatEpoch !== state.chatEpoch) { return; } console.warn("AssistentChatWS failed, falling back", err); if (state.streamEl) { state.streamEl.remove(); state.streamEl = null; state.streamMeta = null; } genericRequest( "AssistentChat", payload, (data) => { if (chatEpoch !== state.chatEpoch) { return; } if (data.error) { finishErr(String(data.error)); return; } const reply = data.reply || ""; appendMessage("assistant", reply, null, data.civitai_results || [], msgMeta); finishOk(reply, data.civitai_results || [], { system_chars: data.system_chars, system_layers: data.system_layers, prompt_eval_count: data.prompt_eval_count ?? data.raw?.prompt_eval_count, knowledge: data.knowledge }); }, 0, (err2) => finishErr(String(err2 || err || "Chat failed")) ); } ); return; } genericRequest( "AssistentChat", payload, (data) => { if (chatEpoch !== state.chatEpoch) { return; } if (data.error) { finishErr(String(data.error)); return; } const reply = data.reply || ""; appendMessage("assistant", reply, null, data.civitai_results || [], msgMeta); finishOk(reply, data.civitai_results || [], { system_chars: data.system_chars, system_layers: data.system_layers, prompt_eval_count: data.prompt_eval_count ?? data.raw?.prompt_eval_count, knowledge: data.knowledge }); }, 0, (err) => finishErr(String(err || "Chat failed")) ); } function wireDropZone() { const board = $("sa_board"); const layout = $("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") { let row = (state.genResults || []).find((r) => r.id === state.selectedGenResultId); if (!row?.src) { const slot = generateSlot(); if (slot?.src && (slot.id === state.selectedSlotId || !state.selectedGenResultId)) { row = { id: slot.id || GEN_ID, src: slot.src }; } } 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 = $("sa_splitter"); const layout = $("sa_layout"); const pane = $("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 = $("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); } await loadProviderState(); loadConfig(localStorage.getItem(LS_PERSONA) || "neutral", () => { refreshModels(); refreshInventory(() => { renderLoraChips(); }); }); probeOllamaHealth(); } function wire() { if (!$("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(); $("sa_btn_new_chat")?.addEventListener("click", (e) => { e.stopPropagation(); startNewChat({ saveCurrent: true, openDrawer: true }); }); $("sa_btn_new_chat_bar")?.addEventListener("click", (e) => { e.stopPropagation(); startNewChat({ saveCurrent: true, openDrawer: true }); }); $("sa_ctx_chip")?.addEventListener("click", (e) => { e.stopPropagation(); toggleCtxPanel(); }); $("sa_ctx_close")?.addEventListener("click", (e) => { e.stopPropagation(); toggleCtxPanel(false); }); $("sa_ctx_panel")?.addEventListener("click", (e) => e.stopPropagation()); $("sa_ctx_compress")?.addEventListener("click", () => compressNowFromUi()); $("sa_ctx_reset")?.addEventListener("click", () => resetCompressionFromUi()); $("sa_ctx_auto")?.addEventListener("change", () => { COMPRESS_AUTO = !!$("sa_ctx_auto")?.checked; const settingsAuto = $("sa_compress_auto"); if (settingsAuto) { settingsAuto.checked = COMPRESS_AUTO; } if (state.config?.assistant) { state.config.assistant.compress_auto = COMPRESS_AUTO; } setStatus(COMPRESS_AUTO ? "\u0410\u0432\u0442\u043E\u0441\u0436\u0430\u0442\u0438\u0435 \u0432\u043A\u043B\u044E\u0447\u0435\u043D\u043E" : "\u0410\u0432\u0442\u043E\u0441\u0436\u0430\u0442\u0438\u0435 \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u043E"); }); $("sa_compress_auto")?.addEventListener("change", () => { COMPRESS_AUTO = !!$("sa_compress_auto")?.checked; const panelAuto = $("sa_ctx_auto"); if (panelAuto) { panelAuto.checked = COMPRESS_AUTO; } }); $("sa_btn_chats")?.addEventListener("click", (e) => { e.stopPropagation(); setChatsPanelOpen(!state.chatsPanelOpen); }); $("sa_btn_chats_close")?.addEventListener("click", () => setChatsPanelOpen(false)); $("sa_chats_panel")?.addEventListener("click", (e) => e.stopPropagation()); $("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; } switchToChat(id); }); let chatsSearchTimer = null; $("sa_chats_search")?.addEventListener("input", () => { const q = ($("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); }); $("sa_tab_chat")?.addEventListener("click", () => setView("chat")); $("sa_tab_settings")?.addEventListener("click", () => openSettings(state.settingsTab || "behavior")); $("sa_board_tab_gen")?.addEventListener("click", () => setBoardTab("generate")); $("sa_board_tab_refs")?.addEventListener("click", () => setBoardTab("refs")); $("sa_persona")?.addEventListener("change", onPersonaChanged); $("sa_persona_delete")?.addEventListener("click", () => deleteCurrentOverlayPersona()); document.querySelectorAll("#sa_settings .sa-stab").forEach((btn) => { btn.addEventListener("click", () => setSettingsTab(btn.getAttribute("data-stab"))); }); $("sa_btn_mem_refresh")?.addEventListener("click", () => { refreshMemoryList(); }); $("sa_mem_kind")?.addEventListener("change", renderMemoryList); $("sa_mem_scope")?.addEventListener("change", renderMemoryList); $("sa_mem_search")?.addEventListener("input", () => renderMemoryList()); $("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}` }); }); $("sa_btn_mem_clear_persona")?.addEventListener("click", () => { clearCraftMemory({ scope: "personal", persona: $("sa_persona")?.value || "neutral", label: "\u043A\u0440\u0430\u0444\u0442 \u044D\u0442\u043E\u0439 \u043B\u0438\u0447\u043D\u043E\u0441\u0442\u0438" }); }); $("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" }); }); $("sa_btn_mem_clear_all")?.addEventListener("click", () => { clearCraftMemory({ label: "\u0432\u0435\u0441\u044C \u043A\u0440\u0430\u0444\u0442 (non-bundled)" }); }); $("sa_btn_prefs_refresh")?.addEventListener("click", () => refreshUserPrefs()); $("sa_btn_pref_add_global")?.addEventListener("click", () => addUserPref("global")); $("sa_btn_pref_add_persona")?.addEventListener("click", () => addUserPref("persona")); $("sa_btn_prefs_clear_global")?.addEventListener("click", () => clearUserPrefs("global")); $("sa_btn_prefs_clear_persona")?.addEventListener("click", () => clearUserPrefs("persona")); $("sa_btn_prefs_clear_all")?.addEventListener("click", () => clearUserPrefs("all")); $("sa_user_prefs_weight")?.addEventListener("input", () => { const lab = $("sa_user_prefs_weight_val"); if (lab) { lab.textContent = Number($("sa_user_prefs_weight").value).toFixed(1); } }); $("sa_user_prefs_weight")?.addEventListener("change", () => saveKnobs()); $("sa_memory_top_k")?.addEventListener("change", () => saveKnobs()); $("sa_btn_knobs_save")?.addEventListener("click", () => saveKnobs()); $("sa_btn_reset_ui")?.addEventListener("click", () => resetUiState()); $("sa_btn_persona_export")?.addEventListener("click", () => exportSelectedPersona()); $("sa_btn_persona_import")?.addEventListener("click", () => $("sa_persona_import_file")?.click()); $("sa_persona_import_file")?.addEventListener("change", (e) => { const file = e.target?.files?.[0]; if (file) { importPersonaFile(file); } e.target.value = ""; }); $("sa_btn_persona_clone")?.addEventListener("click", () => cloneSelectedPersona()); $("sa_btn_persona_delete_panel")?.addEventListener("click", () => deleteSelectedOverlayPersona()); $("sa_btn_settings_health")?.addEventListener("click", () => { probeOllamaHealth(); setTimeout(syncSettingsHealthLine, 400); }); $("sa_settings_chat_model")?.addEventListener("change", () => { const v = $("sa_settings_chat_model")?.value; if (v && $("sa_model")) { $("sa_model").value = v; saveSettings(); } }); $("sa_btn_look_result")?.addEventListener("click", () => askLookAtResult()); $("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 = $("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(() => $("sa_input")?.focus(), 80); }); $("sa_provider")?.addEventListener("change", () => { setChatProvider($("sa_provider")?.value || "ollama"); }); $("sa_btn_openrouter_key_save")?.addEventListener("click", () => { const key = String($("sa_openrouter_key")?.value || "").trim(); if (!key) { setStatus("\u0412\u0441\u0442\u0430\u0432\u044C \u043A\u043B\u044E\u0447 OpenRouter"); return; } saveOpenRouterKey(key); }); $("sa_openrouter_key")?.addEventListener("keydown", (e) => { if (e.key === "Enter") { e.preventDefault(); $("sa_btn_openrouter_key_save")?.click(); } }); $("sa_btn_openrouter_key_clear")?.addEventListener("click", () => saveOpenRouterKey("")); $("sa_openrouter_filter")?.addEventListener("input", () => renderChatModelOptions()); $("sa_btn_refresh_models")?.addEventListener("click", () => { saveSettings(); refreshModels(); probeOllamaHealth(); }); $("sa_btn_refresh_inventory")?.addEventListener("click", () => refreshInventory(() => { renderLoraChips(); }, { rescan: true })); $("sa_btn_add_ref")?.addEventListener("click", () => { setBoardTab("refs"); addRefSlot({ select: true }); }); $("sa_btn_use_current")?.addEventListener("click", () => snapshotGenerateToRef()); $("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 = $("sa_pack"); if (pack && (pack.value === "ordinary" || pack.value === "write_prompt")) { setPackValue("inpaint_edit", { flash: true }); } }); $("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 }); }); $("sa_btn_clear_init")?.addEventListener("click", () => { if (window.confirm("\u0421\u0431\u0440\u043E\u0441\u0438\u0442\u044C Init \u0438 Mask?")) { clearInitAndMask(); } closeAllMoreMenus(); }); $("sa_btn_clear_image")?.addEventListener("click", () => clearSlot(state.selectedSlotId)); $("sa_btn_board_more")?.addEventListener("click", (e) => { e.stopPropagation(); toggleMoreMenu("sa_board_more_menu", "sa_btn_board_more"); }); $("sa_btn_send")?.addEventListener("click", () => sendChat()); $("sa_btn_build_gen")?.addEventListener("click", () => buildCurrentAndGenerate()); $("sa_btn_interrupt")?.addEventListener("click", () => { doInterruptNow(); }); $("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(); } }); $("sa_btn_clear_more")?.addEventListener("click", (e) => { e.stopPropagation(); toggleMoreMenu("sa_clear_more_menu", "sa_btn_clear_more"); }); $("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(); } }); $("sa_btn_clear_patches")?.addEventListener("click", () => { closeAllMoreMenus(); clearPatchBlocksOnly(); }); document.addEventListener("click", () => { if (state.chatsPanelOpen) { setChatsPanelOpen(false); } if (state.ctxPanelOpen) { toggleCtxPanel(false); } closeAllMoreMenus(); }); $("sa_board_more_menu")?.addEventListener("click", (e) => e.stopPropagation()); $("sa_clear_more_menu")?.addEventListener("click", (e) => e.stopPropagation()); $("sa_base_url")?.addEventListener("change", saveSettings); $("sa_model")?.addEventListener("change", () => { const v = $("sa_model")?.value; if (v && $("sa_settings_chat_model")) { $("sa_settings_chat_model").value = v; } saveSettings(); }); $("sa_embed_model")?.addEventListener("change", () => { state.preferredEmbed = $("sa_embed_model")?.value || ""; saveSettings(); }); $("sa_pack")?.addEventListener("change", () => { state.packUserTouched = true; saveSettings(); syncModeBadge(); }); $("sa_chips")?.addEventListener("click", async (e) => { const btn = e.target.closest(".sa-chip"); if (!btn) { return; } 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; } 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"); const sampler = btn.getAttribute("data-sampler"); const scheduler = btn.getAttribute("data-scheduler"); if (aspect) { await applyQuickPatch({ aspect }, `Aspect ${aspect}`); } else if (seed === "lock") { await applyQuickPatch({ lock_seed: true }, "Seed locked"); } else if (seed === "random") { await applyQuickPatch({ seed: -1 }, "Seed random"); } else if (vary) { await applyQuickPatch({ vary: true, seed: -1 }, "Vary", { generate: true }); } 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 }, "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 }, "RAW"); } else if (sampler) { await applyQuickPatch({ sampler }, `Sampler ${sampler}`); } else if (scheduler) { await applyQuickPatch({ scheduler }, `Scheduler ${scheduler}`); } renderLoraChips(); }); $("sa_auto_vision")?.addEventListener("change", () => { saveSettings(); const gen = generateSlot(); if (gen) { gen.attach = wantsAutoVision(); renderBoard(); } }); $("sa_auto_apply")?.addEventListener("change", saveSettings); $("sa_auto_generate")?.addEventListener("change", saveSettings); $("sa_auto_critique")?.addEventListener("change", saveSettings); $("sa_auto_download")?.addEventListener("change", saveSettings); $("sa_park_llm")?.addEventListener("change", saveSettings); syncChipHighlight(); setInterval(syncChipHighlight, 2500); setInterval(renderLoraChips, 4e3); syncLiveParamsBar(); setInterval(syncLiveParamsBar, 1200); syncModeBadge(); syncBuildGenButton(); setInterval(updateGate, 2e3); setInterval(syncGenerateSlot, 700); setInterval(() => { if (!state.busy && !state.generating) { probeOllamaHealth(); } }, 45e3); setInterval(() => { if (!state.busy && !state.generating) { } }, 12e4); window.addEventListener("beforeunload", () => { try { saveActiveChatToStore({ dropEmpty: true }); const chat = findChat(state.activeChatId); if (chat && (chat.messages || []).length) { diskPersist()?.saveChat(chat, { immediate: true }); } diskPersist()?.saveUiState(collectUiState(), { immediate: true }); } catch (e) { } }); setInterval(() => { if (!state.busy) { const tabOn = !!document.getElementById(TAB_BUTTON_ID)?.classList.contains("tab-button-selected") || !!document.getElementById("swarm_assistent_root")?.offsetParent; refreshInventory(null, { rescan: inventoryIsStale(tabOn ? 45e3 : 12e4) }); } }, 3e4); window.SA = window.SA || {}; window.SA.app = { getState: () => state, setTrainingLock(on) { state.trainingLock = !!on; }, refreshModels: () => refreshModels(), setStatus: (msg) => setStatus(msg) }; window.swarmAssistent = { setImageFromSrc, putImageOnBoard, clearVisionImage, snapshotGenerateToRef, openAssistentTab, sendToAssistent: (src) => { putImageOnBoard(src, { switchTab: true, note: "\u0418\u0437\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E \u0432 Assistent", preferSelected: false }); setBoardTab("refs"); }, isKreaSelected, resolveCurrentCheckpoint, refreshInventory, applyPatch, triggerGenerate, setInitFromSrc, setMaskFromSrc, clearInitAndMask, slotById, renderBoard, setBoardTab }; } function wireSlashInput() { const input = $("sa_input"); if (!input || input.dataset.saSlashWired) { return; } input.dataset.saSlashWired = "1"; input.addEventListener("input", () => updateSlashMenuFromInput()); input.addEventListener("keydown", (e) => { const menu = $("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/main.js window.SA = window.SA || {}; window.SA.userAsksGenerate = userAsksGenerate; window.SA.userAsksNoGenerate = userAsksNoGenerate; window.SA.resolveTurnIntent = resolveTurnIntent; attachApi(window.SA); attachPatch(window.SA); attachPersist(window.SA); attachSession(window.SA); attachContext(window.SA); attachActivity(window.SA); attachKreaProfile(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; } }; })();