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).
* 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 () {
const LS_BASE = 'swarm_assistent_base_url';
@@ -102,6 +102,8 @@
exact: null,
sessionExact: {},
lastUserParamIntent: false,
lastPatch: null,
pendingSilentGen: false,
enabledSkills: [],
kreaProfiles: { turbo: { steps: 8, cfg: 1, sigma_shift: 1.15 }, raw: { steps: 28, cfg: 4.5 } },
preferredEmbed: null,
@@ -445,6 +447,28 @@
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) {
if (!obj || typeof obj !== 'object') {
return false;
@@ -918,13 +942,21 @@
});
}
function mountPatchActions(parent, patch) {
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';
note.textContent = 'Применено автоматически · Generate…';
host.appendChild(note);
return;
}
const actions = document.createElement('div');
actions.className = 'sa-patch-actions sa-patch-current';
for (const [label, which] of [
@@ -956,6 +988,23 @@
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() {
const board = $('sa_board');
if (!board) {
@@ -2606,12 +2655,14 @@
div.textContent = prose || text || '';
}
if (finalPatch) {
rememberLastPatch(finalPatch);
const wrap = document.createElement('div');
wrap.className = 'sa-patch';
const pre = document.createElement('pre');
pre.textContent = JSON.stringify(finalPatch, null, 2);
wrap.appendChild(pre);
mountPatchActions(wrap, finalPatch);
const silent = !!(meta && meta.silentPatch);
mountPatchActions(wrap, finalPatch, { silent });
div.appendChild(wrap);
}
if (civitaiResults && civitaiResults.length) {
@@ -2684,12 +2735,14 @@
setAssistantBody(el, prose || fullReply || '');
el.querySelectorAll('.sa-patch, .sa-civitai-list').forEach((n) => n.remove());
if (patch && !isCardObject(patch) && !(card && !patch.prompt && !patch.actions && !patch.loras)) {
rememberLastPatch(patch);
const wrap = document.createElement('div');
wrap.className = 'sa-patch';
const pre = document.createElement('pre');
pre.textContent = JSON.stringify(patch, null, 2);
wrap.appendChild(pre);
mountPatchActions(wrap, patch);
const silent = !!(meta && meta.silentPatch) || !!state.pendingSilentGen;
mountPatchActions(wrap, patch, { silent });
el.appendChild(wrap);
} else if (card) {
const wrap = document.createElement('div');
@@ -4212,6 +4265,9 @@
return;
}
const { patch } = extractPatch(reply);
if (patch) {
rememberLastPatch(patch);
}
if (Array.isArray(patch?.actions) && patch.actions.map(String).includes('interrupt')) {
doInterruptNow();
}
@@ -4228,16 +4284,23 @@
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');
updateTasteFromPatch(patch, opts.userText || '');
if (!fromAutoCritique) {
const src = await runGenerateFromPatch(patch);
if (!fromAutoCritique && (wantsGen || $('sa_auto_generate')?.checked)) {
const src = await runGenerateFromPatch(
{ ...patch, actions: Array.isArray(patch.actions) ? patch.actions : ['generate'] },
{ force: wantsGen },
);
if (src) {
await maybeAutoCritique(src);
}
}
}
state.pendingSilentGen = false;
}
async function applyQuickPatch(patch, note) {
@@ -4502,6 +4565,7 @@
}
if (!opts.fromAutoCritique && !opts.fromDownload && !opts.fromVisionHop) {
state.lastUserParamIntent = userTextMentionsParams(text);
state.pendingSilentGen = userAsksGenerate(text);
}
if (!opts.fromAutoCritique && !opts.fromDownload && !opts.fromVisionHop && !opts.skipSlash) {
@@ -4599,6 +4663,7 @@
const msgMeta = {
persona: currentPersonaInfo(),
pack,
silentPatch: !!state.pendingSilentGen,
};
state.history.push({ role: 'user', content: text });
if (state.pendingPersonaNote) {
@@ -4671,6 +4736,7 @@
await handleReplySideEffects(reply, civitaiResults, {
...opts,
userText: text,
userWantsGenerate: !!state.pendingSilentGen || userAsksGenerate(text),
attachedSlotIds: visionSlots.map((s) => s.id),
});
};
@@ -5020,6 +5086,7 @@
$('sa_btn_clear_init')?.addEventListener('click', clearInitAndMask);
$('sa_btn_clear_image')?.addEventListener('click', () => clearSlot(state.selectedSlotId));
$('sa_btn_send')?.addEventListener('click', () => sendChat());
$('sa_btn_build_gen')?.addEventListener('click', () => buildCurrentAndGenerate());
$('sa_btn_interrupt')?.addEventListener('click', () => {
doInterruptNow();
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)
- `"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.
- `"interrupt"` — stop generation.
- `"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.
- 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.
+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.
**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
+1 -1
View File
@@ -49,7 +49,7 @@ public class SwarmAssistentExtension : Extension
ExtensionAuthor = "mrleo1nid";
Description = "Collaborative Krea 2 assistant: Ollama chat, persona presets, vector memory, model cards, Generate loop.";
License = "MIT";
Version = "0.7.2";
Version = "0.7.3";
Tags = ["tabs", "ui", "llm", "ollama", "krea", "inpaint", "memory"];
}
+1
View File
@@ -100,6 +100,7 @@
</div>
<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-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_clear">Очистить чат</button>
<span class="sa-status" id="sa_status"></span>