Add silent generate-on-request and composer Build+Gen button.

When the user explicitly asks to generate, auto-apply the patch and run Generate without the Apply button strip; Собрать + Gen reapplies the last patch (or current SwarmUI prompt) and generates.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-21 23:23:17 +03:00
co-authored by Cursor
parent 723ab82991
commit c51550bd7e
6 changed files with 79 additions and 11 deletions
+74 -7
View File
@@ -1,6 +1,6 @@
/** /**
* Swarm Assistent — Krea 2 collaborative chat (Ollama via Swarm API). * Swarm Assistent — Krea 2 collaborative chat (Ollama via Swarm API).
* v0.7.2: Slim Exact in prompt; no Exact blob in live ctx; tighter preview filter; critique requires vision+JSON. * v0.7.3: Silent apply+gen when user asks to generate; composer «Собрать + Gen» button.
*/ */
(function () { (function () {
const LS_BASE = 'swarm_assistent_base_url'; const LS_BASE = 'swarm_assistent_base_url';
@@ -102,6 +102,8 @@
exact: null, exact: null,
sessionExact: {}, sessionExact: {},
lastUserParamIntent: false, lastUserParamIntent: false,
lastPatch: null,
pendingSilentGen: false,
enabledSkills: [], enabledSkills: [],
kreaProfiles: { turbo: { steps: 8, cfg: 1, sigma_shift: 1.15 }, raw: { steps: 28, cfg: 4.5 } }, kreaProfiles: { turbo: { steps: 8, cfg: 1, sigma_shift: 1.15 }, raw: { steps: 28, cfg: 4.5 } },
preferredEmbed: null, preferredEmbed: null,
@@ -445,6 +447,28 @@
return /\b(steps?|cfg|seed|sigma|размер|aspect|ширин|высот|resolution|batch|турбо|turbo|raw)\b/i.test(String(text || '')); return /\b(steps?|cfg|seed|sigma|размер|aspect|ширин|высот|resolution|batch|турбо|turbo|raw)\b/i.test(String(text || ''));
} }
function userAsksGenerate(text) {
const t = String(text || '');
if (!t.trim()) {
return false;
}
if (/^(gen|generate|go|рисуй|давай|ещё|еще)\s*[!.…]*$/i.test(t.trim())) {
return true;
}
return /\b(сгенерируй|сгенерировать|генерируй|generate|нарисуй|перегенерируй|перерисуй|сделай\s+(картинк|изображен|фото)|run\s+generat|\/gen)\b/i.test(t);
}
function rememberLastPatch(patch) {
if (patch && typeof patch === 'object' && !isCardObject(patch)) {
state.lastPatch = patch;
const btn = $('sa_btn_build_gen');
if (btn) {
btn.disabled = false;
btn.title = 'Применить последний патч Assistent и Generate';
}
}
}
function applyAspectTableFrom(obj) { function applyAspectTableFrom(obj) {
if (!obj || typeof obj !== 'object') { if (!obj || typeof obj !== 'object') {
return false; return false;
@@ -918,13 +942,21 @@
}); });
} }
function mountPatchActions(parent, patch) { function mountPatchActions(parent, patch, { silent = false } = {}) {
if (!parent || !patch) { if (!parent || !patch) {
return; return;
} }
rememberLastPatch(patch);
retireStalePatchActions(); retireStalePatchActions();
const wrap = parent.classList.contains('sa-patch') ? parent : null; const wrap = parent.classList.contains('sa-patch') ? parent : null;
const host = wrap || parent; const host = wrap || parent;
if (silent) {
const note = document.createElement('div');
note.className = 'sa-patch-actions sa-patch-silent sa-patch-current';
note.textContent = 'Применено автоматически · Generate…';
host.appendChild(note);
return;
}
const actions = document.createElement('div'); const actions = document.createElement('div');
actions.className = 'sa-patch-actions sa-patch-current'; actions.className = 'sa-patch-actions sa-patch-current';
for (const [label, which] of [ for (const [label, which] of [
@@ -956,6 +988,23 @@
syncPatchActionAvailability(); syncPatchActionAvailability();
} }
async function buildCurrentAndGenerate() {
if (state.busy || state.generating || isGenerateUnavailable()) {
setStatus('Занято — подожди или нажми Стоп');
return;
}
const patch = state.lastPatch;
if (patch) {
setStatus('Собираю патч → Generate…');
await applyPatch(patch, 'all');
await runGenerateFromPatch({ ...patch, actions: ['generate'] }, { force: true });
return;
}
// No Assistent patch yet — generate with whatever is already in SwarmUI.
setStatus('Generate с текущим промптом…');
await runGenerateFromPatch({ actions: ['generate'] }, { force: true });
}
function renderBoard() { function renderBoard() {
const board = $('sa_board'); const board = $('sa_board');
if (!board) { if (!board) {
@@ -2606,12 +2655,14 @@
div.textContent = prose || text || ''; div.textContent = prose || text || '';
} }
if (finalPatch) { if (finalPatch) {
rememberLastPatch(finalPatch);
const wrap = document.createElement('div'); const wrap = document.createElement('div');
wrap.className = 'sa-patch'; wrap.className = 'sa-patch';
const pre = document.createElement('pre'); const pre = document.createElement('pre');
pre.textContent = JSON.stringify(finalPatch, null, 2); pre.textContent = JSON.stringify(finalPatch, null, 2);
wrap.appendChild(pre); wrap.appendChild(pre);
mountPatchActions(wrap, finalPatch); const silent = !!(meta && meta.silentPatch);
mountPatchActions(wrap, finalPatch, { silent });
div.appendChild(wrap); div.appendChild(wrap);
} }
if (civitaiResults && civitaiResults.length) { if (civitaiResults && civitaiResults.length) {
@@ -2684,12 +2735,14 @@
setAssistantBody(el, prose || fullReply || ''); setAssistantBody(el, prose || fullReply || '');
el.querySelectorAll('.sa-patch, .sa-civitai-list').forEach((n) => n.remove()); el.querySelectorAll('.sa-patch, .sa-civitai-list').forEach((n) => n.remove());
if (patch && !isCardObject(patch) && !(card && !patch.prompt && !patch.actions && !patch.loras)) { if (patch && !isCardObject(patch) && !(card && !patch.prompt && !patch.actions && !patch.loras)) {
rememberLastPatch(patch);
const wrap = document.createElement('div'); const wrap = document.createElement('div');
wrap.className = 'sa-patch'; wrap.className = 'sa-patch';
const pre = document.createElement('pre'); const pre = document.createElement('pre');
pre.textContent = JSON.stringify(patch, null, 2); pre.textContent = JSON.stringify(patch, null, 2);
wrap.appendChild(pre); wrap.appendChild(pre);
mountPatchActions(wrap, patch); const silent = !!(meta && meta.silentPatch) || !!state.pendingSilentGen;
mountPatchActions(wrap, patch, { silent });
el.appendChild(wrap); el.appendChild(wrap);
} else if (card) { } else if (card) {
const wrap = document.createElement('div'); const wrap = document.createElement('div');
@@ -4212,6 +4265,9 @@
return; return;
} }
const { patch } = extractPatch(reply); const { patch } = extractPatch(reply);
if (patch) {
rememberLastPatch(patch);
}
if (Array.isArray(patch?.actions) && patch.actions.map(String).includes('interrupt')) { if (Array.isArray(patch?.actions) && patch.actions.map(String).includes('interrupt')) {
doInterruptNow(); doInterruptNow();
} }
@@ -4228,16 +4284,23 @@
return; return;
} }
} }
if (patch && $('sa_auto_apply')?.checked) { const wantsGen = !!(opts.userWantsGenerate || state.pendingSilentGen
|| (Array.isArray(patch?.actions) && patch.actions.map(String).includes('generate')));
const doApply = !!(patch && (wantsGen || $('sa_auto_apply')?.checked));
if (doApply) {
await applyPatch(patch, 'all'); await applyPatch(patch, 'all');
updateTasteFromPatch(patch, opts.userText || ''); updateTasteFromPatch(patch, opts.userText || '');
if (!fromAutoCritique) { if (!fromAutoCritique && (wantsGen || $('sa_auto_generate')?.checked)) {
const src = await runGenerateFromPatch(patch); const src = await runGenerateFromPatch(
{ ...patch, actions: Array.isArray(patch.actions) ? patch.actions : ['generate'] },
{ force: wantsGen },
);
if (src) { if (src) {
await maybeAutoCritique(src); await maybeAutoCritique(src);
} }
} }
} }
state.pendingSilentGen = false;
} }
async function applyQuickPatch(patch, note) { async function applyQuickPatch(patch, note) {
@@ -4502,6 +4565,7 @@
} }
if (!opts.fromAutoCritique && !opts.fromDownload && !opts.fromVisionHop) { if (!opts.fromAutoCritique && !opts.fromDownload && !opts.fromVisionHop) {
state.lastUserParamIntent = userTextMentionsParams(text); state.lastUserParamIntent = userTextMentionsParams(text);
state.pendingSilentGen = userAsksGenerate(text);
} }
if (!opts.fromAutoCritique && !opts.fromDownload && !opts.fromVisionHop && !opts.skipSlash) { if (!opts.fromAutoCritique && !opts.fromDownload && !opts.fromVisionHop && !opts.skipSlash) {
@@ -4599,6 +4663,7 @@
const msgMeta = { const msgMeta = {
persona: currentPersonaInfo(), persona: currentPersonaInfo(),
pack, pack,
silentPatch: !!state.pendingSilentGen,
}; };
state.history.push({ role: 'user', content: text }); state.history.push({ role: 'user', content: text });
if (state.pendingPersonaNote) { if (state.pendingPersonaNote) {
@@ -4671,6 +4736,7 @@
await handleReplySideEffects(reply, civitaiResults, { await handleReplySideEffects(reply, civitaiResults, {
...opts, ...opts,
userText: text, userText: text,
userWantsGenerate: !!state.pendingSilentGen || userAsksGenerate(text),
attachedSlotIds: visionSlots.map((s) => s.id), attachedSlotIds: visionSlots.map((s) => s.id),
}); });
}; };
@@ -5020,6 +5086,7 @@
$('sa_btn_clear_init')?.addEventListener('click', clearInitAndMask); $('sa_btn_clear_init')?.addEventListener('click', clearInitAndMask);
$('sa_btn_clear_image')?.addEventListener('click', () => clearSlot(state.selectedSlotId)); $('sa_btn_clear_image')?.addEventListener('click', () => clearSlot(state.selectedSlotId));
$('sa_btn_send')?.addEventListener('click', () => sendChat()); $('sa_btn_send')?.addEventListener('click', () => sendChat());
$('sa_btn_build_gen')?.addEventListener('click', () => buildCurrentAndGenerate());
$('sa_btn_interrupt')?.addEventListener('click', () => { $('sa_btn_interrupt')?.addEventListener('click', () => {
doInterruptNow(); doInterruptNow();
state.busy = false; state.busy = false;
+1 -1
View File
@@ -95,7 +95,7 @@ A JSON block named "Live SwarmUI context" is attached. Treat it as ground truth
### Actions (auto-safe) ### Actions (auto-safe)
- `"generate"` — after Apply, start generation. - `"generate"` — after Apply, start generation. When the user explicitly asks to generate, always include this; the UI applies silently (no Apply-button strip).
- `"search_civitai"` — Civitai search; user Confirms downloads. - `"search_civitai"` — Civitai search; user Confirms downloads.
- `"interrupt"` — stop generation. - `"interrupt"` — stop generation.
- `"memory_upsert"` / `"memory_forget"` — write or delete facts in vector memory. - `"memory_upsert"` / `"memory_forget"` — write or delete facts in vector memory.
+1 -1
View File
@@ -29,5 +29,5 @@ Good: `A fluffy red fox sitting alert in fresh powder snow, ears forward, breath
- Explain briefly what you changed. - Explain briefly what you changed.
- JSON patch with at least `prompt`, and `loras` when relevant. - JSON patch with at least `prompt`, and `loras` when relevant.
- `actions: ["generate"]` when the user wants a new image. - `actions: ["generate"]` when the user wants a new image — the UI will apply the patch and Generate **without** showing Apply buttons.
- `aspect` (or width/height) only if framing should change. - `aspect` (or width/height) only if framing should change.
+1 -1
View File
@@ -2,7 +2,7 @@
SwarmUI extension for **collaborative Krea 2** prompting via **Ollama**: chat + board (Generate | Refs tabs), LoRA chips, **persona presets** (`Config/personas/`), **vector memory**, model cards with Civitai fetch, img2img/inpaint, slash commands, auto Generate. SwarmUI extension for **collaborative Krea 2** prompting via **Ollama**: chat + board (Generate | Refs tabs), LoRA chips, **persona presets** (`Config/personas/`), **vector memory**, model cards with Civitai fetch, img2img/inpaint, slash commands, auto Generate.
**Version 0.7.2** — Exact KV (slim in prompt); block `ViewSpecial` model-card previews; critique requires vision + real JSON fence. **Version 0.7.3** — Exact KV; silent apply+Generate when user asks; composer **Собрать + Gen**; block model-card previews; critique needs vision + JSON.
## Layout ## Layout
+1 -1
View File
@@ -49,7 +49,7 @@ public class SwarmAssistentExtension : Extension
ExtensionAuthor = "mrleo1nid"; ExtensionAuthor = "mrleo1nid";
Description = "Collaborative Krea 2 assistant: Ollama chat, persona presets, vector memory, model cards, Generate loop."; Description = "Collaborative Krea 2 assistant: Ollama chat, persona presets, vector memory, model cards, Generate loop.";
License = "MIT"; License = "MIT";
Version = "0.7.2"; Version = "0.7.3";
Tags = ["tabs", "ui", "llm", "ollama", "krea", "inpaint", "memory"]; Tags = ["tabs", "ui", "llm", "ollama", "krea", "inpaint", "memory"];
} }
+1
View File
@@ -100,6 +100,7 @@
</div> </div>
<div class="sa-composer-actions"> <div class="sa-composer-actions">
<button type="button" class="basic-button sa-primary" id="sa_btn_send">Отправить</button> <button type="button" class="basic-button sa-primary" id="sa_btn_send">Отправить</button>
<button type="button" class="basic-button sa-btn-gen" id="sa_btn_build_gen" title="Применить последний патч Assistent (если есть) и Generate">Собрать + Gen</button>
<button type="button" class="basic-button" id="sa_btn_interrupt" title="Прервать генерацию / чат" hidden>Стоп</button> <button type="button" class="basic-button" id="sa_btn_interrupt" title="Прервать генерацию / чат" hidden>Стоп</button>
<button type="button" class="basic-button" id="sa_btn_clear">Очистить чат</button> <button type="button" class="basic-button" id="sa_btn_clear">Очистить чат</button>
<span class="sa-status" id="sa_status"></span> <span class="sa-status" id="sa_status"></span>