Ship Assistent 0.11.6: opt-in vision, negative pass-through, generate from context.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-22 05:21:48 +03:00
co-authored by Cursor
parent 56e089d8da
commit 784426c9bf
11 changed files with 265 additions and 40 deletions
+236 -18
View File
@@ -16,6 +16,8 @@
const LS_AUTO_APPLY = 'swarm_assistent_auto_apply'; const LS_AUTO_APPLY = 'swarm_assistent_auto_apply';
const LS_AUTO_GENERATE = 'swarm_assistent_auto_generate'; const LS_AUTO_GENERATE = 'swarm_assistent_auto_generate';
const LS_AUTO_CRITIQUE = 'swarm_assistent_auto_critique'; const LS_AUTO_CRITIQUE = 'swarm_assistent_auto_critique';
/** One-shot: turn off auto look_at / auto-critique (vision is button / /look / model look_at). */
const LS_VISION_OPTIN_MIG = 'swarm_assistent_vision_optin_v1';
const LS_AUTO_DOWNLOAD = 'swarm_assistent_auto_download'; const LS_AUTO_DOWNLOAD = 'swarm_assistent_auto_download';
/** When '1', unload chat LLM before Generate (frees VRAM; VL reload can take 12 min). Default off. */ /** When '1', unload chat LLM before Generate (frees VRAM; VL reload can take 12 min). Default off. */
const LS_PARK_LLM = 'swarm_assistent_park_llm'; const LS_PARK_LLM = 'swarm_assistent_park_llm';
@@ -89,7 +91,7 @@
/debug ask то же + короткий ответ модели /debug ask то же + короткий ответ модели
/why сразу /debug ask /why сразу /debug ask
/gen Generate сейчас /gen Generate сейчас
/look generate|refN прикрепить окно к vision /look generate|refN показать кадр модели (vision)
/init /mask /clear Init / Mask / Clear Init /init /mask /clear Init / Mask / Clear Init
/interrupt остановить генерацию /interrupt остановить генерацию
/aspect 16:9 размер из таблицы 1K /aspect 16:9 размер из таблицы 1K
@@ -656,6 +658,43 @@
el.dispatchEvent(new Event('change', { bubbles: true })); el.dispatchEvent(new Event('change', { bubbles: true }));
} }
function liveNegativePrompt() {
return String(val('input_negativeprompt') || val('alt_negativeprompt_textbox') || '').trim();
}
function exactDefaultNegative() {
const { exact } = resolveExactBundle();
const n = exact?.generation?.negative ?? exact?.negative;
return n != null ? String(n).trim() : '';
}
function setNegativePrompt(text) {
const s = text != null ? String(text) : '';
if (document.getElementById('input_negativeprompt')) {
setVal('input_negativeprompt', s);
}
if (document.getElementById('alt_negativeprompt_textbox')) {
setVal('alt_negativeprompt_textbox', s);
}
}
/** Create / keep / pass-through negative so Generate never drops the Swarm box. */
function ensureNegativeForGenerate(patch) {
let neg = '';
if (patch && patch.negative != null && String(patch.negative).trim() !== '') {
neg = String(patch.negative).trim();
} else {
neg = liveNegativePrompt() || exactDefaultNegative();
}
if (neg) {
setNegativePrompt(neg);
if (patch && (patch.negative == null || String(patch.negative).trim() === '')) {
patch.negative = neg;
}
}
return neg;
}
function isEmptyParamField(raw, { treatZeroEmpty = false } = {}) { function isEmptyParamField(raw, { treatZeroEmpty = false } = {}) {
if (raw == null) { if (raw == null) {
return true; return true;
@@ -875,7 +914,7 @@
/** When the model wrote ### JSON Patch with no fence — build Apply+Generate from prose / last patch. */ /** When the model wrote ### JSON Patch with no fence — build Apply+Generate from prose / last patch. */
function synthesizePatchAfterEmptyFence(reply, userText, opts = {}) { function synthesizePatchAfterEmptyFence(reply, userText, opts = {}) {
const wants = !!(opts.userWantsGenerate || state.pendingSilentGen const wants = !!(opts.userWantsGenerate || state.pendingSilentGen
|| userAsksGenerate(userText) || userAsksContinue(userText)); || userAsksGenerate(userText) || userAsksContinue(userText) || userImpliesGenerate(userText));
const missing = replyMissingJsonPatch(reply); const missing = replyMissingJsonPatch(reply);
if (!wants && !missing) { if (!wants && !missing) {
return null; return null;
@@ -930,6 +969,14 @@
if (patch.aspect) { if (patch.aspect) {
keep.aspect = patch.aspect; keep.aspect = patch.aspect;
} }
if (patch.negative != null && String(patch.negative).trim() !== '') {
keep.negative = patch.negative;
} else {
const liveNeg = liveNegativePrompt() || exactDefaultNegative();
if (liveNeg) {
keep.negative = liveNeg;
}
}
if (Array.isArray(patch.loras)) { if (Array.isArray(patch.loras)) {
keep.loras = patch.loras; keep.loras = patch.loras;
} }
@@ -947,9 +994,10 @@
+ '- Structure & front-load: subject → pose/action → body/wardrobe → setting → materials/textures → camera/framing → lighting → medium/mood.\n' + '- Structure & front-load: subject → pose/action → body/wardrobe → setting → materials/textures → camera/framing → lighting → medium/mood.\n'
+ '- Expand thin ideas; fix anti-patterns; one coherent scene.\n' + '- Expand thin ideas; fix anti-patterns; one coherent scene.\n'
+ '- Keep LoRA trigger phrases in English near the subject they affect.\n' + '- Keep LoRA trigger phrases in English near the subject they affect.\n'
+ '- Prefer positives over negatives; preserve meaning and NSFW level from SOURCE.\n' + '- Always include JSON "negative": keep/supplement SOURCE+live negative, or use Exact default if empty. Never drop it.\n'
+ '- Put “no blur / empty street” ideas as positives in prompt, not as a huge negative dump.\n'
+ '- One short ack in the user language max, then ONE fenced JSON merging these keys: ' + '- One short ack in the user language max, then ONE fenced JSON merging these keys: '
+ `${JSON.stringify(keep)} plus the new English "prompt".\n` + `${JSON.stringify(keep)} plus the new English "prompt" and "negative".\n`
+ '- Include actions:["generate"] when an image was requested.\n\n' + '- Include actions:["generate"] when an image was requested.\n\n'
+ `SOURCE:\n${patch.prompt}` + `SOURCE:\n${patch.prompt}`
); );
@@ -965,6 +1013,9 @@
...base, ...base,
...effective, ...effective,
prompt: effective.prompt || base.prompt, prompt: effective.prompt || base.prompt,
negative: (effective.negative != null && String(effective.negative).trim() !== '')
? effective.negative
: (base.negative || liveNegativePrompt() || exactDefaultNegative() || undefined),
actions: (Array.isArray(effective.actions) && effective.actions.length) actions: (Array.isArray(effective.actions) && effective.actions.length)
? effective.actions ? effective.actions
: (base.actions || ['generate']), : (base.actions || ['generate']),
@@ -990,6 +1041,137 @@
).test(t); ).test(t);
} }
/** Button / /look / «посмотри на кадр» — not casual «смотри какая». */
function userAsksLook(text) {
const t = String(text || '').trim();
if (!t) {
return false;
}
if (/\b(look\s+at|critique|criticize|describe\s+(this|the|ref|image)|what\s+do\s+you\s+see)\b/i.test(t)) {
return true;
}
if (cyrTokenRe('критик[а-яё]*|что\\s+не\\s+так|разбери').test(t)) {
return true;
}
if (cyrTokenRe('опиши\\s+(это|эту|реф|изображ[а-яё]*|картинк[а-яё]*|кадр|результат|референс)').test(t)) {
return true;
}
return /(?:^|[^а-яёa-z0-9_])(посмотри|смотри|глянь)\s+(на\s+)?(результат|картинк[а-яё]*|изображен[а-яё]*|кадр|ген|реф)/i.test(t);
}
/**
* Scene / edit / «ещё» Generate without the magic word «генерируй».
* Bare «давай» and trivia questions stay false.
*/
function userImpliesGenerate(text) {
const t = String(text || '').trim();
if (!t || userAsksNoGenerate(t)) {
return false;
}
if (userAsksGenerate(t) || userAsksContinue(t) || isSameButAspectRequest(t)) {
return true;
}
if (t.length < 8) {
return false;
}
const wantsLook = userAsksLook(t);
const wantsRedraw = cyrTokenRe('поправь|исправь|перегенерир|перерисуй|улучши|переделай').test(t)
|| /\b(fix|redo|redraw|improve)\b/i.test(t);
if (wantsLook && !wantsRedraw && !userAsksGenerate(t)) {
return false;
}
if (cyrTokenRe(
'что\\s+такое|как\\s+работает|зачем\\s+|какие\\s+(лор|модел|чекпоинт)|'
+ 'список\\s+лор|где\\s+настрой|что\\s+значит',
).test(t) && !cyrTokenRe('нарису|сгенер|картинк').test(t)) {
return false;
}
if (cyrTokenRe(
'нарису|сгенер|перерису|картинк|изображен|'
+ 'хочу\\s+(увидеть|видеть)|покажи\\s+(её|ее|его|как)|'
+ 'сделай\\s+(её|ее|его|мне)|пусть\\s+будет|'
+ 'давай\\s+(её|ее|его|с\\s|в\\s|на\\s)|'
+ 'в\\s+(студии|лесу|интерьер|постел)|'
+ 'другой\\s+(ракурс|свет|наряд|поза)|'
+ 'поменяй\\s+(позу|свет|одежд|фон)|добавь\\s+(свет|детал)|'
+ 'ещё\\s+одн|еще\\s+одн|вариант',
).test(t)) {
return true;
}
if (/\b(draw|paint|render|make her|make him|another one|new frame|in the (studio|forest))\b/i.test(t)) {
return true;
}
const isQuestion = /[?]\s*$/.test(t);
if (isQuestion) {
return cyrTokenRe('нарису|сгенер|можешь\\s+(сделать|нарисовать)|можно\\s+(картинк|сгенер)').test(t);
}
return t.length >= 40 && cyrTokenRe(
'девушк|женщин|парн|мужчин|стоит|сидит|лежит|обнаж|поза|интерьер|студи',
).test(t);
}
function packBlocksAutoGenerate(pack) {
const p = String(pack || '');
return p === 'describe_ref' || p === 'catalog_card' || p === 'author_persona';
}
/** If the turn is a frame, put actions:["generate"] on the patch even when the model omitted it. */
function ensureGenerateAction(patch, userText) {
if (!patch || typeof patch !== 'object') {
return patch;
}
if (userAsksNoGenerate(userText)) {
return patch;
}
if (Array.isArray(patch.actions) && patch.actions.map(String).includes('generate')) {
return patch;
}
const pack = $('sa_pack')?.value || '';
if (packBlocksAutoGenerate(pack) && !userAsksGenerate(userText) && !userAsksContinue(userText)) {
return patch;
}
const hasFrame = patch.prompt != null
|| (Array.isArray(patch.variants) && patch.variants.length > 0);
const implied = userImpliesGenerate(userText);
if (!hasFrame && !implied) {
return patch;
}
const out = { ...patch };
const acts = Array.isArray(patch.actions) ? patch.actions.map(String).filter((a) => a && a !== 'generate') : [];
acts.push('generate');
out.actions = acts;
return out;
}
function packWantsVision(pack) {
const p = String(pack || '');
return p === 'critique_image' || p === 'describe_ref' || p === 'compose_scene' || p === 'inpaint_edit';
}
/** Honor model look_at only when asked, pack needs it, or look-only (no Generate this turn). */
function shouldHonorLookAt(patch, opts = {}) {
if (!patch || typeof patch !== 'object') {
return false;
}
if (patch.look_at == null && patch.vision_from == null && patch.vision_slots == null) {
return false;
}
if (opts.fromAutoCritique || opts.fromVisionHop) {
return true;
}
if (userAsksLook(opts.userText || '')) {
return true;
}
if (packWantsVision($('sa_pack')?.value)) {
return true;
}
// look_at + generate on a write turn stares at the old frame and delays the new one.
if (patchHasGenTrigger(patch)) {
return false;
}
return true;
}
function stripGenerateAction(patch) { function stripGenerateAction(patch) {
if (!patch || typeof patch !== 'object') { if (!patch || typeof patch !== 'object') {
return patch; return patch;
@@ -1204,6 +1386,12 @@
setVal(batchId, String(batch)); setVal(batchId, String(batch));
} }
} }
if (!liveNegativePrompt()) {
const neg = exactDefaultNegative();
if (neg) {
setNegativePrompt(neg);
}
}
} }
function shouldSkipSessionRollback(key, patchValue) { function shouldSkipSessionRollback(key, patchValue) {
@@ -2274,6 +2462,9 @@
promptBox.dispatchEvent(new Event('change', { bubbles: true })); promptBox.dispatchEvent(new Event('change', { bubbles: true }));
} }
setVal('input_negativeprompt', params.negative != null ? String(params.negative) : ''); setVal('input_negativeprompt', params.negative != null ? String(params.negative) : '');
if (document.getElementById('alt_negativeprompt_textbox')) {
setVal('alt_negativeprompt_textbox', params.negative != null ? String(params.negative) : '');
}
// Force-write numerics when present so prior chat values cannot stick. // Force-write numerics when present so prior chat values cannot stick.
if (params.width != null) { if (params.width != null) {
setVal('input_width', String(params.width)); setVal('input_width', String(params.width));
@@ -3965,9 +4156,6 @@
box.dispatchEvent(new Event('input', { bubbles: true })); box.dispatchEvent(new Event('input', { bubbles: true }));
box.dispatchEvent(new Event('change', { bubbles: true })); box.dispatchEvent(new Event('change', { bubbles: true }));
} }
if (patch.negative != null) {
setVal('input_negativeprompt', patch.negative);
}
if (Array.isArray(patch.loras)) { if (Array.isArray(patch.loras)) {
for (const l of patch.loras) { for (const l of patch.loras) {
const triggers = l.triggers || (l.trigger_phrase ? [l.trigger_phrase] : []); const triggers = l.triggers || (l.trigger_phrase ? [l.trigger_phrase] : []);
@@ -3980,6 +4168,13 @@
} }
} }
} }
if (doPrompt) {
if (patch.negative != null) {
setNegativePrompt(patch.negative);
} else if (patchHasGenTrigger(patch)) {
ensureNegativeForGenerate(patch);
}
}
if (doLoras && Array.isArray(patch.loras) && typeof loraHelper !== 'undefined' && loraHelper) { if (doLoras && Array.isArray(patch.loras) && typeof loraHelper !== 'undefined' && loraHelper) {
try { try {
@@ -4611,6 +4806,7 @@
await applyPatch(job.patch, 'all'); await applyPatch(job.patch, 'all');
syncLiveParamsBar(); syncLiveParamsBar();
} }
ensureNegativeForGenerate(job.patch);
if (epoch !== state.chatEpoch) { if (epoch !== state.chatEpoch) {
break; break;
@@ -5332,6 +5528,18 @@
} }
localStorage.setItem(LS_PACK_ORDINARY_MIG, '1'); localStorage.setItem(LS_PACK_ORDINARY_MIG, '1');
} }
// One-shot: stop staring at every Generate. Vision = button / /look / model look_at.
if (!localStorage.getItem(LS_VISION_OPTIN_MIG)) {
localStorage.setItem(LS_AUTO_VISION, '0');
localStorage.setItem(LS_AUTO_CRITIQUE, '0');
localStorage.setItem(LS_VISION_OPTIN_MIG, '1');
if ($('sa_auto_vision')) {
$('sa_auto_vision').checked = false;
}
if ($('sa_auto_critique')) {
$('sa_auto_critique').checked = false;
}
}
const base = localStorage.getItem(LS_BASE); const base = localStorage.getItem(LS_BASE);
const model = localStorage.getItem(LS_MODEL); const model = localStorage.getItem(LS_MODEL);
const pack = localStorage.getItem(LS_PACK); const pack = localStorage.getItem(LS_PACK);
@@ -7865,19 +8073,18 @@
} }
if (!effective && !fromVisionHop && !fromAutoCritique && !fromDebug if (!effective && !fromVisionHop && !fromAutoCritique && !fromDebug
&& !opts.fromEmptyPatchRetry && !opts.fromEmptyPatchRetry
&& replyMissingJsonPatch(reply) && userImpliesGenerate(opts.userText || '')) {
&& (opts.userWantsGenerate || state.pendingSilentGen || userAsksContinue(opts.userText || ''))) { appendSystemNote('Нужен кадр — прошу JSON с prompt + generate.');
appendSystemNote('Патч пустой — прошу модель дописать JSON.');
await sendChat({ await sendChat({
skipSlash: true, skipSlash: true,
skipAutoPack: true, skipAutoPack: true,
fromEmptyPatchRetry: true, fromEmptyPatchRetry: true,
userWantsGenerate: true, userWantsGenerate: true,
forcedUserText: forcedUserText:
'Ты написал «JSON Patch» без fenced ```json```. ' 'Пользователь уже просит кадр (это следует из сообщения, даже без слова «генерируй»). '
+ 'Сейчас ответь ТОЛЬКО одним fenced JSON объектом: ' + 'Ответь ТОЛЬКО одним fenced JSON: '
+ '{"prompt":"<English Krea prompt for Qwen3-VL>","actions":["generate"]}. ' + '{"prompt":"<English Krea prompt>","negative":"<echo or short quality neg>","actions":["generate"]}. '
+ 'prompt — только английский (skill prompting). Без прозы, без ### заголовков.', + 'prompt — английский. Без прозы, без «скажи сгенерируй».',
}); });
return; return;
} }
@@ -7905,7 +8112,17 @@
rememberLastPatch(effective); rememberLastPatch(effective);
} }
} }
if (effective && !fromVisionHop && !fromAutoCritique && !suppressGen) { if (effective && !fromDebug && !suppressGen) {
effective = ensureGenerateAction(effective, opts.userText || '');
rememberLastPatch(effective);
}
if (effective && !fromVisionHop && !fromAutoCritique && !fromDebug && !shouldHonorLookAt(effective, opts)) {
effective = stripLookAt(effective);
if (effective) {
rememberLastPatch(effective);
}
}
if (effective && !fromVisionHop && !fromAutoCritique && !suppressGen && shouldHonorLookAt(effective, opts)) {
const hopped = await maybeVisionHop(effective, opts.attachedSlotIds || []); const hopped = await maybeVisionHop(effective, opts.attachedSlotIds || []);
if (hopped) { if (hopped) {
return; return;
@@ -7917,6 +8134,7 @@
return; return;
} }
const wantsGen = !suppressGen && !!(opts.userWantsGenerate || state.pendingSilentGen const wantsGen = !suppressGen && !!(opts.userWantsGenerate || state.pendingSilentGen
|| userImpliesGenerate(opts.userText || '')
|| (Array.isArray(effective?.actions) && effective.actions.map(String).includes('generate'))); || (Array.isArray(effective?.actions) && effective.actions.map(String).includes('generate')));
const willGen = !!(effective && !fromAutoCritique && !suppressGen const willGen = !!(effective && !fromAutoCritique && !suppressGen
&& (wantsGen || $('sa_auto_generate')?.checked)); && (wantsGen || $('sa_auto_generate')?.checked));
@@ -8385,7 +8603,7 @@
if (!opts.fromAutoCritique && !opts.fromDownload && !opts.fromVisionHop && !opts.fromDebug) { if (!opts.fromAutoCritique && !opts.fromDownload && !opts.fromVisionHop && !opts.fromDebug) {
state.lastUserParamIntent = userTextMentionsParams(text); state.lastUserParamIntent = userTextMentionsParams(text);
state.lastUserControlIntent = userTextMentionsControls(text); state.lastUserControlIntent = userTextMentionsControls(text);
state.pendingSilentGen = userAsksGenerate(text) || userAsksContinue(text) || isSameButAspectRequest(text); state.pendingSilentGen = userImpliesGenerate(text);
if (userAsksNoGenerate(text)) { if (userAsksNoGenerate(text)) {
state.pendingSilentGen = false; state.pendingSilentGen = false;
} }
@@ -8602,12 +8820,12 @@
const prose = extractPatch(reply).prose || reply; const prose = extractPatch(reply).prose || reply;
state.history.push({ role: 'assistant', content: prose, persona, pack }); state.history.push({ role: 'assistant', content: prose, persona, pack });
persistHistory(); persistHistory();
setBusyPhase(state.pendingSilentGen || userAsksGenerate(text) || userAsksContinue(text) ? 'silent_gen' : 'thinking'); setBusyPhase(state.pendingSilentGen || userImpliesGenerate(text) ? 'silent_gen' : 'thinking');
try { try {
await handleReplySideEffects(reply, civitaiResults, { await handleReplySideEffects(reply, civitaiResults, {
...opts, ...opts,
userText: text, userText: text,
userWantsGenerate: !!state.pendingSilentGen || userAsksGenerate(text) || userAsksContinue(text), userWantsGenerate: !!state.pendingSilentGen || userImpliesGenerate(text),
attachedSlotIds: visionSlots.map((s) => s.id), attachedSlotIds: visionSlots.map((s) => s.id),
}); });
} finally { } finally {
+3 -3
View File
@@ -6,7 +6,7 @@ window.SA = window.SA || {};
(function () { (function () {
const PATCH_KEYS = [ const PATCH_KEYS = [
'prompt', 'loras', 'width', 'height', 'steps', 'cfg', 'seed', 'sigma_shift', 'sampler', 'prompt', 'negative', 'loras', 'width', 'height', 'steps', 'cfg', 'seed', 'sigma_shift', 'sampler',
'actions', 'search_query', 'civitai_query', 'actions', 'search_query', 'civitai_query',
'use_init_image', 'clear_init_image', 'init_creativity', 'denoise', 'use_init_image', 'clear_init_image', 'init_creativity', 'denoise',
'use_mask_image', 'clear_mask_image', 'mask_blur', 'mask_grow', 'use_mask_image', 'clear_mask_image', 'mask_blur', 'mask_grow',
@@ -31,8 +31,8 @@ window.SA = window.SA || {};
return false; return false;
} }
const cardish = !!(obj.kind || obj.triggers || obj.when || obj.prompt_hint); const cardish = !!(obj.kind || obj.triggers || obj.when || obj.prompt_hint);
const genish = !!(obj.prompt != null || obj.loras || obj.actions || obj.width || obj.height const genish = !!(obj.prompt != null || obj.negative != null || obj.loras || obj.actions
|| obj.steps || obj.cfg || obj.aspect || obj.seed != null || obj.width || obj.height || obj.steps || obj.cfg || obj.aspect || obj.seed != null
|| obj.search_query || obj.civitai_query || obj.look_at || obj.controls); || obj.search_query || obj.civitai_query || obj.look_at || obj.controls);
if (cardish && !genish && (obj.name || obj.triggers || obj.when)) { if (cardish && !genish && (obj.name || obj.triggers || obj.when)) {
return true; return true;
+2 -2
View File
@@ -11,7 +11,7 @@ public partial class SwarmAssistentExtension
static readonly string[] PatchKeys = static readonly string[] PatchKeys =
[ [
"prompt", "loras", "width", "height", "steps", "cfg", "seed", "sigma_shift", "sampler", "prompt", "negative", "loras", "width", "height", "steps", "cfg", "seed", "sigma_shift", "sampler",
"actions", "search_query", "civitai_query", "actions", "search_query", "civitai_query",
"use_init_image", "clear_init_image", "init_creativity", "denoise", "use_init_image", "clear_init_image", "init_creativity", "denoise",
"use_mask_image", "clear_mask_image", "mask_blur", "mask_grow", "use_mask_image", "clear_mask_image", "mask_blur", "mask_grow",
@@ -66,7 +66,7 @@ public partial class SwarmAssistentExtension
return false; return false;
} }
bool cardish = HasValue(obj, "kind") || HasValue(obj, "triggers") || HasValue(obj, "when") || HasValue(obj, "prompt_hint"); bool cardish = HasValue(obj, "kind") || HasValue(obj, "triggers") || HasValue(obj, "when") || HasValue(obj, "prompt_hint");
bool genish = HasValue(obj, "prompt") || HasValue(obj, "loras") || HasValue(obj, "actions") bool genish = HasValue(obj, "prompt") || HasValue(obj, "negative") || HasValue(obj, "loras") || HasValue(obj, "actions")
|| HasValue(obj, "width") || HasValue(obj, "height") || HasValue(obj, "steps") || HasValue(obj, "cfg") || HasValue(obj, "width") || HasValue(obj, "height") || HasValue(obj, "steps") || HasValue(obj, "cfg")
|| HasValue(obj, "aspect") || HasValue(obj, "seed") || HasValue(obj, "search_query") || HasValue(obj, "aspect") || HasValue(obj, "seed") || HasValue(obj, "search_query")
|| HasValue(obj, "civitai_query") || HasValue(obj, "look_at") || HasValue(obj, "controls"); || HasValue(obj, "civitai_query") || HasValue(obj, "look_at") || HasValue(obj, "controls");
+5 -4
View File
@@ -27,7 +27,7 @@ Never write a “JSON Patch” section in prose without an actual fenced ```json
- Use only LoRA/checkpoint **names** from `selected_loras` / `available_loras` (or Civitai hop results). `selected_loras` = currently enabled. Prefer listed `triggers` / `trigger_phrase` / `blurb` — **never invent**. - Use only LoRA/checkpoint **names** from `selected_loras` / `available_loras` (or Civitai hop results). `selected_loras` = currently enabled. Prefer listed `triggers` / `trigger_phrase` / `blurb` — **never invent**.
- Rich entries (blurbs/triggers) are selected + top krea-likely. Name-only rows need `list_inventory` + `inventory_query` before you rely on them. - Rich entries (blurbs/triggers) are selected + top krea-likely. Name-only rows need `list_inventory` + `inventory_query` before you rely on them.
- `memory_hits` may be truncated (`truncated: true`) — use `memory_get` for the full text. - `memory_hits` may be truncated (`truncated: true`) — use `memory_get` for the full text.
- `has_vision_image` true means a real board frame exists. `images_in_request` true means JPEG bytes are in **this** request. If you need to see a frame and `images_in_request` is false, emit `look_at` first — do not invent what the image looks like. - `has_vision_image` true means a real board frame exists. `images_in_request` true means JPEG bytes are in **this** request. **Do not** `look_at` just because a frame exists. Emit `look_at` only when you cannot continue without pixels (user asked to look/critique/describe/compare, or a defect you cannot infer from the prompt). Never invent what the image looks like. A new Generate / «ещё» / prompt edit does **not** need vision.
- Prefer `krea_likely` / Krea architecture; ignore FLUX/SDXL. Respect current params unless asked or pack is `form_params`. - Prefer `krea_likely` / Krea architecture; ignore FLUX/SDXL. Respect current params unless asked or pack is `form_params`.
- Init/inpaint flags and `image_slots` are in the JSON. Extra pack fields are documented in the active pack. - Init/inpaint flags and `image_slots` are in the JSON. Extra pack fields are documented in the active pack.
@@ -45,6 +45,7 @@ Never write a “JSON Patch” section in prose without an actual fenced ```json
```json ```json
{ {
"prompt": "A fluffy red fox in fresh powder snow, soft morning light, 85mm f/2.8…", "prompt": "A fluffy red fox in fresh powder snow, soft morning light, 85mm f/2.8…",
"negative": "bad quality, worst quality",
"loras": [{"name": "exact_name_from_list", "weight": 0.8, "triggers": ["listed_trigger"]}], "loras": [{"name": "exact_name_from_list", "weight": 0.8, "triggers": ["listed_trigger"]}],
"aspect": "16:9", "aspect": "16:9",
"actions": ["generate"], "actions": ["generate"],
@@ -69,7 +70,7 @@ Several options in one ask (still one fence):
### Patch rules ### Patch rules
- Omit unchanged keys. Prefer omitting Exact-matching `steps`/`cfg`/`sigma_shift`/`aspect`. - Omit unchanged **params** (`steps`/`cfg`/`sigma_shift`/`aspect`). For Generate, still include **`negative`**: create if live is empty, lightly supplement if the scene needs a specific omit, or echo the live/Exact box unchanged — never drop it.
- Prefer omitting Exact-matching **`controls`** (e.g. do not re-emit `"horny": 35` / `"preference_bias": 0.35` when unchanged) — echoing defaults in a Generate patch resets the UI sliders. - Prefer omitting Exact-matching **`controls`** (e.g. do not re-emit `"horny": 35` / `"preference_bias": 0.35` when unchanged) — echoing defaults in a Generate patch resets the UI sliders.
- `loras` replaces the full intended set for Apply. Prefer `aspect` over raw width/height. - `loras` replaces the full intended set for Apply. Prefer `aspect` over raw width/height.
- Optional keys (seed, vary, init/mask, creativity/sliders, pack, **controls**, persona authoring, search/memory queries, **`variants`**) — use when needed; packs list the ones for that mode. - Optional keys (seed, vary, init/mask, creativity/sliders, pack, **controls**, persona authoring, search/memory queries, **`variants`**) — use when needed; packs list the ones for that mode.
@@ -78,7 +79,7 @@ Several options in one ask (still one fence):
### Actions / hops ### Actions / hops
- `"generate"` — Apply + start generation when the user wants a new image. Auto-Generate is a **UI checkbox** (Settings → Поведение); the model cannot toggle it — emit `actions:["generate"]` instead. - `"generate"` — Apply + start generation. Emit this whenever the turn is a **new/updated frame** (scene brief, “her in the studio”, pose/light/wardrobe change, «ещё», variants) — not only if they typed «генерируй». Omit `generate` only for: Pure Q&A, remember/save prompt, look/critique without a redraw, describe_ref without a gen ask, Cards/authoring.
- If the user only asks to **remember / save** a prompt as base/template («запомни», «как базовый промпт», «шаблон») and did **not** ask for a new image: **omit** `actions: ["generate"]`, do **not** `look_at`. Prefer `memory_upsert` (kind `note`, key like `base_prompt`) or a short ack; you may echo `prompt` in the patch only to sync the SwarmUI box — the UI will not Generate on remember turns. - If the user only asks to **remember / save** a prompt as base/template («запомни», «как базовый промпт», «шаблон») and did **not** ask for a new image: **omit** `actions: ["generate"]`, do **not** `look_at`. Prefer `memory_upsert` (kind `note`, key like `base_prompt`) or a short ack; you may echo `prompt` in the patch only to sync the SwarmUI box — the UI will not Generate on remember turns.
- `"search_civitai"` + **required** short `search_query` — Civitai hop (user Confirms downloads). Without `search_query` the hop is skipped (never search the whole user message). - `"search_civitai"` + **required** short `search_query` — Civitai hop (user Confirms downloads). Without `search_query` the hop is skipped (never search the whole user message).
- `"interrupt"` — stop generation. - `"interrupt"` — stop generation.
@@ -88,5 +89,5 @@ Several options in one ask (still one fence):
- `"persona_read"` — load lore shelves not in always-on identity (e.g. `roleplay` / `craft` / `humor` when NSFW tone or craft detail needs them). - `"persona_read"` — load lore shelves not in always-on identity (e.g. `roleplay` / `craft` / `humor` when NSFW tone or craft detail needs them).
- `"memory_upsert"` / `"memory_forget"` / `"user_pref_upsert"` / `"user_pref_forget"` — writes. - `"memory_upsert"` / `"memory_forget"` / `"user_pref_upsert"` / `"user_pref_forget"` — writes.
- `"persona_clone"` / `"persona_write"` / `"persona_switch"` — `author_persona` only. Never `"persona_delete"`. - `"persona_clone"` / `"persona_write"` / `"persona_switch"` — `author_persona` only. Never `"persona_delete"`.
- `look_at: ["generate"|"ref1"|…]` — vision hop (JPEG arrives on the follow-up). - `look_at: ["generate"|"ref1"|…]` — vision hop (JPEG arrives on the follow-up). Opt-in: user asked, or you truly need pixels. Do not pair `look_at` with `actions:["generate"]` on a normal write turn (that stares at the *old* frame and delays the new one).
- Pure Q&A: omit the JSON patch. - Pure Q&A: omit the JSON patch.
+3 -2
View File
@@ -4,7 +4,8 @@
"steps": 8, "steps": 8,
"cfg": 1, "cfg": 1,
"sigma_shift": 1.15, "sigma_shift": 1.15,
"images": 1 "images": 1,
"negative": "bad quality, worst quality"
}, },
"profiles": { "profiles": {
"turbo": { "turbo": {
@@ -31,7 +32,7 @@
"facts": { "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.", "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.",
"prompt_language": "Chat model always prep's Generate prompt for Krea: English natural prose for Qwen3-VL, structured (subject→pose→setting→camera→light). Chat may be RU; never leave Russian or thin drafts in patch.prompt.", "prompt_language": "Chat model always prep's Generate prompt for Krea: English natural prose for Qwen3-VL, structured (subject→pose→setting→camera→light). Chat may be RU; never leave Russian or thin drafts in patch.prompt.",
"negatives": "Negative prompts are nearly useless with Qwen3-VL. Prefer positives (sharp focus, empty street) over no blur / no people. Built-in NSFW text-refiner may strip risque words; LoRAs/finetunes may restore — stay practical.", "negatives": "Qwen3-VL negatives are weak — still keep a short Swarm negative box. Prefer positives in `prompt`; for `negative`: create if live is empty (Exact generation.negative), lightly supplement if the scene needs a specific omit, or echo live unchanged. Never drop/clear the box on Generate. Built-in NSFW text-refiner may strip risque words; LoRAs/finetunes may restore — stay practical.",
"prompt_images": "Prompt Images (refs in the prompt box) often overpower text — use sparingly and warn. Init Image = structure (img2img). Mask = local fix. They are not interchangeable. Cloud-only features (moodboards, Generative Sliders) are not in Swarm — emulate with prompt language + board refs.", "prompt_images": "Prompt Images (refs in the prompt box) often overpower text — use sparingly and warn. Init Image = structure (img2img). Mask = local fix. They are not interchangeable. Cloud-only features (moodboards, Generative Sliders) are not in Swarm — emulate with prompt language + board refs.",
"turbo": "Krea 2 Turbo defaults live in exact.generation / exact.profiles.turbo. Never use CFG 0 — broken output. Side ~1024 (1284096 OK).", "turbo": "Krea 2 Turbo defaults live in exact.generation / exact.profiles.turbo. Never use CFG 0 — broken output. Side ~1024 (1284096 OK).",
"raw": "Krea 2 RAW/Base: prefer exact.profiles.raw when checkpoint name/title looks like RAW (not Turbo). If a turbo LoRA exists, weight ~0.6 for photoreal (1.0 ≈ full turbo). Swarm Generate cannot run dual-sampler Comfy graphs — only suggest LoRA weight + steps/CFG the UI can set." "raw": "Krea 2 RAW/Base: prefer exact.profiles.raw when checkpoint name/title looks like RAW (not Turbo). If a turbo LoRA exists, weight ~0.6 for photoreal (1.0 ≈ full turbo). Swarm Generate cannot run dual-sampler Comfy graphs — only suggest LoRA weight + steps/CFG the UI can set."
+4 -4
View File
@@ -4,8 +4,8 @@ Default all-rounder. Handle this turn from the user message + live context — d
## What you cover here ## What you cover here
- **Write / improve prompt** → patch with `prompt` (+ `loras` when useful) and `actions: ["generate"]` when they want a new image. - **Write / improve prompt** → patch with `prompt` + `negative` (+ `loras` when useful) and `actions: ["generate"]` when this turn is a frame (scene, edit, «ещё») — **not** only if they typed «генерируй». Do **not** `look_at` the last frame first. `negative`: create / supplement / echo live — do not omit on Generate.
- **Light critique / improve last frame** → short notes + better `prompt`; use `look_at: ["generate"]` if `has_vision_image` and you have not seen the JPEG this turn. - **Light critique / improve last frame** → only when the user asks to look / critique / describe the picture. Then `look_at: ["generate"]` if `images_in_request` is false. Otherwise edit the prompt from text; `has_vision_image` alone is not a reason to look.
- **Scene / mood** → compose direction into the prompt (same patch rules). - **Scene / mood** → compose direction into the prompt (same patch rules).
- **Params** → only when they ask (steps/CFG/aspect/seed); omit Exact-matching numbers otherwise. - **Params** → only when they ask (steps/CFG/aspect/seed); omit Exact-matching numbers otherwise.
- **Inpaint / img2img** → set init/mask fields when they ask and flags allow; else say what is missing. - **Inpaint / img2img** → set init/mask fields when they ask and flags allow; else say what is missing.
@@ -26,6 +26,6 @@ Otherwise **stay in ordinary** and just do the work.
## Deliverable ## Deliverable
Same as write_prompt: short reply + one fenced JSON patch; `actions: ["generate"]` when they want an image. Same as write_prompt: short reply + one fenced JSON patch; `actions: ["generate"]` when the turn is a frame (context is enough — they do not have to say «генерируй»).
«давай дальше» / next frame = new English `prompt` + `actions:["generate"]` in the **same** turn — never leave an empty `### JSON Patch` header. Chat may be RU; **Generate `prompt` is always EN** (skill `prompting`). «давай дальше» / next frame = new English `prompt` + `negative` (echo live if unchanged) + `actions:["generate"]` in the **same** turn — never leave an empty `### JSON Patch` header. Chat may be RU; **Generate `prompt` is always EN** (skill `prompting`).
Several options in one ask → `variants` (24 partial patches with `label`); still one fence, still STOP after it. Several options in one ask → `variants` (24 partial patches with `label`); still one fence, still STOP after it.
+2 -2
View File
@@ -5,8 +5,8 @@ Goal: craft or improve a **Krea 2** prompt that will generate well on Turbo (loc
## Deliverable ## Deliverable
- Brief note of what you changed. - Brief note of what you changed.
- JSON patch with at least `prompt`, and `loras` when relevant. - JSON patch with at least `prompt` and `negative` (create / supplement / echo live), and `loras` when relevant.
- `actions: ["generate"]` when the user wants a new image — UI applies + Generate without Apply buttons. - `actions: ["generate"]` when this turn is a new/updated image (scene brief, edit, «ещё») — they do **not** have to type «генерируй». Skip generate only for Q&A / remember / look-only. Do **not** `look_at` unless they asked to see/critique the last frame.
- Prefer Exact Turbo defaults / `recommended_params`. Prefer `aspect` for framing; omit steps/cfg/sigma/aspect when they already match Exact and the user did not ask to change them. - Prefer Exact Turbo defaults / `recommended_params`. Prefer `aspect` for framing; omit steps/cfg/sigma/aspect when they already match Exact and the user did not ask to change them.
- Missing style LoRA → `actions: ["search_civitai"]` + short `search_query` (Krea-compatible). - Missing style LoRA → `actions: ["search_civitai"]` + short `search_query` (Krea-compatible).
- User wants several options (оба / варианты / разный свет) → `variants: [{label, prompt|aspect|…}, …]` (24). Base keys inherit; each item overrides only its diffs. Still one fence. - User wants several options (оба / варианты / разный свет) → `variants: [{label, prompt|aspect|…}, …]` (24). Base keys inherit; each item overrides only its diffs. Still one fence.
+2 -1
View File
@@ -9,12 +9,13 @@ The **chat model** prepares the Generate-box text for **Krea 2** (Qwen3-VL). Do
3. **Order (front-load):** 3. **Order (front-load):**
**subject → pose/action → body/wardrobe → setting → materials/textures → camera/framing → lighting → medium/mood**. **subject → pose/action → body/wardrobe → setting → materials/textures → camera/framing → lighting → medium/mood**.
4. Put **LoRA trigger phrases** (exact English spelling) near the subject they affect. 4. Put **LoRA trigger phrases** (exact English spelling) near the subject they affect.
5. Prefer **positives** over negatives (Qwen negatives are weak). 5. **`negative` on every Generate** — create if live is empty (Exact `generation.negative`), supplement if the scene needs a specific omit, or echo live unchanged. Put “no blur / no people” ideas as positives in `prompt` instead of stuffing the negative box. Never clear `negative`.
6. Short ideas: expand into a full Krea paragraph. Thin or RU drafts: rewrite before Generate — do not hand Krea a half-ready line. 6. Short ideas: expand into a full Krea paragraph. Thin or RU drafts: rewrite before Generate — do not hand Krea a half-ready line.
## Prep checklist (before `actions:["generate"]`) ## Prep checklist (before `actions:["generate"]`)
- English only in `prompt` - English only in `prompt`
- `negative` present (new / supplemented / echoed — not omitted)
- Subject and action clear in the first sentence - Subject and action clear in the first sentence
- Wardrobe / body / setting concrete - Wardrobe / body / setting concrete
- Camera + lighting present - Camera + lighting present
+5 -1
View File
@@ -2,7 +2,11 @@
SwarmUI extension for **collaborative Krea 2** prompting via **Ollama**: chat + board (Generate | Refs tabs), LoRA chips, **persona presets** (`Config/personas/`), **About the user** prefs + craft 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/`), **About the user** prefs + craft vector memory, model cards with Civitai fetch, img2img/inpaint, slash commands, auto Generate.
**Version 0.11.3**Stream no longer aborts on weak JSON fences (so prompts/skill_load can finish); `num_predict` 3072. Builds on 0.11.2 Krea prep. **Version 0.11.6**Generate from context, not only «генерируй»: scene briefs / edits / «ещё» emit `actions:["generate"]`; client injects it if the model forgets. Builds on 0.11.5.
**Version 0.11.5** — Generate always carries a negative: model creates / supplements / echoes live; client pass-through if omitted. Builds on 0.11.4.
**Version 0.11.4** — Vision is opt-in: look via «Посмотри результат» / `/look`, or when the model truly needs pixels. No auto look_at / auto-critique after every Generate (checkboxes stay, default off). Builds on 0.11.3.
**Version 0.11.2** — Before Krea Generate, chat model maximally preps the prompt (EN + structure); skip prep hop only if already Krea-ready English. Builds on 0.11.1. **Version 0.11.2** — Before Krea Generate, chat model maximally preps the prompt (EN + structure); skip prep hop only if already Krea-ready English. Builds on 0.11.1.
+1 -1
View File
@@ -36,7 +36,7 @@ public partial 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.11.3"; Version = "0.11.6";
Tags = ["tabs", "ui", "llm", "ollama", "krea", "inpaint", "memory"]; Tags = ["tabs", "ui", "llm", "ollama", "krea", "inpaint", "memory"];
} }
+2 -2
View File
@@ -168,10 +168,10 @@
<div class="sa-settings-panes"> <div class="sa-settings-panes">
<div class="sa-spane" data-spane="behavior"> <div class="sa-spane" data-spane="behavior">
<p class="sa-settings-hint">Автодействия после ответа модели и скилы текущей личности.</p> <p class="sa-settings-hint">Автодействия после ответа модели и скилы текущей личности.</p>
<label class="sa-check" title="После Generate шлёт look_at с JPEG кадра (не каждый чат). Если включена авто-критика — она уже подставляет кадр."><input type="checkbox" id="sa_auto_vision" /> После Generate — look_at кадра</label> <label class="sa-check" title="По умолчанию выкл. Кадр смотрит по кнопке «Посмотри результат», /look, или когда модель сама шлёт look_at. Эта галка — после каждого Generate."><input type="checkbox" id="sa_auto_vision" /> После Generate — look_at кадра</label>
<label class="sa-check"><input type="checkbox" id="sa_auto_apply" checked /> Авто-применять патч</label> <label class="sa-check"><input type="checkbox" id="sa_auto_apply" checked /> Авто-применять патч</label>
<label class="sa-check"><input type="checkbox" id="sa_auto_generate" checked /> Авто-Generate после патча</label> <label class="sa-check"><input type="checkbox" id="sa_auto_generate" checked /> Авто-Generate после патча</label>
<label class="sa-check"><input type="checkbox" id="sa_auto_critique" /> Авто-критика после Generate</label> <label class="sa-check" title="По умолчанию выкл. Критика кадра — кнопка «Посмотри результат» или /look. Галка шлёт JPEG после каждого Generate."><input type="checkbox" id="sa_auto_critique" /> Авто-критика после Generate</label>
<label class="sa-check" title="Выгружает чат-модель перед Generate (keep_alive:0). Для VL 7B обратная загрузка часто 1–2 мин — включай только если Generate падает по VRAM."><input type="checkbox" id="sa_park_llm" /> Park LLM перед Generate (VRAM)</label> <label class="sa-check" title="Выгружает чат-модель перед Generate (keep_alive:0). Для VL 7B обратная загрузка часто 1–2 мин — включай только если Generate падает по VRAM."><input type="checkbox" id="sa_park_llm" /> Park LLM перед Generate (VRAM)</label>
<label class="sa-check sa-danger" title="Опасно — скачивает без Confirm"><input type="checkbox" id="sa_auto_download" /> Авто-скачивание Civitai (выкл)</label> <label class="sa-check sa-danger" title="Опасно — скачивает без Confirm"><input type="checkbox" id="sa_auto_download" /> Авто-скачивание Civitai (выкл)</label>
<div class="sa-skills-label">Скилы (процедуры)</div> <div class="sa-skills-label">Скилы (процедуры)</div>