diff --git a/Assets/assistent.bundle.js b/Assets/assistent.bundle.js index 2a63bc2..8af3191 100644 --- a/Assets/assistent.bundle.js +++ b/Assets/assistent.bundle.js @@ -22,11 +22,26 @@ if (!t || userAsksNoGenerate(t)) { return false; } + if (/^как\s+(сгенерир|нарисов|сделать\s+(фото|кадр|изображ))/i.test(t)) { + return false; + } + if (/\bhow\s+to\s+(generate|draw|make|create)\b/i.test(t)) { + return false; + } if (/\b(generat(e|ion)|draw|render|make\s+(an?\s+)?image|run\s+generate)\b/i.test(t)) { return true; } + if (/\b(create|make)\s+(a\s+)?(photo|picture|image|shot|portrait)\b/i.test(t)) { + return true; + } + if (/\blet['']?s\s+(make|create|generate|draw)\s+(an?\s+)?(image|photo|picture|shot)\b/i.test(t)) { + return true; + } + if (/\bi\s+want\s+(an?\s+)?(image|photo|picture|shot)\b/i.test(t)) { + return true; + } return cyrTokenRe( - "\u0441\u0433\u0435\u043D\u0435\u0440[\u0430-\u044F\u0451]*|\u043D\u0430\u0440\u0438\u0441\u0443\u0439|\u043D\u0430\u0440\u0438\u0441\u0443\u0439\u0442\u0435|\u043D\u0430\u0440\u0438\u0441\u0443\u0435\u043C|\u0437\u0430\u043F\u0443\u0441\u0442\u0438\\s+\u0433\u0435\u043D\u0435\u0440[\u0430-\u044F\u0451]*|\u0441\u0434\u0435\u043B\u0430(\u0439|\u0435\u043C|\u0439\u0442\u0435)\\s+(\u043A\u0430\u0434\u0440|\u043A\u0430\u0440\u0442\u0438\u043D\u043A[\u0430-\u044F\u0451]*|\u0438\u0437\u043E\u0431\u0440\u0430\u0436[\u0430-\u044F\u0451]*)" + "\u0441\u0433\u0435\u043D\u0435\u0440[\u0430-\u044F\u0451]*|\u043D\u0430\u0440\u0438\u0441\u0443\u0439|\u043D\u0430\u0440\u0438\u0441\u0443\u0439\u0442\u0435|\u043D\u0430\u0440\u0438\u0441\u0443\u0435\u043C|\u0437\u0430\u043F\u0443\u0441\u0442\u0438\\s+\u0433\u0435\u043D\u0435\u0440[\u0430-\u044F\u0451]*|\u0441\u043E\u0437\u0434\u0430(\u0439|\u0439\u0442\u0435|\u0434\u0438\u043C|\u0434\u0438\u0442\u0435)\\s+(\u0444\u043E\u0442\u043E|\u0444\u043E\u0442\u043E\u0433\u0440\u0430\u0444[\u0430-\u044F\u0451]*|\u043A\u0430\u0440\u0442\u0438\u043D\u043A[\u0430-\u044F\u0451]*|\u0438\u0437\u043E\u0431\u0440\u0430\u0436[\u0430-\u044F\u0451]*|\u043A\u0430\u0434\u0440|\u043F\u043E\u0440\u0442\u0440\u0435\u0442|\u0441\u043D\u0438\u043C\u043E\u043A)|\u0441\u0434\u0435\u043B\u0430(\u0439|\u0439\u0442\u0435|\u0435\u043C|\u0439\u0442\u0435)\\s+(\u0444\u043E\u0442\u043E|\u0444\u043E\u0442\u043E\u0433\u0440\u0430\u0444[\u0430-\u044F\u0451]*|\u043A\u0430\u0434\u0440|\u043A\u0430\u0440\u0442\u0438\u043D\u043A[\u0430-\u044F\u0451]*|\u0438\u0437\u043E\u0431\u0440\u0430\u0436[\u0430-\u044F\u0451]*|\u043F\u043E\u0440\u0442\u0440\u0435\u0442|\u0441\u043D\u0438\u043C\u043E\u043A)|\u0434\u0430\u0432\u0430\u0439\\s+(\u0444\u043E\u0442\u043E|\u0444\u043E\u0442\u043E\u0433\u0440\u0430\u0444[\u0430-\u044F\u0451]*|\u043A\u0430\u0440\u0442\u0438\u043D\u043A[\u0430-\u044F\u0451]*|\u0438\u0437\u043E\u0431\u0440\u0430\u0436[\u0430-\u044F\u0451]*|\u043A\u0430\u0434\u0440|\u0441\u0433\u0435\u043D\u0435\u0440[\u0430-\u044F\u0451]*|\u043D\u0430\u0440\u0438\u0441\u0443[\u0430-\u044F\u0451]*)|\u0434\u0430\u0432\u0430\u0439\\s+\u0441\u0434\u0435\u043B\u0430\u0435\u043C\\s+(\u0444\u043E\u0442\u043E|\u0444\u043E\u0442\u043E\u0433\u0440\u0430\u0444[\u0430-\u044F\u0451]*|\u043A\u0430\u0440\u0442\u0438\u043D\u043A[\u0430-\u044F\u0451]*|\u0438\u0437\u043E\u0431\u0440\u0430\u0436[\u0430-\u044F\u0451]*|\u043A\u0430\u0434\u0440)|\u0445\u043E\u0447\u0443\\s+(\u0444\u043E\u0442\u043E|\u0444\u043E\u0442\u043E\u0433\u0440\u0430\u0444[\u0430-\u044F\u0451]*|\u043A\u0430\u0440\u0442\u0438\u043D\u043A[\u0430-\u044F\u0451]*|\u0438\u0437\u043E\u0431\u0440\u0430\u0436[\u0430-\u044F\u0451]*|\u043A\u0430\u0434\u0440)|\u043D\u0443\u0436\u043D[\u0430\u043E]?\\s+(\u0444\u043E\u0442\u043E|\u0444\u043E\u0442\u043E\u0433\u0440\u0430\u0444[\u0430-\u044F\u0451]*|\u043A\u0430\u0440\u0442\u0438\u043D\u043A[\u0430-\u044F\u0451]*|\u0438\u0437\u043E\u0431\u0440\u0430\u0436[\u0430-\u044F\u0451]*|\u043A\u0430\u0434\u0440)|\u0441\u0444\u043E\u0442\u043E\u0433\u0440\u0430\u0444\u0438\u0440\u0443\u0439|\u043E\u0442\u0440\u0438\u0441\u0443\u0439|\u043F\u0440\u0438\u0434\u0443\u043C\u0430\u0439\\s+(\u0444\u043E\u0442\u043E|\u043A\u0430\u0434\u0440|\u043A\u0430\u0440\u0442\u0438\u043D\u043A[\u0430-\u044F\u0451]*|\u0441\u0446\u0435\u043D\u0443|\u0438\u0437\u043E\u0431\u0440\u0430\u0436[\u0430-\u044F\u0451]*)|\u0441\u0433\u0435\u043D\u0435\u0440\u0438(\u0440\u0443\u0439|\u0440\u0443\u0439\u0442\u0435)\\s+(\u043C\u043D\u0435\\s+)?(\u0444\u043E\u0442\u043E|\u043A\u0430\u0434\u0440|\u043A\u0430\u0440\u0442\u0438\u043D\u043A[\u0430-\u044F\u0451]*|\u0438\u0437\u043E\u0431\u0440\u0430\u0436[\u0430-\u044F\u0451]*)" ).test(t); } function generateFlagOn(patch) { @@ -46,7 +61,7 @@ const vetoed = !opts.machineTurn && userAsksNoGenerate(userText); const modelAsked = generateFlagOn(patch); const hasPrompt = !!(String(patch?.prompt || "").trim() || String(opts.sessionPrompt || "").trim()); - const userAsked = !opts.machineTurn && userAsksGenerate(userText) && (modelAsked || hasPrompt); + const userAsked = !opts.machineTurn && userAsksGenerate(userText); const generate = !vetoed && !opts.fromAutoCritique && (userAsked || !!opts.userWantsGenerate); const hasLook = !!patch && (patch.look_at != null || patch.vision_from != null || patch.vision_slots != null); const look = !!(hasLook && !vetoed && !generate); @@ -774,8 +789,7 @@ const delta = normalizeDelta(patch) || {}; const vetoed = typeof vetoFn === "function" ? !!vetoFn(userText) : false; const modelAsked = patchWantsGenerate(delta); - const hasPrompt = !!(String(delta.prompt || "").trim() || String(sessionPrompt || "").trim()); - const userAsked = typeof askGenerateFn === "function" && !!askGenerateFn(userText) && (modelAsked || hasPrompt); + const userAsked = typeof askGenerateFn === "function" && !!askGenerateFn(userText); const generate = !vetoed && !fromAutoCritique && (userAsked || !!userWantsGenerate); const hasLook = delta.look_at != null || delta.vision_from != null || delta.vision_slots != null; const look = !!(hasLook && !generate && !vetoed); @@ -2468,8 +2482,7 @@ ${patch.prompt}`; } const vetoed = !isMachineTurn(opts) && userAsksNoGenerate2(userText); const modelAsked = !!(patch && (patch.generate === true || patch.generate === 1 || typeof patch.generate === "string" && /^(true|1|yes|on)$/i.test(patch.generate) || Array.isArray(patch.actions) && patch.actions.map(String).includes("generate"))); - const hasPrompt = !!(String(patch?.prompt || "").trim() || String(opts.sessionPrompt || "").trim()); - const userAsked = !isMachineTurn(opts) && userAsksGenerate2(userText) && (modelAsked || hasPrompt); + const userAsked = !isMachineTurn(opts) && userAsksGenerate2(userText); const generate = !vetoed && !opts.fromAutoCritique && (userAsked || !!opts.userWantsGenerate); const hasLook = !!patch && (patch.look_at != null || patch.vision_from != null || patch.vision_slots != null); const look = !!(hasLook && !vetoed && !generate); diff --git a/Config/_base/core/core.md b/Config/_base/core/core.md index a8cb9a4..2e7dd3a 100644 --- a/Config/_base/core/core.md +++ b/Config/_base/core/core.md @@ -70,7 +70,7 @@ Several options (still one fence): ### Commands -- `"generate": true` — you believe this turn is a draw request. The client **does not** start Swarm from this flag alone (small models often set it on chat questions). Set it **only** when the user asked to draw / generate / «нарисуй» / «сгенерируй». They can always click **Сгенерировать**. **Omit** the JSON for chat, opinions, Civitai/knowledge Q&A, remember/save, look-only. Legacy `actions:["generate"]` is the same advisory flag. +- `"generate": true` — mark a draw turn in JSON. The client **does not** start Swarm from this flag alone on chat/Q&A (models often dump it wrongly). **But** when the user clearly asked to create/draw — «создай фото», «нарисуй», «сгенерируй», «давай картинку», «make an image» — the **client auto-starts Generate** as soon as your JSON has a `prompt`. Do **not** tell them to click **Сгенерировать** on those turns: short ack + fenced `prompt` (+ optional params) is enough. **Omit** JSON for pure chat, opinions, Civitai/knowledge Q&A, remember/save, look-only. Legacy `actions:["generate"]` is the same advisory flag. - If the user says not to generate / only remember / only answer — **omit** `generate` and do not look. - `"look_at": ["generate"|"ref1"|…]` — vision hop (JPEG on follow-up). Use when you need pixels; do not pair with `generate` on the same normal write turn. - `"ask": ["settings"]` — request full settings dump (Exact + all fields). diff --git a/Config/_base/packs/ordinary.md b/Config/_base/packs/ordinary.md index fdc7e6f..e96c0c6 100644 --- a/Config/_base/packs/ordinary.md +++ b/Config/_base/packs/ordinary.md @@ -4,7 +4,7 @@ Default all-rounder. Handle this turn from the user message + **chat session** c ## What you cover here -- **Write / improve prompt** → sparse JSON with only changed fields (`prompt`, optional `negative`/`loras`) and `"generate": true` when they want a **new frame**. Chat / «нравится» / Q&A → prose only, **no** JSON. Do **not** `look_at` the last frame first. +- **Write / improve prompt** → sparse JSON with `prompt` (+ optional `negative`/`loras`). When they ask to **create/draw** («создай фото», «нарисуй», «сгенерируй», «давай картинку») the client **auto-Generates** after your JSON — do not ask them to click the button; include `"generate": true` if you like but the draw verbs alone are enough. Chat / «нравится» / Q&A → prose only, **no** JSON. Do **not** `look_at` the last frame first. - **Critique / look** → only when they ask to look / critique. Then `look_at: ["generate"]` if pixels are not already in the request. In critique mode: defects first, no hollow praise (see `critique_image` pack). - **Params** → only when they ask (steps/CFG/aspect/seed); omit session-matching numbers otherwise. Exception: if live steps/cfg disagree with Exact/`krea_profile` (e.g. 20/7 under turbo), include Exact profile numbers when generating. - **Inpaint / img2img** → set init/mask fields when they ask. diff --git a/SwarmAssistentExtension.cs b/SwarmAssistentExtension.cs index 5dd6d1a..6e00733 100644 --- a/SwarmAssistentExtension.cs +++ b/SwarmAssistentExtension.cs @@ -33,7 +33,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.15.15"; + Version = "0.15.16"; Tags = ["tabs", "ui", "llm", "ollama", "krea", "inpaint", "memory", "training", "heard", "qlora"]; } diff --git a/src/app.js b/src/app.js index 5a59ac4..452de17 100644 --- a/src/app.js +++ b/src/app.js @@ -1218,9 +1218,7 @@ import { DEFAULT_ASPECT_TABLE, applyAspectTableFromObject as mergeAspectTable } const modelAsked = !!(patch && (patch.generate === true || patch.generate === 1 || (typeof patch.generate === 'string' && /^(true|1|yes|on)$/i.test(patch.generate)) || (Array.isArray(patch.actions) && patch.actions.map(String).includes('generate')))); - const hasPrompt = !!(String(patch?.prompt || '').trim() || String(opts.sessionPrompt || '').trim()); - const userAsked = !isMachineTurn(opts) && userAsksGenerate(userText) - && (modelAsked || hasPrompt); + const userAsked = !isMachineTurn(opts) && userAsksGenerate(userText); const generate = !vetoed && !opts.fromAutoCritique && (userAsked || !!opts.userWantsGenerate); const hasLook = !!patch && (patch.look_at != null || patch.vision_from != null || patch.vision_slots != null); diff --git a/src/intent.js b/src/intent.js index b491c2a..7c8dc16 100644 --- a/src/intent.js +++ b/src/intent.js @@ -28,13 +28,36 @@ export function userAsksGenerate(text) { if (!t || userAsksNoGenerate(t)) { return false; } + if (/^как\s+(сгенерир|нарисов|сделать\s+(фото|кадр|изображ))/i.test(t)) { + return false; + } + if (/\bhow\s+to\s+(generate|draw|make|create)\b/i.test(t)) { + return false; + } if (/\b(generat(e|ion)|draw|render|make\s+(an?\s+)?image|run\s+generate)\b/i.test(t)) { return true; } + if (/\b(create|make)\s+(a\s+)?(photo|picture|image|shot|portrait)\b/i.test(t)) { + return true; + } + if (/\blet['']?s\s+(make|create|generate|draw)\s+(an?\s+)?(image|photo|picture|shot)\b/i.test(t)) { + return true; + } + if (/\bi\s+want\s+(an?\s+)?(image|photo|picture|shot)\b/i.test(t)) { + return true; + } return cyrTokenRe( 'сгенер[а-яё]*|нарисуй|нарисуйте|нарисуем|' + 'запусти\\s+генер[а-яё]*|' - + 'сдела(й|ем|йте)\\s+(кадр|картинк[а-яё]*|изображ[а-яё]*)', + + 'созда(й|йте|дим|дите)\\s+(фото|фотограф[а-яё]*|картинк[а-яё]*|изображ[а-яё]*|кадр|портрет|снимок)|' + + 'сдела(й|йте|ем|йте)\\s+(фото|фотограф[а-яё]*|кадр|картинк[а-яё]*|изображ[а-яё]*|портрет|снимок)|' + + 'давай\\s+(фото|фотограф[а-яё]*|картинк[а-яё]*|изображ[а-яё]*|кадр|сгенер[а-яё]*|нарису[а-яё]*)|' + + 'давай\\s+сделаем\\s+(фото|фотограф[а-яё]*|картинк[а-яё]*|изображ[а-яё]*|кадр)|' + + 'хочу\\s+(фото|фотограф[а-яё]*|картинк[а-яё]*|изображ[а-яё]*|кадр)|' + + 'нужн[ао]?\\s+(фото|фотограф[а-яё]*|картинк[а-яё]*|изображ[а-яё]*|кадр)|' + + 'сфотографируй|отрисуй|' + + 'придумай\\s+(фото|кадр|картинк[а-яё]*|сцену|изображ[а-яё]*)|' + + 'сгенери(руй|руйте)\\s+(мне\\s+)?(фото|кадр|картинк[а-яё]*|изображ[а-яё]*)', ).test(t); } @@ -87,14 +110,13 @@ function generateFlagOn(patch) { /** * Generate is user-owned. Model `generate:true` is advisory (Qwen dumps it on Q&A). - * User «нарисуй»/«сгенерируй» (or opts.userWantsGenerate from hops/buttons) starts Generate. + * User «нарисуй»/«сгенерируй»/«создай фото» (or opts.userWantsGenerate from hops/buttons) starts Generate. */ export function resolveTurnIntent(patch, userText, opts = {}) { const vetoed = !opts.machineTurn && userAsksNoGenerate(userText); const modelAsked = generateFlagOn(patch); const hasPrompt = !!(String(patch?.prompt || '').trim() || String(opts.sessionPrompt || '').trim()); - const userAsked = !opts.machineTurn && userAsksGenerate(userText) - && (modelAsked || hasPrompt); + const userAsked = !opts.machineTurn && userAsksGenerate(userText); const generate = !vetoed && !opts.fromAutoCritique && (userAsked || !!opts.userWantsGenerate); const hasLook = !!patch && (patch.look_at != null || patch.vision_from != null || patch.vision_slots != null); diff --git a/src/session.js b/src/session.js index 0c33f35..71b98c5 100644 --- a/src/session.js +++ b/src/session.js @@ -355,9 +355,7 @@ export function resolveTurnIntent(patch, userText, { const delta = normalizeDelta(patch) || {}; const vetoed = typeof vetoFn === 'function' ? !!vetoFn(userText) : false; const modelAsked = patchWantsGenerate(delta); - const hasPrompt = !!(String(delta.prompt || '').trim() || String(sessionPrompt || '').trim()); - const userAsked = typeof askGenerateFn === 'function' && !!askGenerateFn(userText) - && (modelAsked || hasPrompt); + const userAsked = typeof askGenerateFn === 'function' && !!askGenerateFn(userText); const generate = !vetoed && !fromAutoCritique && (userAsked || !!userWantsGenerate); const hasLook = delta.look_at != null || delta.vision_from != null || delta.vision_slots != null; const look = !!(hasLook && !generate && !vetoed); diff --git a/test/intent.test.js b/test/intent.test.js index b3d2e98..0a3382e 100644 --- a/test/intent.test.js +++ b/test/intent.test.js @@ -24,6 +24,11 @@ describe('intent.js', () => { assert.equal(userAsksGenerate('сгенерируй'), true); assert.equal(userAsksGenerate('нарисуй лису'), true); assert.equal(userAsksGenerate('Давай сделаем изображение красивой рыжеволосой девушки'), true); + assert.equal(userAsksGenerate('Привет, давай фотографию 19-летней девушки на кастинге'), true); + assert.equal(userAsksGenerate('создай фото как на референсе — рыжая в кожанке'), true); + assert.equal(userAsksGenerate('сделай фото портрет'), true); + assert.equal(userAsksGenerate('create a photo of a redhead'), true); + assert.equal(userAsksGenerate('как сгенерировать изображение в swarm'), false); assert.equal(userAsksGenerate('а ты знаешь какие то промпты с civitai'), false); assert.equal(userAsksGenerate('что ты умеешь'), false); assert.equal(userAsksGenerate('не генерируй'), false); @@ -40,6 +45,20 @@ describe('intent.js', () => { const userGen = resolveTurnIntent({ prompt: 'A slender redhead in leather' }, 'сгенерируй', {}); assert.equal(userGen.generate, true); + const createPhoto = resolveTurnIntent( + { prompt: '19yo redhead at casting, studio light' }, + 'создай фото рыжей девушки на кастинге', + {}, + ); + assert.equal(createPhoto.generate, true); + + const promptOnly = resolveTurnIntent( + { prompt: 'fox in snow' }, + 'создай фото лисы', + {}, + ); + assert.equal(promptOnly.generate, true); + const vetoed = resolveTurnIntent({ prompt: 'fox', generate: true }, 'только запомни промпт', {}); assert.equal(vetoed.generate, false); assert.equal(vetoed.vetoed, true);