diff --git a/Assets/assistent.bundle.js b/Assets/assistent.bundle.js index 7af94e4..b4b9cfe 100644 --- a/Assets/assistent.bundle.js +++ b/Assets/assistent.bundle.js @@ -1,4 +1,59 @@ (() => { + // 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 (/\b(generat(e|ion)|draw|render|make\s+(an?\s+)?image|run\s+generate)\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\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 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) && (modelAsked || hasPrompt); + 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) { @@ -86,7 +141,7 @@ function has(obj, key) { return obj[key] !== void 0 && obj[key] !== null; } - function generateFlagOn(obj) { + function generateFlagOn2(obj) { if (!obj || typeof obj !== "object") { return false; } @@ -111,7 +166,7 @@ return patch; } const acts = Array.isArray(patch.actions) ? patch.actions.map(String) : []; - if (generateFlagOn(patch) || acts.includes("generate")) { + if (generateFlagOn2(patch) || acts.includes("generate")) { patch.generate = true; } if (typeof patch.ask === "string") { @@ -181,11 +236,18 @@ } return { prose: text, patch: null }; } + function visibleProse(text) { + const { prose, patch } = extractPatch(text); + if (patch) { + return prose || ""; + } + return String(text || ""); + } function isTerminalStreamPatch(obj) { if (!obj || typeof obj !== "object") { return false; } - if (generateFlagOn(obj)) { + if (generateFlagOn2(obj)) { return true; } if (Array.isArray(obj.ask) && obj.ask.length) { @@ -215,7 +277,8 @@ SA2.isTerminalStreamPatch = isTerminalStreamPatch; SA2.normalizePatch = normalizePatch; SA2.extractPatch = extractPatch; - SA2.generateFlagOn = generateFlagOn; + SA2.generateFlagOn = generateFlagOn2; + SA2.visibleProse = visibleProse; } // src/persist.js @@ -678,16 +741,23 @@ session_exact: extras.sessionExact || null }; } - function resolveTurnIntent(patch, userText, { vetoFn, askGenerateFn, fromAutoCritique } = {}) { + 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) && !!(modelAsked || String(delta.prompt || "").trim()); - const generate = !vetoed && !fromAutoCritique && (modelAsked || userAsked); + const hasPrompt = !!(String(delta.prompt || "").trim() || String(sessionPrompt || "").trim()); + const userAsked = typeof askGenerateFn === "function" && !!askGenerateFn(userText) && (modelAsked || hasPrompt); + 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 }; + return { generate, look, vetoed, ask, modelAsked }; } var EXACT_GENERATE_PARAM_KEYS = ["steps", "cfg", "sigma_shift"]; function resolveExactProfileDefaults({ exact, profiles, profileName } = {}) { @@ -749,7 +819,7 @@ toPersistParams, compactContext, fullSettingsDump, - resolveTurnIntent, + resolveTurnIntent: resolveTurnIntent2, resolveExactProfileDefaults, mergeExactParamsForGenerate, EXACT_GENERATE_PARAM_KEYS, @@ -1218,6 +1288,65 @@ }; } + // 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"; @@ -1250,16 +1379,7 @@ let COMPRESS_AT = 0.7; let CHARS_PER_TOKEN = 3.2; let COMPRESS_AUTO = true; - let ASPECT_TABLE = { - "1:1": [1024, 1024], - "4:3": [1184, 896], - "3:2": [1248, 832], - "16:9": [1376, 768], - "2.35:1": [1568, 672], - "4:5": [928, 1152], - "2:3": [832, 1248], - "9:16": [768, 1376] - }; + let ASPECT_TABLE = { ...DEFAULT_ASPECT_TABLE }; let PACK_ALIASES = { ordinary: "ordinary", combine: "ordinary", @@ -1337,6 +1457,8 @@ 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 } }, @@ -1847,6 +1969,7 @@ let text = String(raw || "").replace(/\r\n/g, "\n"); text = text.replace(/(?:^|\n)#{1,6}\s*JSON\s*Patch\s*(?=\n|$)/gi, "\n"); text = text.replace(/(?:^|\n)\s*JSON\s*Patch\s*:?\s*(?=\n|$)/gi, "\n"); + text = text.replace(/```(?:json)?\s*[\s\S]*?```/gi, ""); text = text.replace(/\n{3,}/g, "\n\n").trim(); if (!text) { return ""; @@ -1927,10 +2050,39 @@ if (!el) { return; } - el.value = value; + 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(); } @@ -1976,7 +2128,7 @@ } return false; } - function cyrTokenRe(alts) { + 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"); @@ -2004,10 +2156,10 @@ if (named) { return normalizeAspect(named[1]); } - if (cyrTokenRe("\u043F\u043E\u0440\u0442\u0440\u0435\u0442|\u0432\u0435\u0440\u0442\u0438\u043A\u0430\u043B[\u0430-\u044F\u0451]*").test(t) || /\b(portrait|vertical)\b/i.test(t)) { + 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 (cyrTokenRe("\u0430\u043B\u044C\u0431\u043E\u043C|\u0433\u043E\u0440\u0438\u0437\u043E\u043D\u0442\u0430\u043B[\u0430-\u044F\u0451]*").test(t) || /\b(landscape|horizontal|widescreen)\b/i.test(t)) { + 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; @@ -2024,7 +2176,7 @@ if (/\b(horny|controls?|preference[_\s-]?bias)\b/i.test(t)) { return true; } - return cyrTokenRe("\u0445\u043E\u0440\u043D\u0438|\u043E\u0441\u0442\u044B\u043D\u044C|\u0441\u043B\u0430\u0439\u0434\u0435\u0440").test(t) || /\/\s*(остынь|ostyn|horny-game)/i.test(t) || /слайдер\s*вкус|вкус\s*(на|в)\s*\d|поставь\s*вкус|крутани\s*вкус/i.test(t); + 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") { @@ -2072,7 +2224,7 @@ if (/\b(steps?|cfg|seed|sigma|aspect|resolution|batch|turbo|raw)\b/i.test(t)) { return true; } - return cyrTokenRe( + 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); } @@ -2094,7 +2246,7 @@ if (/^(давай\s+дальше|продолжай|продолжим|go\s+on|continue|keep\s+going|next(\s+one)?|next\s+frame)([!.…\s]|$)/i.test(t)) { return true; } - return cyrTokenRe( + 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); } @@ -2212,7 +2364,7 @@ ${patch.prompt}`; aspect: effective.aspect || base.aspect }; } - function userAsksNoGenerate(text) { + function userAsksNoGenerate2(text) { const t = String(text || "").trim(); if (!t) { return false; @@ -2220,7 +2372,7 @@ ${patch.prompt}`; 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( + 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); } @@ -2232,50 +2384,53 @@ ${patch.prompt}`; if (/\b(look\s+at|critique|criticize|describe\s+(this|the|ref|image)|what\s+do\s+you\s+see)\b/i.test(t)) { return true; } - if (cyrTokenRe("\u043A\u0440\u0438\u0442\u0438\u043A[\u0430-\u044F\u0451]*|\u0447\u0442\u043E\\s+\u043D\u0435\\s+\u0442\u0430\u043A|\u0440\u0430\u0437\u0431\u0435\u0440\u0438").test(t)) { + 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 (cyrTokenRe("\u043E\u043F\u0438\u0448\u0438\\s+(\u044D\u0442\u043E|\u044D\u0442\u0443|\u0440\u0435\u0444|\u0438\u0437\u043E\u0431\u0440\u0430\u0436[\u0430-\u044F\u0451]*|\u043A\u0430\u0440\u0442\u0438\u043D\u043A[\u0430-\u044F\u0451]*|\u043A\u0430\u0434\u0440|\u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442|\u0440\u0435\u0444\u0435\u0440\u0435\u043D\u0441)").test(t)) { + 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 userAsksGenerate(text) { + function userAsksGenerate2(text) { if (window.SA && typeof SA.userAsksGenerate === "function") { return SA.userAsksGenerate(text); } const t = String(text || "").trim(); - if (!t || userAsksNoGenerate(t)) { + 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 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|\u0437\u0430\u043F\u0443\u0441\u0442\u0438\\s+\u0433\u0435\u043D\u0435\u0440[\u0430-\u044F\u0451]*|\u0441\u0434\u0435\u043B\u0430\u0439\\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]*)" + 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 resolveTurnIntent2(patch, userText, opts = {}) { + function resolveTurnIntent3(patch, userText, opts = {}) { const S = window.SA && window.SA.session; if (S && typeof S.resolveTurnIntent === "function") { return S.resolveTurnIntent(patch, userText, { - vetoFn: userAsksNoGenerate, - askGenerateFn: userAsksGenerate, - fromAutoCritique: !!opts.fromAutoCritique + vetoFn: userAsksNoGenerate2, + askGenerateFn: userAsksGenerate2, + fromAutoCritique: !!opts.fromAutoCritique, + sessionPrompt: opts.sessionPrompt || "", + userWantsGenerate: !!opts.userWantsGenerate }); } - const vetoed = !isMachineTurn(opts) && userAsksNoGenerate(userText); + 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) && userAsksGenerate(userText) && !!(modelAsked || String(patch?.prompt || "").trim()); - const generate = !vetoed && !opts.fromAutoCritique && (modelAsked || userAsked); + const hasPrompt = !!(String(patch?.prompt || "").trim() || String(opts.sessionPrompt || "").trim()); + const userAsked = !isMachineTurn(opts) && userAsksGenerate2(userText) && (modelAsked || hasPrompt); + 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 }; + return { generate, look, vetoed, ask, modelAsked }; } function stripGenerateAction(patch) { if (!patch || typeof patch !== "object") { @@ -2354,31 +2509,53 @@ ${patch.prompt}`; badge.title = `\u0420\u0435\u0436\u0438\u043C: ${pack}`; badge.classList.toggle("sa-mode-hot", pack === "critique_image" || pack === "inpaint_edit"); } - function syncLiveParamsBar() { - const el = $2("sa_live_params"); - if (!el) { - return; - } + function formatLiveParamsLine() { const w = parseInt(val("input_width") || "0", 10) || null; const h = parseInt(val("input_height") || "0", 10) || null; const aspect = guessAspectFromSize(w, h) || "\u2014"; const steps = val("input_steps") || "\u2014"; const cfg = val("input_cfgscale") || val("input_cfg") || "\u2014"; + const sigma = val("input_sigmashift") || ""; const seed = val("input_seed") || "\u2014"; const profile = detectKreaProfileName2(); - el.textContent = `${aspect} \xB7 ${w || "?"}\xD7${h || "?"} \xB7 steps ${steps} \xB7 cfg ${cfg} \xB7 ${profile} \xB7 seed ${seed}`; + const batch = val("input_images") || val("input_batchsize") || ""; + const sampler = val("input_sampler") || ""; + const scheduler = val("input_scheduler") || ""; + const parts = [ + aspect, + `${w || "?"}\xD7${h || "?"}`, + `steps ${steps}`, + `cfg ${cfg}` + ]; + if (sigma) { + parts.push(`\u03C3 ${sigma}`); + } + parts.push(profile, `seed ${seed}`); + if (batch && batch !== "1") { + parts.push(`\xD7${batch}`); + } + if (sampler) { + parts.push(sampler); + } + if (scheduler) { + parts.push(scheduler); + } + return parts.join(" \xB7 "); + } + function syncLiveParamsBar() { + const line = formatLiveParamsLine(); + const boardEl = $2("sa_live_params"); + const composerEl = $2("sa_composer_params"); + if (boardEl) { + boardEl.textContent = line; + } + if (composerEl) { + composerEl.textContent = line; + } } function applyAspectTableFrom(obj) { - if (!obj || typeof obj !== "object") { - return false; - } - const next = {}; - for (const [k, v] of Object.entries(obj)) { - if (Array.isArray(v) && v.length >= 2) { - next[k] = [Number(v[0]), Number(v[1])]; - } - } - if (!Object.keys(next).length) { + const next = applyAspectTableFromObject(obj); + if (!next) { return false; } ASPECT_TABLE = next; @@ -3436,7 +3613,7 @@ ${patch.prompt}`; actions.appendChild(toSession); const genBtn = document.createElement("button"); genBtn.type = "button"; - genBtn.className = "basic-button sa-btn-gen"; + 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()) { @@ -3449,6 +3626,9 @@ ${patch.prompt}`; 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); @@ -3582,6 +3762,22 @@ ${patch.prompt}`; }); 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 = $2("sa_gen_lightbox"); if (root) { @@ -3646,7 +3842,7 @@ ${patch.prompt}`; return root; } function currentLightboxRow() { - const list = (state.genResults || []).filter((r) => r.src); + const list = genLightboxList(); if (!list.length || state.lightboxIndex < 0) { return null; } @@ -3654,7 +3850,7 @@ ${patch.prompt}`; } function syncGenLightbox() { const root = ensureGenLightbox(); - const list = (state.genResults || []).filter((r) => r.src); + const list = genLightboxList(); const row = list[state.lightboxIndex]; if (!row) { root.hidden = true; @@ -3676,7 +3872,7 @@ ${patch.prompt}`; } } function openGenLightbox(id) { - const list = (state.genResults || []).filter((r) => r.src); + const list = genLightboxList(); let idx = list.findIndex((r) => r.id === id); if (idx < 0) { idx = 0; @@ -3697,7 +3893,7 @@ ${patch.prompt}`; } } function stepGenLightbox(delta) { - const list = (state.genResults || []).filter((r) => r.src); + const list = genLightboxList(); if (list.length < 2) { return; } @@ -3817,10 +4013,22 @@ ${patch.prompt}`; busy.hidden = !(slot.type === "generate" && (state.generating || state.busyPhase === "generating")); busy.innerHTML = ''; el.appendChild(busy); - el.addEventListener("click", () => { + 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(); @@ -5086,6 +5294,13 @@ ${patch.prompt}`; } 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; @@ -5207,22 +5422,22 @@ ${patch.prompt}`; if (userTextMentionsParams(t) || parseAspectFromUserText(t)) { return cur === "critique_image" || cur === "describe_ref" ? "ordinary" : "fix_params"; } - if (cyrTokenRe("\u043F\u043E\u043F\u0440\u0430\u0432\u044C|\u0438\u0441\u043F\u0440\u0430\u0432\u044C|\u043F\u0435\u0440\u0435\u043F\u0438\u0448\u0438|\u0443\u043B\u0443\u0447\u0448\u0438").test(t) || /\b(fix\s+it|make\s+it\s+better|rewrite)\b/i.test(t)) { + 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 (cyrTokenRe("\u043E\u043F\u0438\u0448\u0438\\s+(\u0440\u0435\u0444|\u0438\u0437\u043E\u0431\u0440\u0430\u0436[\u0430-\u044F\u0451]*|\u044D\u0442\u043E\u0442|\u044D\u0442\u0443|\u043A\u0430\u0440\u0442\u0438\u043D\u043A[\u0430-\u044F\u0451]*|\u0440\u0435\u0444\u0435\u0440\u0435\u043D\u0441)").test(t) || /\b(prompt\s+from\s+image|describe\s+(this|the|ref|image)|reverse\s*prompt)\b/i.test(t) || /опиши\s+(этот|эту|картинк|референс)/i.test(t)) { + 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) || cyrTokenRe("\u043A\u0440\u0438\u0442\u0438\u043A[\u0430-\u044F\u0451]*|\u0447\u0442\u043E\\s+\u043D\u0435\\s+\u0442\u0430\u043A|\u0440\u0430\u0437\u0431\u0435\u0440\u0438").test(t) || /(?:^|[^а-яёa-z0-9_])(посмотри|смотри)\s+(на\s+)?(результат|картинк[а-яё]*|изображен[а-яё]*|кадр|ген)/i.test(t)) { + 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) || cyrTokenRe("\u0437\u0430\u043C\u0430\u0436\u044C|\u0437\u0430\u043A\u0440\u0430\u0441\u044C|\u0440\u0443\u043A\u0438|\u043B\u0438\u0446\u043E|\u043C\u0430\u0441\u043A[\u0430-\u044F\u0451]*").test(t) || /init\s*image/i.test(t)) { + 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) || cyrTokenRe("\u0441\u0446\u0435\u043D[\u0430-\u044F\u0451]*|\u0430\u0442\u043C\u043E\u0441\u0444\u0435\u0440[\u0430-\u044F\u0451]*|\u043C\u0438\u0437\u0430\u043D\u0441\u0446\u0435\u043D[\u0430-\u044F\u0451]*").test(t)) { + 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"; @@ -5700,7 +5915,7 @@ ${patch.prompt}`; } const persona = $2("sa_persona")?.value || localStorage.getItem(LS_PERSONA) || "neutral"; const pack = $2("sa_pack")?.value || defaultPackId(); - const prose = typeof extractPatch2 === "function" ? extractPatch2(text).prose || text : text; + const prose = visibleAssistantProse(text); if (state.streamEl) { finalizeStreamMessage(text, []); } else { @@ -6227,7 +6442,7 @@ ${patch.prompt}`; const { prose, patch: extracted } = role === "assistant" ? extractPatch2(text) : { prose: text, patch: null }; const finalPatch = patch || extracted; if (role === "assistant") { - setAssistantBody(div, prose || text || ""); + setAssistantBody(div, finalPatch ? prose || "" : prose || text || ""); } else { div.textContent = prose || text || ""; } @@ -6330,7 +6545,7 @@ ${patch.prompt}`; if (streamHasClosedPatchFence(state.streamText)) { state.streamText = trimToClosedPatchFence(state.streamText); state.streamFenceDone = true; - setAssistantBody(state.streamEl, state.streamText, { live: true }); + setAssistantBody(state.streamEl, visibleAssistantProse(state.streamText), { live: true }); scrollMessagesToBottom(); const fn = state.onClosedTerminalFence; state.onClosedTerminalFence = null; @@ -6364,7 +6579,7 @@ ${patch.prompt}`; el.classList.remove("sa-streaming", "sa-typing"); mountAssistantMeta(el, meta || void 0); const { prose, patch } = extractPatch2(fullReply); - setAssistantBody(el, prose || 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; @@ -8437,8 +8652,32 @@ ${summary || "(\u043F\u0443\u0441\u0442\u043E)"}`; 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 (effective && S) { + 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", @@ -8453,28 +8692,6 @@ ${summary || "(\u043F\u0443\u0441\u0442\u043E)"}`; } } catch (e) { } - if (typeof rememberLastPatch === "function") rememberLastPatch(effective); - } - if (Array.isArray(effective?.actions) && effective.actions.map(String).includes("interrupt")) { - if (typeof doInterruptNow === "function") doInterruptNow(); - } - let intent = resolveTurnIntent2(effective, opts.userText || "", opts); - if (opts.userWantsGenerate && effective && !intent.vetoed && !fromAutoCritique) { - intent = { ...intent, generate: true }; - } - if (effective) { - if (intent.generate) { - const acts = Array.isArray(effective.actions) ? effective.actions.map(String) : []; - effective = { ...effective, actions: acts.includes("generate") ? acts : acts.concat("generate"), generate: true }; - } else if (typeof stripGenerateAction === "function") { - effective = stripGenerateAction(effective); - if (effective && effective.generate) { - effective = { ...effective }; - delete effective.generate; - } - } - if (!intent.look && typeof stripLookAt === "function") effective = stripLookAt(effective); - if (typeof rememberLastPatch === "function") rememberLastPatch(effective); } if (intent.vetoed) { state.pendingSilentGen = false; @@ -8583,44 +8800,83 @@ ${summary || "(\u043F\u0443\u0441\u0442\u043E)"}`; if (typeof maybeAutoVisionLook === "function") await maybeAutoVisionLook(srcOut); } } else if (effective) { - if (S) state.chatSession = S.mergeDelta(state.chatSession || S.emptySession(), effective); - if (promptChanged || $2("sa_auto_apply")?.checked) { - await pushSessionToSwarm(state.chatSession); - if (typeof syncLiveParamsBar === "function") syncLiveParamsBar(); - if (promptChanged && typeof appendSystemNote === "function") { - appendSystemNote("\u041F\u0440\u043E\u043C\u043F\u0442 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D"); - } - if (promptChanged) { - setStatus("\u041F\u0440\u043E\u043C\u043F\u0442 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D"); - } - } if (!state.generating && typeof stopBusyUi === "function") { - stopBusyUi(intent.vetoed ? "\u0417\u0430\u043F\u043E\u043C\u043D\u0438\u043B \xB7 \u0431\u0435\u0437 Generate" : promptChanged ? "\u041F\u0440\u043E\u043C\u043F\u0442 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D" : ""); + 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: $2("sa_persona")?.value || "", + pack: $2("sa_pack")?.value || "" + }); } - async function applyQuickPatch(patch, note) { + 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 }; - if (!Array.isArray(withActions.actions) && patchHasGenTrigger(withActions)) { - withActions.actions = ["generate"]; + 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 (S && S.patchWantsGenerate && S.patchWantsGenerate(withActions)) { + if (wantGenerate && S && S.patchWantsGenerate && S.patchWantsGenerate(withActions)) { withActions = ensureExactParamsForGenerate(withActions); } if (S) { state.chatSession = S.mergeDelta(state.chatSession || S.emptySession(), withActions); } - await pushSessionToSwarm(state.chatSession); + 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); + } state.lastUserParamIntent = prevIntent; - setStatus(note || "Applied"); - if (patchHasGenTrigger(withActions)) { + 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 = $2("sa_chips"); @@ -8881,7 +9137,7 @@ ${summary || "(\u043F\u0443\u0441\u0442\u043E)"}`; setStatus(`Unknown aspect. Try: ${Object.keys(ASPECT_TABLE).join(", ")}`); return true; } - await applyQuickPatch({ aspect: key, actions: ["generate"] }, `Aspect ${key}`); + await applyQuickPatch({ aspect: key }, `Aspect ${key}`); return true; } if (cmd === "seed") { @@ -8889,12 +9145,12 @@ ${summary || "(\u043F\u0443\u0441\u0442\u043E)"}`; if (mode === "lock" || mode === "keep") { await applyQuickPatch({ lock_seed: true }, "Seed locked"); } else { - await applyQuickPatch({ seed: -1, vary: true, actions: ["generate"] }, "Seed random"); + await applyQuickPatch({ seed: -1, vary: true }, "Seed random"); } return true; } if (cmd === "vary") { - await applyQuickPatch({ vary: true, seed: -1, actions: ["generate"] }, "Vary (new seed)"); + await applyQuickPatch({ vary: true, seed: -1 }, "Vary (new seed)", { generate: true }); return true; } if (cmd === "inventory" || cmd === "inv") { @@ -9009,6 +9265,7 @@ ${HELP_TEXT}`); state.lastUserParamIntent = userTextMentionsParams(text); state.lastUserControlIntent = userTextMentionsControls(text); state.pendingSilentGen = false; + state.turnUserWantsGenerate = userAsksGenerate2(text); } if (!isMachineTurn(opts) && !opts.skipSlash) { if (rawInput.startsWith("/")) { @@ -9039,7 +9296,7 @@ ${HELP_TEXT}`); 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}`); + await applyQuickPatch(patch, `Aspect ${aspect}`, { generate: true }); return; } } @@ -9228,7 +9485,7 @@ ${HELP_TEXT}`); state.lastContextChars = 0; } updateCtxChip(); - const prose = extractPatch2(reply).prose || reply; + const prose = visibleAssistantProse(reply); state.history.push({ role: "assistant", content: prose, persona, pack }); persistHistory(); setBusyPhase(state.pendingSilentGen ? "silent_gen" : "thinking"); @@ -9236,7 +9493,7 @@ ${HELP_TEXT}`); await handleReplySideEffects(reply, civitaiResults, { ...opts, userText: text, - userWantsGenerate: !!opts.userWantsGenerate || !isMachineTurn(opts) && state.pendingSilentGen, + userWantsGenerate: !!opts.userWantsGenerate || !!state.turnUserWantsGenerate || !isMachineTurn(opts) && state.pendingSilentGen, attachedSlotIds: visionSlots.map((s) => s.id) }); } finally { @@ -9425,8 +9682,14 @@ ${HELP_TEXT}`); closeGenLightbox(); return; } - if ((e.key === "Enter" || e.key === " ") && state.boardTab === "generate" && state.selectedGenResultId) { - const row = (state.genResults || []).find((r) => r.id === state.selectedGenResultId); + 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); @@ -9582,6 +9845,7 @@ ${HELP_TEXT}`); window.__swarmAssistentWired = true; loadSettings(); setView(state.view || "chat"); + void window.SA?.training?.resumePolling?.(); updateGate(); ensureBoard(); setBoardTab(state.boardTab || "generate", { persist: false }); @@ -9901,19 +10165,27 @@ ${HELP_TEXT}`); const vary = btn.getAttribute("data-vary"); const profile = btn.getAttribute("data-krea-profile"); if (aspect) { - await applyQuickPatch({ aspect, actions: ["generate"] }, `Aspect ${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, actions: ["generate"] }, "Seed random"); + await applyQuickPatch({ seed: -1 }, "Seed random"); } else if (vary) { - await applyQuickPatch({ vary: true, seed: -1, actions: ["generate"] }, "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, actions: ["generate"] }, "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, actions: ["generate"] }, "RAW"); + await applyQuickPatch({ + steps: p.steps ?? 28, + cfg: p.cfg ?? 4.5, + sigma_shift: p.sigma_shift + }, "RAW"); } renderLoraChips(); }); @@ -10139,6 +10411,7 @@ ${HELP_TEXT}`); if (state.ttab === "train") { syncModelfileModels(); syncQloraModels(); + void resumeTrainJobPolling(); } if (state.ttab === "models") refreshTrainModels(); } @@ -10330,17 +10603,47 @@ ${HELP_TEXT}`); async function importHf() { if (!state.hfSelected && !state.hfCheck?.id) { setTrainStatus("\u0421\u043D\u0430\u0447\u0430\u043B\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u043D\u0430\u0431\u043E\u0440"); + const hfSt2 = $("sa_hf_status"); + if (hfSt2) hfSt2.textContent = "\u0421\u043D\u0430\u0447\u0430\u043B\u0430 \u043D\u0430\u0436\u043C\u0438 \xAB\u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C\xBB"; return; } const id = state.hfSelected || state.hfCheck.id; const limit = Number($("sa_hf_import_limit")?.value) || 200; const mapping = buildHfMappingPayload(); + const btn = $("sa_btn_hf_import"); + const hfSt = $("sa_hf_status"); + const busy = "\u0418\u043C\u043F\u043E\u0440\u0442\u0438\u0440\u0443\u044E\u2026"; + setTrainStatus(busy); + if (hfSt) hfSt.textContent = busy; + if (btn) { + btn.disabled = true; + btn.dataset.label = btn.textContent; + btn.textContent = "\u2026"; + } try { const data = await SA2.request("AssistentImportHfDataset", { dataset: id, limit, mapping }); - setTrainStatus(`\u0418\u043C\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u043E: ${data.imported}${data.runner_only ? " (runner-only)" : ""}`); + const n = Number(data?.imported) || 0; + let msg; + if (data?.runner_only) { + msg = `Runner-only: ${data.note || id} (\u0432 sqlite \u043D\u0435 \u0438\u043C\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043B\u0438)`; + } else if (n > 0) { + msg = `\u0418\u043C\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u043E: ${n} \u2014 \u0447\u0435\u0440\u043D\u043E\u0432\u0438\u043A\u0438 \u0432 \u0441\u043F\u0438\u0441\u043A\u0435 \u043D\u0438\u0436\u0435`; + } else { + msg = "\u0418\u043C\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u043E: 0 \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044C HF token, \u043C\u0430\u043F\u043F\u0438\u043D\u0433 \u043A\u043E\u043B\u043E\u043D\u043E\u043A \u0438\u043B\u0438 \u043B\u0438\u043C\u0438\u0442 \u0441\u0442\u0440\u043E\u043A"; + } + setTrainStatus(msg); + if (hfSt) hfSt.textContent = msg; await refreshSamples(); + $("sa_train_samples")?.scrollIntoView({ behavior: "smooth", block: "nearest" }); } catch (e) { - setTrainStatus(String(e.message || e)); + const err = String(e.message || e); + setTrainStatus(err); + if (hfSt) hfSt.textContent = err; + } finally { + if (btn) { + btn.disabled = false; + btn.textContent = btn.dataset.label || "\u0418\u043C\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C"; + } } } async function syncModelfileModels() { @@ -10370,7 +10673,9 @@ ${HELP_TEXT}`); } } async function createModelfile() { - setTrainStatus("\u0421\u043E\u0437\u0434\u0430\u044E \u043C\u043E\u0434\u0435\u043B\u044C\u2026"); + const btn = $("sa_btn_modelfile_create"); + btn?.setAttribute("disabled", "disabled"); + setTrainStatus("\u0421\u043E\u0437\u0434\u0430\u044E Modelfile \u0432 Ollama\u2026"); try { const data = await SA2.request("AssistentCreateOllamaModel", { base_url: $("sa_base_url")?.value, @@ -10386,11 +10691,15 @@ ${HELP_TEXT}`); SA2.app?.refreshModels?.(); } catch (e) { setTrainStatus(String(e.message || e)); + } finally { + btn?.removeAttribute("disabled"); } } function setTrainMode(mode) { $("sa_train_form_modelfile").hidden = mode !== "modelfile"; $("sa_train_form_qlora").hidden = mode !== "qlora"; + const radio = document.querySelector(`input[name="sa_train_mode"][value="${mode}"]`); + if (radio) radio.checked = true; } function setTrainingLock(on, text) { const root = $("swarm_assistent_root"); @@ -10409,15 +10718,20 @@ ${HELP_TEXT}`); const prog = data?.progress || (data?.job?.progress_json ? JSON.parse(data.job.progress_json) : null); const active = data?.training_active || data?.job?.status === "running"; const status = data?.job?.status || prog?.status; - setTrainingLock(active, prog?.status === "running" ? `\u0422\u0440\u0435\u043D\u0438\u0440\u043E\u0432\u043A\u0430 \xB7 ${prog?.percent ?? 0}%` : "\u0418\u0434\u0451\u0442 \u0442\u0440\u0435\u043D\u0438\u0440\u043E\u0432\u043A\u0430\u2026"); + const pct = prog?.percent; + const bannerText = active && pct != null ? `QLoRA \xB7 ${pct}%` : active ? "\u0418\u0434\u0451\u0442 QLoRA\u2026" : "\u0418\u0434\u0451\u0442 \u0442\u0440\u0435\u043D\u0438\u0440\u043E\u0432\u043A\u0430\u2026"; + setTrainingLock(active, bannerText); const logEl = $("sa_train_log"); const bar = $("sa_train_progress_fill"); const box = $("sa_train_progress"); if (prog) { if (box) box.hidden = false; - if (bar && prog.percent != null) bar.style.width = `${prog.percent}%`; + if (bar && pct != null) bar.style.width = `${pct}%`; if (logEl && prog.log) logEl.textContent = prog.log; } + if (active) { + setTrainStatus(pct != null ? `QLoRA \xB7 ${pct}% \u2014 \u043F\u043E\u043B\u043D\u044B\u0439 \u043B\u043E\u0433 \u043D\u0430 \u0432\u043A\u043B\u0430\u0434\u043A\u0435 \xAB\u0422\u0440\u0435\u043D\u0438\u0440\u043E\u0432\u043A\u0430\xBB` : "QLoRA \u0437\u0430\u043F\u0443\u0449\u0435\u043D\u0430 \u2014 \u043F\u043E\u043B\u043D\u044B\u0439 \u043B\u043E\u0433 \u043D\u0430 \u0432\u043A\u043B\u0430\u0434\u043A\u0435 \xAB\u0422\u0440\u0435\u043D\u0438\u0440\u043E\u0432\u043A\u0430\xBB"); + } if (!active) { clearInterval(state.polling); state.polling = null; @@ -10444,6 +10758,19 @@ ${HELP_TEXT}`); } catch (e) { } } + async function resumeTrainJobPolling() { + try { + const data = await SA2.request("AssistentGetTrainJob", {}); + const active = data?.training_active || data?.job?.status === "running"; + await pollTrainJob(); + if (!active) return; + setTrainMode("qlora"); + $("sa_btn_qlora_cancel").hidden = false; + if (state.polling) clearInterval(state.polling); + state.polling = setInterval(pollTrainJob, 1500); + } catch (e) { + } + } async function startQlora() { setTrainStatus("\u0417\u0430\u043F\u0443\u0441\u043A\u2026"); try { @@ -10470,7 +10797,9 @@ ${HELP_TEXT}`); if (state.polling) clearInterval(state.polling); state.polling = setInterval(pollTrainJob, 1500); pollTrainJob(); - setTrainStatus("\u0422\u0440\u0435\u043D\u0438\u0440\u043E\u0432\u043A\u0430 \u0437\u0430\u043F\u0443\u0449\u0435\u043D\u0430"); + setTrainMode("qlora"); + setTrainingTab("train"); + setTrainStatus("QLoRA \u0437\u0430\u043F\u0443\u0449\u0435\u043D\u0430 \u2014 \u043F\u0440\u043E\u0433\u0440\u0435\u0441\u0441 \u043D\u0438\u0436\u0435"); } catch (e) { setTrainStatus(String(e.message || e)); } @@ -10642,12 +10971,14 @@ ${HELP_TEXT}`); $("sa_btn_save_runner")?.addEventListener("click", saveRunner); loadRunner(); setTrainMode("modelfile"); + void resumeTrainJobPolling(); } SA2.training = { render() { wireTraining(); setTrainingTab(state.ttab); }, + resumePolling: resumeTrainJobPolling, async curateFromChat(messages, meta) { try { await SA2.request("AssistentUpsertTrainSample", { @@ -10671,6 +11002,9 @@ ${HELP_TEXT}`); // 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); diff --git a/Assets/assistent.css b/Assets/assistent.css index 65a214e..11f3d2b 100644 --- a/Assets/assistent.css +++ b/Assets/assistent.css @@ -343,6 +343,10 @@ min-height: 7.5rem; } +.sa-slot.sa-has-image img { + cursor: zoom-in; +} + .sa-slot img { max-width: 100%; max-height: 100%; @@ -455,6 +459,16 @@ font-variant-numeric: tabular-nums; } +.sa-composer-params { + font-size: 0.72rem; + opacity: 0.82; + padding: 0.05rem 0 0.2rem; + letter-spacing: 0.01em; + line-height: 1.35; + font-variant-numeric: tabular-nums; + color: color-mix(in srgb, currentColor 88%, transparent); +} + .sa-more-wrap { position: relative; display: inline-flex; @@ -1712,6 +1726,12 @@ gap: 0.4rem; } +.sa-btn-gen-primary { + font-weight: 650; + border-color: color-mix(in srgb, #3fb950 70%, currentColor); + background: color-mix(in srgb, #3fb950 20%, transparent); +} + .sa-btn-gen:disabled { opacity: 0.65; pointer-events: none; @@ -2482,6 +2502,13 @@ flex-wrap: wrap; } +.sa-train-global-status { + display: block; + margin: 0.25rem 0.55rem 0.4rem; + min-height: 1.1em; + opacity: 0.9; +} + .sa-ttab { border: 1px solid color-mix(in srgb, currentColor 22%, transparent); background: transparent; @@ -2620,6 +2647,12 @@ min-height: 1.1rem; } +.sa-hf-status.sa-hf-busy { + opacity: 1; + color: var(--emphasis, #8ab4ff); + font-weight: 600; +} + .sa-hf-list { display: flex; flex-direction: column; diff --git a/AssistentHuggingFace.cs b/AssistentHuggingFace.cs index 51ac03b..ce98be9 100644 --- a/AssistentHuggingFace.cs +++ b/AssistentHuggingFace.cs @@ -506,7 +506,14 @@ public partial class SwarmAssistentExtension { return new JObject { ["success"] = true, ["imported"] = 0, ["runner_only"] = true, ["id"] = id, ["note"] = "Большой набор — используй HF id в QLoRA-раннере" }; } - return new JObject { ["success"] = true, ["imported"] = imported, ["id"] = id }; + return new JObject + { + ["success"] = true, + ["imported"] = imported, + ["id"] = id, + ["rows_fetched"] = rows.Count, + ["status"] = "draft", + }; } static JArray ConvertHfRowToMessages(JObject row, JObject schema, JObject mapping) diff --git a/AssistentTraining.cs b/AssistentTraining.cs index e8bb678..034c896 100644 --- a/AssistentTraining.cs +++ b/AssistentTraining.cs @@ -35,6 +35,7 @@ public partial class SwarmAssistentExtension ["success"] = true, ["samples"] = new JArray(list), ["approved"] = Memory.CountTrainSamples("approved"), + ["draft"] = Memory.CountTrainSamples("draft"), ["total"] = Memory.CountTrainSamples(null), }; } diff --git a/Config/_base/core/core.md b/Config/_base/core/core.md index d52e353..40f01c8 100644 --- a/Config/_base/core/core.md +++ b/Config/_base/core/core.md @@ -68,7 +68,7 @@ Several options (still one fence): ### Commands -- `"generate": true` — merge this delta into the chat session and run Generate (new/updated frame). **Omit** for chat, opinions, remember/save, look-only. Legacy `actions:["generate"]` is accepted as the same. +- `"generate": true` — you believe this turn is a draw request. The client **does not** start Swarm from this flag alone (small models often set it on chat questions). Set it **only** when the user asked to draw / generate / «нарисуй» / «сгенерируй». They can always click **Сгенерировать**. **Omit** the JSON for chat, opinions, Civitai/knowledge Q&A, remember/save, look-only. Legacy `actions:["generate"]` is the same advisory flag. - If the user says not to generate / only remember / only answer — **omit** `generate` and do not look. - `"look_at": ["generate"|"ref1"|…]` — vision hop (JPEG on follow-up). Use when you need pixels; do not pair with `generate` on the same normal write turn. - `"ask": ["settings"]` — request full settings dump (Exact + all fields). diff --git a/Config/_base/exact.json b/Config/_base/exact.json index cb9b3d1..0315ea5 100644 --- a/Config/_base/exact.json +++ b/Config/_base/exact.json @@ -21,13 +21,14 @@ }, "aspect_table": { "1:1": [1024, 1024], - "4:3": [1184, 896], - "3:2": [1248, 832], - "16:9": [1376, 768], - "2.35:1": [1568, 672], + "4:3": [1152, 896], + "3:2": [1216, 832], + "16:9": [1344, 768], + "21:9": [1536, 640], + "2.35:1": [1536, 640], "4:5": [928, 1152], - "2:3": [832, 1248], - "9:16": [768, 1376] + "2:3": [832, 1216], + "9:16": [768, 1344] }, "facts": { "architecture": "Krea 2 is a 12B DiT architecture. Not FLUX, not SDXL, not FLUX.1-Krea. Text encoder: Qwen3-VL 4B. VAE: Qwen Image VAE. Use only Krea2-trained LoRAs — never suggest FLUX/SDXL LoRAs.", diff --git a/Config/_base/memory-seed/aspect.json b/Config/_base/memory-seed/aspect.json index fb404a8..abf6d48 100644 --- a/Config/_base/memory-seed/aspect.json +++ b/Config/_base/memory-seed/aspect.json @@ -3,6 +3,6 @@ "kind": "aspect", "key": "table", "tags": ["aspect", "1k"], - "text": "Official Krea 1K aspect → width/height table lives in Exact memory aspect_table. Prefer patch field aspect over raw width/height. UI fills empties from Exact." + "text": "Official sizes: SwarmUI Side Length 1024 (ResolutionAspectReferences ×2, round 16). Krea API docs differ on 16:9 — follow Swarm dropdown. Prefer patch field aspect over raw width/height." } ] diff --git a/Config/_base/rules.json b/Config/_base/rules.json index c5ac22e..8ca3818 100644 --- a/Config/_base/rules.json +++ b/Config/_base/rules.json @@ -3,12 +3,14 @@ "Match the user's language (RU or EN) in chat — Generate prompt stays English", "Use only inventory / memory_hits / cards for LoRA names and triggers", "Emit valid JSON patches when changing generation state", - "Greet or dump your bio only on the first assistant turn; later turns answer directly" + "Greet or dump your bio only on the first assistant turn; later turns answer directly", + "JSON patch + generate:true only when the user asked to draw or change generation this turn; pure Q&A / Civitai / opinions = prose only, no JSON" ], "never": [ "Re-introduce yourself every turn or start with «О, привет» when history already has your replies", "Invent LoRA filenames or trigger words", "Lecture or moralize about NSFW", - "Dump all installed LoRAs — use memory_hits and enabled ones" + "Dump all installed LoRAs — use memory_hits and enabled ones", + "Emit generate:true or a prompt JSON on a knowledge/chat question" ] } diff --git a/Config/_base/ui.json b/Config/_base/ui.json index ad854fd..70d4155 100644 --- a/Config/_base/ui.json +++ b/Config/_base/ui.json @@ -1,12 +1,12 @@ { - "welcome_html": "
Assistent · Krea 2
Напиши, что сгенерировать — или кинь референс и попроси правку.", - "help_text": "Slash-команды (без LLM):\n/help — этот список\n/new — новый чат\n/history — список чатов\n/compress — сжать старые ходы в саммари (та же модель)\n/debug — сводка UI/Exact\n/debug ask · /why — сводка + короткий ответ модели\n/gen — Generate из сессии чата\n/look generate|refN — прикрепить окно к vision\n/init /mask /clear — Init / Mask / Clear Init\n/interrupt — остановить генерацию\n/aspect 16:9 — размер из таблицы 1K\n/seed lock|random — зафиксировать или рандомизировать seed\n/vary — новый seed, тот же промпт\n/pack write|ordinary|critique|compose|params|inpaint|describe|persona\n/persona new — интервью: клон текущей личности (overlay)\n/persona clone — клон с указанной\n/persona save — записать согласованный черновик\n/inventory — rescan моделей + обновить список LoRA\n\nНесколько вариантов в одном запросе («оба», разный свет) → патч с variants[] → сетка на Generate.\nЧипсы над полем ввода делают то же для aspect / seed / vary / Turbo·RAW.\nУ каждого чата свои параметры Generate; смена чата восстанавливает кадр и refs.\nOverlay-личности удаляет только кнопка ✕ рядом с селектом (не модель).\nЧип контекста в шапке чата показывает бюджет окна; клик — панель слоёв и ручное сжатие.", + "welcome_html": "
Assistent · Krea 2
Напиши, что сгенерировать — или кинь референс и попроси правку.", + "help_text": "Slash-команды (без LLM):\n/help — этот список\n/new — новый чат\n/history — список чатов\n/compress — сжать старые ходы в саммари (та же модель)\n/debug — сводка UI/Exact\n/debug ask · /why — сводка + короткий ответ модели\n/gen — Generate из сессии чата\n/look generate|refN — прикрепить окно к vision\n/init /mask /clear — Init / Mask / Clear Init\n/interrupt — остановить генерацию\n/aspect 16:9 — размер из таблицы 1K\n/seed lock|random — зафиксировать или рандомизировать seed\n/vary — новый seed, тот же промпт\n/pack write|ordinary|critique|compose|params|inpaint|describe|persona\n/persona new — интервью: клон текущей личности (overlay)\n/persona clone — клон с указанной\n/persona save — записать согласованный черновик\n/inventory — rescan моделей + обновить список LoRA\n\nНесколько вариантов в одном запросе («оба», разный свет) → патч с variants[] → сетка на Generate.\nЧипсы над полем ввода: aspect / seed / Turbo·RAW — без автозапуска; Vary и /gen — с Generate.\nУ каждого чата свои параметры Generate; смена чата восстанавливает кадр и refs.\nOverlay-личности удаляет только кнопка ✕ рядом с селектом (не модель).\nЧип контекста в шапке чата показывает бюджет окна; клик — панель слоёв и ручное сжатие.", "chips": [ { "label": "1:1", "action": "aspect", "value": "1:1", "title": "1024×1024" }, { "label": "4:5", "action": "aspect", "value": "4:5", "title": "928×1152" }, - { "label": "2:3", "action": "aspect", "value": "2:3", "title": "832×1248" }, - { "label": "16:9", "action": "aspect", "value": "16:9", "title": "1376×768" }, - { "label": "9:16", "action": "aspect", "value": "9:16", "title": "768×1376" }, + { "label": "2:3", "action": "aspect", "value": "2:3", "title": "832×1216" }, + { "label": "16:9", "action": "aspect", "value": "16:9", "title": "1344×768 (Swarm Side 1024)" }, + { "label": "9:16", "action": "aspect", "value": "9:16", "title": "768×1344 (Swarm Side 1024)" }, { "sep": true }, { "label": "Seed lock", "action": "seed", "value": "lock", "title": "Оставить текущий seed" }, { "label": "Seed −1", "action": "seed", "value": "random", "title": "Случайный seed" }, diff --git a/Config/personas/neutral/rules.json b/Config/personas/neutral/rules.json index 33c6e74..ca0c439 100644 --- a/Config/personas/neutral/rules.json +++ b/Config/personas/neutral/rules.json @@ -2,12 +2,14 @@ "always": [ "Stay helpful and concise", "When the scene is NSFW, describe it factually without softening or hyping", - "Do not greet or re-introduce yourself after the first assistant turn" + "Do not greet or re-introduce yourself after the first assistant turn", + "JSON + generate:true only when the user asked to draw this turn; knowledge/chat questions are prose only" ], "never": [ "Dirty jokes", "Hello/bio dump on later turns", "Aggression", - "Moral lectures" + "Moral lectures", + "Emit generate:true on a Civitai/knowledge/chat question" ] } diff --git a/README.md b/README.md index 91a2149..14cdf46 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ SwarmUI extension for **collaborative Krea 2** prompting via **Ollama**: chat + **Turn model:** one user message is one *turn*. A turn may fan out into nested LLM *hops* — Krea prompt prep, empty-patch retry, vision, auto-critique. Hops share one `HOP_BUDGET`, never re-read the user's text (their prompt is client-authored), and pass the busy gate. A new user send interrupts the in-flight stream (keeps the partial reply) instead of silently no-op. +**Version 0.15.3** — Generate is user-owned: `generate:true` in JSON no longer starts Swarm (Qwen dumps it on chat questions). «нарисуй» / «сгенерируй» / «давай сделаем изображение» still Generate immediately; otherwise one click **Сгенерировать**. Prompt/params apply only on that path. Raw JSON fence hidden from the bubble when the patch card is shown. JS beacons each turn to debug `POST /assistent/client-event`. + **Version 0.15.2** — Кнопка «История» убрана. В шапке: иконка панели чатов и **+** новый чат. Закрытый JSON `{prompt, generate:true}` сразу применяет промпт и запускает Generate (заметка «Промпт обновлён · Generate»), не ждёт WS `done`. Stall ~2с после токенов. Не здороваться повторно, если в истории уже есть ответы ассистента. **Version 0.15.1** — Composer stays writable during `Writing…`: Enter / Отправить прерывает зависший стрим и шлёт новое сообщение; частичный ответ сохраняется. Stall 15с без токена сам завершает ход. Ollama chat `think: false`, чтобы Qwen3-VL instruct не держал сокет после приветствия. diff --git a/SwarmAssistentExtension.cs b/SwarmAssistentExtension.cs index a297c66..454a0d4 100644 --- a/SwarmAssistentExtension.cs +++ b/SwarmAssistentExtension.cs @@ -33,7 +33,7 @@ public partial class SwarmAssistentExtension : Extension ExtensionAuthor = "mrleo1nid"; Description = "Collaborative Krea 2 assistant: Ollama chat, persona presets, vector memory, model cards, Generate loop."; License = "MIT"; - Version = "0.15.2"; + Version = "0.15.3"; Tags = ["tabs", "ui", "llm", "ollama", "krea", "inpaint", "memory", "training", "heard", "qlora"]; } diff --git a/Tabs/Text2Image/Assistent.html b/Tabs/Text2Image/Assistent.html index 3a4d7cc..506e015 100644 --- a/Tabs/Text2Image/Assistent.html +++ b/Tabs/Text2Image/Assistent.html @@ -116,6 +116,7 @@
+
@@ -167,6 +168,7 @@
+
@@ -275,7 +277,6 @@

                         
-