diff --git a/Assets/assistent.js b/Assets/assistent.js index e12fdf0..182e699 100644 --- a/Assets/assistent.js +++ b/Assets/assistent.js @@ -16,6 +16,8 @@ const LS_AUTO_APPLY = 'swarm_assistent_auto_apply'; const LS_AUTO_GENERATE = 'swarm_assistent_auto_generate'; 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'; /** When '1', unload chat LLM before Generate (frees VRAM; VL reload can take 1–2 min). Default off. */ const LS_PARK_LLM = 'swarm_assistent_park_llm'; @@ -89,7 +91,7 @@ /debug ask — то же + короткий ответ модели /why — сразу /debug ask /gen — Generate сейчас -/look generate|refN — прикрепить окно к vision +/look generate|refN — показать кадр модели (vision) /init /mask /clear — Init / Mask / Clear Init /interrupt — остановить генерацию /aspect 16:9 — размер из таблицы 1K @@ -656,6 +658,43 @@ 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 } = {}) { if (raw == null) { return true; @@ -875,7 +914,7 @@ /** When the model wrote ### JSON Patch with no fence — build Apply+Generate from prose / last patch. */ function synthesizePatchAfterEmptyFence(reply, userText, opts = {}) { const wants = !!(opts.userWantsGenerate || state.pendingSilentGen - || userAsksGenerate(userText) || userAsksContinue(userText)); + || userAsksGenerate(userText) || userAsksContinue(userText) || userImpliesGenerate(userText)); const missing = replyMissingJsonPatch(reply); if (!wants && !missing) { return null; @@ -930,6 +969,14 @@ if (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)) { 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' + '- Expand thin ideas; fix anti-patterns; one coherent scene.\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: ' - + `${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' + `SOURCE:\n${patch.prompt}` ); @@ -965,6 +1013,9 @@ ...base, ...effective, 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) ? effective.actions : (base.actions || ['generate']), @@ -990,6 +1041,137 @@ ).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) { if (!patch || typeof patch !== 'object') { return patch; @@ -1204,6 +1386,12 @@ setVal(batchId, String(batch)); } } + if (!liveNegativePrompt()) { + const neg = exactDefaultNegative(); + if (neg) { + setNegativePrompt(neg); + } + } } function shouldSkipSessionRollback(key, patchValue) { @@ -2274,6 +2462,9 @@ promptBox.dispatchEvent(new Event('change', { bubbles: true })); } 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. if (params.width != null) { setVal('input_width', String(params.width)); @@ -3965,9 +4156,6 @@ box.dispatchEvent(new Event('input', { bubbles: true })); box.dispatchEvent(new Event('change', { bubbles: true })); } - if (patch.negative != null) { - setVal('input_negativeprompt', patch.negative); - } if (Array.isArray(patch.loras)) { for (const l of patch.loras) { 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) { try { @@ -4611,6 +4806,7 @@ await applyPatch(job.patch, 'all'); syncLiveParamsBar(); } + ensureNegativeForGenerate(job.patch); if (epoch !== state.chatEpoch) { break; @@ -5332,6 +5528,18 @@ } 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 model = localStorage.getItem(LS_MODEL); const pack = localStorage.getItem(LS_PACK); @@ -7865,19 +8073,18 @@ } if (!effective && !fromVisionHop && !fromAutoCritique && !fromDebug && !opts.fromEmptyPatchRetry - && replyMissingJsonPatch(reply) - && (opts.userWantsGenerate || state.pendingSilentGen || userAsksContinue(opts.userText || ''))) { - appendSystemNote('Патч пустой — прошу модель дописать JSON.'); + && userImpliesGenerate(opts.userText || '')) { + appendSystemNote('Нужен кадр — прошу JSON с prompt + generate.'); await sendChat({ skipSlash: true, skipAutoPack: true, fromEmptyPatchRetry: true, userWantsGenerate: true, forcedUserText: - 'Ты написал «JSON Patch» без fenced ```json```. ' - + 'Сейчас ответь ТОЛЬКО одним fenced JSON объектом: ' - + '{"prompt":"","actions":["generate"]}. ' - + 'prompt — только английский (skill prompting). Без прозы, без ### заголовков.', + 'Пользователь уже просит кадр (это следует из сообщения, даже без слова «генерируй»). ' + + 'Ответь ТОЛЬКО одним fenced JSON: ' + + '{"prompt":"","negative":"","actions":["generate"]}. ' + + 'prompt — английский. Без прозы, без «скажи сгенерируй».', }); return; } @@ -7905,7 +8112,17 @@ 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 || []); if (hopped) { return; @@ -7917,6 +8134,7 @@ return; } const wantsGen = !suppressGen && !!(opts.userWantsGenerate || state.pendingSilentGen + || userImpliesGenerate(opts.userText || '') || (Array.isArray(effective?.actions) && effective.actions.map(String).includes('generate'))); const willGen = !!(effective && !fromAutoCritique && !suppressGen && (wantsGen || $('sa_auto_generate')?.checked)); @@ -8385,7 +8603,7 @@ if (!opts.fromAutoCritique && !opts.fromDownload && !opts.fromVisionHop && !opts.fromDebug) { state.lastUserParamIntent = userTextMentionsParams(text); state.lastUserControlIntent = userTextMentionsControls(text); - state.pendingSilentGen = userAsksGenerate(text) || userAsksContinue(text) || isSameButAspectRequest(text); + state.pendingSilentGen = userImpliesGenerate(text); if (userAsksNoGenerate(text)) { state.pendingSilentGen = false; } @@ -8602,12 +8820,12 @@ const prose = extractPatch(reply).prose || reply; state.history.push({ role: 'assistant', content: prose, persona, pack }); persistHistory(); - setBusyPhase(state.pendingSilentGen || userAsksGenerate(text) || userAsksContinue(text) ? 'silent_gen' : 'thinking'); + setBusyPhase(state.pendingSilentGen || userImpliesGenerate(text) ? 'silent_gen' : 'thinking'); try { await handleReplySideEffects(reply, civitaiResults, { ...opts, userText: text, - userWantsGenerate: !!state.pendingSilentGen || userAsksGenerate(text) || userAsksContinue(text), + userWantsGenerate: !!state.pendingSilentGen || userImpliesGenerate(text), attachedSlotIds: visionSlots.map((s) => s.id), }); } finally { diff --git a/Assets/assistent.patch.js b/Assets/assistent.patch.js index c8650c8..dca8974 100644 --- a/Assets/assistent.patch.js +++ b/Assets/assistent.patch.js @@ -6,7 +6,7 @@ window.SA = window.SA || {}; (function () { 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', 'use_init_image', 'clear_init_image', 'init_creativity', 'denoise', 'use_mask_image', 'clear_mask_image', 'mask_blur', 'mask_grow', @@ -31,8 +31,8 @@ window.SA = window.SA || {}; return false; } const cardish = !!(obj.kind || obj.triggers || obj.when || obj.prompt_hint); - const genish = !!(obj.prompt != null || obj.loras || obj.actions || obj.width || obj.height - || obj.steps || obj.cfg || obj.aspect || obj.seed != null + const genish = !!(obj.prompt != null || obj.negative != null || obj.loras || obj.actions + || obj.width || obj.height || obj.steps || obj.cfg || obj.aspect || obj.seed != null || obj.search_query || obj.civitai_query || obj.look_at || obj.controls); if (cardish && !genish && (obj.name || obj.triggers || obj.when)) { return true; diff --git a/AssistentPatch.cs b/AssistentPatch.cs index 8175f19..1639fd6 100644 --- a/AssistentPatch.cs +++ b/AssistentPatch.cs @@ -11,7 +11,7 @@ public partial class SwarmAssistentExtension 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", "use_init_image", "clear_init_image", "init_creativity", "denoise", "use_mask_image", "clear_mask_image", "mask_blur", "mask_grow", @@ -66,7 +66,7 @@ public partial class SwarmAssistentExtension return false; } 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, "aspect") || HasValue(obj, "seed") || HasValue(obj, "search_query") || HasValue(obj, "civitai_query") || HasValue(obj, "look_at") || HasValue(obj, "controls"); diff --git a/Config/_base/core/core.md b/Config/_base/core/core.md index de373e4..a34b0e7 100644 --- a/Config/_base/core/core.md +++ b/Config/_base/core/core.md @@ -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**. - 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. -- `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`. - 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 { "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"]}], "aspect": "16:9", "actions": ["generate"], @@ -69,7 +70,7 @@ Several options in one ask (still one fence): ### 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. - `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. @@ -78,7 +79,7 @@ Several options in one ask (still one fence): ### 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. - `"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. @@ -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). - `"memory_upsert"` / `"memory_forget"` / `"user_pref_upsert"` / `"user_pref_forget"` — writes. - `"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. diff --git a/Config/_base/exact.json b/Config/_base/exact.json index 86d07fe..6e8aea1 100644 --- a/Config/_base/exact.json +++ b/Config/_base/exact.json @@ -4,7 +4,8 @@ "steps": 8, "cfg": 1, "sigma_shift": 1.15, - "images": 1 + "images": 1, + "negative": "bad quality, worst quality" }, "profiles": { "turbo": { @@ -31,7 +32,7 @@ "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.", "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.", "turbo": "Krea 2 Turbo defaults live in exact.generation / exact.profiles.turbo. Never use CFG 0 — broken output. Side ~1024 (128–4096 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." diff --git a/Config/_base/packs/ordinary.md b/Config/_base/packs/ordinary.md index 84d8f5e..d09003e 100644 --- a/Config/_base/packs/ordinary.md +++ b/Config/_base/packs/ordinary.md @@ -4,8 +4,8 @@ Default all-rounder. Handle this turn from the user message + live context — d ## What you cover here -- **Write / improve prompt** → patch with `prompt` (+ `loras` when useful) and `actions: ["generate"]` when they want a new image. -- **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. +- **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** → 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). - **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. @@ -26,6 +26,6 @@ Otherwise **stay in ordinary** and just do the work. ## Deliverable -Same as write_prompt: short reply + one fenced JSON patch; `actions: ["generate"]` when they want an image. -«давай дальше» / 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`). +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` + `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` (2–4 partial patches with `label`); still one fence, still STOP after it. diff --git a/Config/_base/packs/write_prompt.md b/Config/_base/packs/write_prompt.md index e6a7384..ff12abb 100644 --- a/Config/_base/packs/write_prompt.md +++ b/Config/_base/packs/write_prompt.md @@ -5,8 +5,8 @@ Goal: craft or improve a **Krea 2** prompt that will generate well on Turbo (loc ## Deliverable - Brief note of what you changed. -- JSON patch with at least `prompt`, and `loras` when relevant. -- `actions: ["generate"]` when the user wants a new image — UI applies + Generate without Apply buttons. +- JSON patch with at least `prompt` and `negative` (create / supplement / echo live), and `loras` when relevant. +- `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. - Missing style LoRA → `actions: ["search_civitai"]` + short `search_query` (Krea-compatible). - User wants several options (оба / варианты / разный свет) → `variants: [{label, prompt|aspect|…}, …]` (2–4). Base keys inherit; each item overrides only its diffs. Still one fence. diff --git a/Config/_base/skills/prompting.md b/Config/_base/skills/prompting.md index 30caf38..649b583 100644 --- a/Config/_base/skills/prompting.md +++ b/Config/_base/skills/prompting.md @@ -9,12 +9,13 @@ The **chat model** prepares the Generate-box text for **Krea 2** (Qwen3-VL). Do 3. **Order (front-load):** **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. -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. ## Prep checklist (before `actions:["generate"]`) - English only in `prompt` +- `negative` present (new / supplemented / echoed — not omitted) - Subject and action clear in the first sentence - Wardrobe / body / setting concrete - Camera + lighting present diff --git a/README.md b/README.md index b57bbe8..1f16b1a 100644 --- a/README.md +++ b/README.md @@ -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. -**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. diff --git a/SwarmAssistentExtension.cs b/SwarmAssistentExtension.cs index 39baa05..f80a09e 100644 --- a/SwarmAssistentExtension.cs +++ b/SwarmAssistentExtension.cs @@ -36,7 +36,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.11.3"; + Version = "0.11.6"; Tags = ["tabs", "ui", "llm", "ollama", "krea", "inpaint", "memory"]; } diff --git a/Tabs/Text2Image/Assistent.html b/Tabs/Text2Image/Assistent.html index 2f7c1e4..18cee22 100644 --- a/Tabs/Text2Image/Assistent.html +++ b/Tabs/Text2Image/Assistent.html @@ -168,10 +168,10 @@

Автодействия после ответа модели и скилы текущей личности.

- + - +
Скилы (процедуры)