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;