Ship Assistent 0.10.13: ordinary pack, senior chat default, aspect/critique fixes, prose UI.

Default pack is Обычный; prefer default_chat/senior Ollama tag; client-apply same-but-aspect; leave critique pack after hops; render Critique as Критика instead of raw ### markdown.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-22 03:50:58 +03:00
co-authored by Cursor
parent 6e7272ffc9
commit 43984208fc
18 changed files with 687 additions and 198 deletions
+56 -1
View File
@@ -1154,10 +1154,65 @@
}
.sa-msg-body {
white-space: pre-wrap;
word-break: break-word;
}
.sa-msg-body.sa-prose {
white-space: normal;
display: flex;
flex-direction: column;
gap: 0.35rem;
}
.sa-prose-h {
font-weight: 650;
letter-spacing: 0.01em;
line-height: 1.25;
margin: 0.15rem 0 0.05rem;
color: color-mix(in srgb, currentColor 92%, #fff);
}
.sa-prose-h1 {
font-size: 1.15rem;
}
.sa-prose-h2 {
font-size: 1.05rem;
}
.sa-prose-h3 {
font-size: 0.98rem;
opacity: 0.92;
}
.sa-prose-p {
margin: 0;
line-height: 1.45;
white-space: pre-wrap;
}
.sa-prose-list {
margin: 0.1rem 0 0.2rem 1.1rem;
padding: 0;
line-height: 1.4;
}
.sa-prose-list li {
margin: 0.15rem 0;
}
.sa-prose-code {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 0.88em;
padding: 0.05em 0.3em;
border-radius: 0.25rem;
background: color-mix(in srgb, currentColor 12%, transparent);
}
.sa-prose-gap {
height: 0.35rem;
}
.sa-msg.error {
align-self: stretch;
border: 1px solid color-mix(in srgb, #c44 55%, transparent);
+398 -60
View File
@@ -7,6 +7,9 @@
const LS_MODEL = 'swarm_assistent_model';
const LS_EMBED = 'swarm_assistent_embed_model';
const LS_PACK = 'swarm_assistent_pack';
const LS_PACK_ORDINARY_MIG = 'swarm_assistent_pack_ordinary_v1';
/** One-shot: drop junior chat tags stuck in LS so UI/warm pick preferred/senior. */
const LS_MODEL_SENIOR_MIG = 'swarm_assistent_model_senior_v1';
const LS_PERSONA = 'swarm_assistent_persona';
const LS_VIEW = 'swarm_assistent_view';
const LS_AUTO_VISION = 'swarm_assistent_auto_vision';
@@ -44,6 +47,11 @@
};
let PACK_ALIASES = {
ordinary: 'ordinary',
combine: 'ordinary',
normal: 'ordinary',
general: 'ordinary',
default: 'ordinary',
write: 'write_prompt',
write_prompt: 'write_prompt',
critique: 'critique_image',
@@ -138,9 +146,11 @@
tasteSaveTimer: null,
streamEl: null,
streamMeta: null,
streamText: '',
critiqueHopUsed: false,
visionHopUsed: false,
lastSystemChars: 0,
lastSystemLayers: null,
lastContextChars: 0,
busyPhase: 'idle',
busyStarted: 0,
@@ -459,6 +469,131 @@
.replace(/"/g, '&quot;');
}
/** Known ### headings → RU display labels. JSON Patch is hidden (real UI is the patch strip). */
const PROSE_SECTION_TITLES = {
critique: 'Критика',
критика: 'Критика',
analysis: 'Разбор',
разбор: 'Разбор',
notes: 'Заметки',
заметки: 'Заметки',
summary: 'Кратко',
кратко: 'Кратко',
prompt: 'Промпт',
промпт: 'Промпт',
'improved prompt': 'Промпт',
'next prompt': 'Промпт',
deliverable: 'Итог',
итог: 'Итог',
verdict: 'Вердикт',
вердикт: 'Вердикт',
issues: 'Проблемы',
проблемы: 'Проблемы',
fixes: 'Правки',
правки: 'Правки',
suggestion: 'Предложение',
suggestions: 'Предложения',
предложения: 'Предложения',
};
function localizeProseHeading(raw) {
const cleaned = String(raw || '').replace(/[*_`#]/g, '').trim();
if (!cleaned) {
return null;
}
const key = cleaned.toLowerCase().replace(/\s+/g, ' ');
if (/^json\s*patch$/.test(key) || /^патч$/.test(key) || /^json\s*патч$/.test(key)) {
return null;
}
if (PROSE_SECTION_TITLES[key]) {
return PROSE_SECTION_TITLES[key];
}
// "Critique — blur" → take first token bucket
const head = key.split(/[—:\-|]/)[0].trim();
if (PROSE_SECTION_TITLES[head]) {
return PROSE_SECTION_TITLES[head];
}
return cleaned;
}
function formatProseInline(escapedLine) {
let t = escapedLine;
t = t.replace(/`([^`]+)`/g, '<code class="sa-prose-code">$1</code>');
t = t.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
t = t.replace(/(^|[^*])\*([^*\n]+)\*(?!\*)/g, '$1<em>$2</em>');
return t;
}
/** Lightweight chat prose: ### Critique → «Критика», lists, bold — not a full markdown engine. */
function formatAssistantProseHtml(raw) {
let text = String(raw || '').replace(/\r\n/g, '\n');
text = text.replace(/(?:^|\n)#{1,6}\s*JSON\s*Patch\s*(?=\n|$)/gi, '\n');
text = text.replace(/\n{3,}/g, '\n\n').trim();
if (!text) {
return '';
}
const lines = text.split('\n');
const parts = [];
let listItems = [];
const flushList = () => {
if (!listItems.length) {
return;
}
parts.push(
`<ul class="sa-prose-list">${listItems.map((li) => `<li>${formatProseInline(escapeHtml(li))}</li>`).join('')}</ul>`,
);
listItems = [];
};
for (const line of lines) {
const heading = line.match(/^#{1,3}\s+(.+?)\s*$/);
if (heading) {
flushList();
const title = localizeProseHeading(heading[1]);
if (!title) {
continue;
}
const level = Math.min((line.match(/^#+/) || ['###'])[0].length, 3);
parts.push(
`<div class="sa-prose-h sa-prose-h${level}" role="heading" aria-level="${level}">${escapeHtml(title)}</div>`,
);
continue;
}
const bullet = line.match(/^\s*[-*•]\s+(.+)$/);
if (bullet) {
listItems.push(bullet[1]);
continue;
}
flushList();
if (!line.trim()) {
parts.push('<div class="sa-prose-gap" aria-hidden="true"></div>');
continue;
}
parts.push(`<p class="sa-prose-p">${formatProseInline(escapeHtml(line))}</p>`);
}
flushList();
return parts.join('');
}
function setAssistantBody(div, text) {
if (!div) {
return;
}
let body = div.querySelector('.sa-msg-body');
if (!body) {
body = document.createElement('div');
body.className = 'sa-msg-body';
div.appendChild(body);
}
const raw = text || '';
body.classList.add('sa-prose');
const html = formatAssistantProseHtml(raw);
if (html) {
body.innerHTML = html;
} else {
body.textContent = '';
}
}
function val(id) {
const el = document.getElementById(id);
return el ? el.value : '';
@@ -495,12 +630,70 @@
return new RegExp(`${boundary}(?:${alts})${end}`, 'i');
}
/** Parse aspect from chat: 9:16, 9x16, 9×16, «9 на 16», «9 к 16». */
function parseAspectFromUserText(text) {
const t = String(text || '');
if (!t.trim()) {
return null;
}
const ratio = t.match(/(?:^|[^0-9])(\d+(?:\.\d+)?)\s*[:x×хX]\s*(\d+(?:\.\d+)?)(?=$|[^0-9])/);
if (ratio) {
const key = normalizeAspect(`${ratio[1]}:${ratio[2]}`);
if (key) {
return key;
}
}
const na = t.match(/(?:^|[^0-9])(\d+(?:\.\d+)?)\s*(?:на|к|to)\s*(\d+(?:\.\d+)?)(?=$|[^0-9])/i);
if (na) {
const key = normalizeAspect(`${na[1]}:${na[2]}`);
if (key) {
return key;
}
}
const named = t.match(/\b(16:9|9:16|1:1|4:5|2:3|3:2|4:3|2\.35:1)\b/i);
if (named) {
return normalizeAspect(named[1]);
}
if (cyrTokenRe('портрет|вертикал[а-яё]*').test(t) || /\b(portrait|vertical)\b/i.test(t)) {
return normalizeAspect('9:16') || normalizeAspect('2:3');
}
if (cyrTokenRe('альбом|горизонтал[а-яё]*').test(t) || /\b(landscape|horizontal|widescreen)\b/i.test(t)) {
return normalizeAspect('16:9');
}
return null;
}
/** «такую же, только 9 на 16» — keep prompt, change aspect, generate (no LLM needed). */
function isSameButAspectRequest(text) {
const t = String(text || '');
if (!parseAspectFromUserText(t)) {
return false;
}
return /такую\s+же|тот\s+же\s+промпт|same\s+(one|prompt|thing|again)|только\s+(поменя|смени|поставь)|поменяй\s+на|смени\s+на|only\s+change|just\s+change/i.test(t)
|| /поменяй\s+(размер|aspect|соотношен)/i.test(t)
|| /смени\s+(размер|aspect|соотношен)/i.test(t);
}
function userTextMentionsParams(text) {
const t = String(text || '');
if (parseAspectFromUserText(t)) {
return true;
}
if (/\b(steps?|cfg|seed|sigma|aspect|resolution|batch|turbo|raw)\b/i.test(t)) {
return true;
}
return cyrTokenRe('размер|ширин[а-яё]*|высот[а-яё]*|турбо').test(t);
return cyrTokenRe('размер|ширин[а-яё]*|высот[а-яё]*|соотношен[а-яё]*|турбо|портрет|вертикал[а-яё]*').test(t);
}
function replyMissingJsonPatch(reply) {
const t = String(reply || '');
if (!t.trim()) {
return false;
}
if (/```(?:json)?\s*\{[\s\S]*?\}```/i.test(t)) {
return false;
}
return /###\s*JSON\s*Patch\b/i.test(t) || /JSON\s*Patch\s*:?\s*$/im.test(t);
}
function userAsksGenerate(text) {
@@ -548,13 +741,20 @@
}
}
function defaultPackId() {
return state.config?.assistant?.default_pack
|| $('sa_pack')?.querySelector('option')?.value
|| 'ordinary';
}
function syncModeBadge() {
const badge = $('sa_mode_badge');
const pack = $('sa_pack')?.value || 'write_prompt';
const pack = $('sa_pack')?.value || defaultPackId();
if (!badge) {
return;
}
const shortMap = {
ordinary: 'обычный',
write_prompt: 'write',
critique_image: 'critique',
compose_scene: 'compose',
@@ -562,6 +762,7 @@
inpaint_edit: 'inpaint',
describe_ref: 'describe',
catalog_card: 'card',
author_persona: 'persona',
};
const short = shortMap[pack] || pack.replace(/_/g, ' ').slice(0, 12);
badge.textContent = short;
@@ -1406,6 +1607,9 @@
function stripJsonFencesForHistory(content) {
return String(content || '')
.replace(/```(?:json)?\s*[\s\S]*?```/gi, '')
// Drop echoed critique templates / empty patch headers so the next turn doesn't copy them.
.replace(/###\s*Critique\b[\s\S]*?(?=###|$)/gi, '')
.replace(/###\s*JSON\s*Patch\b[\s\S]*$/gi, '')
.replace(/\n{3,}/g, '\n\n')
.trim();
}
@@ -1472,7 +1676,7 @@
batch: parseInt(val('input_images') || val('input_batchsize') || '0', 10) || null,
loras,
persona: $('sa_persona')?.value || 'neutral',
pack: $('sa_pack')?.value || 'write_prompt',
pack: $('sa_pack')?.value || defaultPackId(),
sessionExact,
lastPatch,
};
@@ -2487,11 +2691,11 @@
}
return out;
});
ctx.enabled_loras = ctx.selected_loras;
// selected_loras = enabled; do not also emit enabled_loras (duplicate).
}
} catch (e) { /* ignore */ }
// Recommendation cards: checkpoint + selected LoRAs only (no has_card sweep).
// Recommendation cards: checkpoint + selected LoRAs only when they add beyond inventory.
const cardKeys = [];
const seenCard = new Set();
const addKey = (kind, name) => {
@@ -2515,9 +2719,24 @@
}
for (const k of cardKeys) {
const cached = state.modelCards[`${k.kind}:${k.name}`];
if (cached) {
ctx.model_cards.push(slimCardForContext(cached));
if (!cached) {
continue;
}
const slim = slimCardForContext(cached);
if (!slim) {
continue;
}
if (k.kind === 'lora') {
const sel = (ctx.selected_loras || []).find(
(l) => String(l.name || '').toLowerCase() === String(k.name).toLowerCase(),
);
const inventoryRich = !!(sel && (sel.triggers?.length || sel.trigger_phrase || sel.blurb));
const cardExtra = !!(slim.when || slim.avoid || slim.prompt_hint || slim.notes);
if (inventoryRich && !cardExtra) {
continue;
}
}
ctx.model_cards.push(slim);
}
// Fallback if inventory empty
@@ -3037,10 +3256,19 @@
if (state.packUserTouched) {
return null;
}
// Комбайн «Обычный» сам выбирает поведение — не переключаем pack.
const cur = $('sa_pack')?.value || defaultPackId();
if (cur === 'ordinary') {
return null;
}
const t = String(text || '').toLowerCase();
if (!t.trim()) {
return null;
}
// Param / aspect asks must leave a stuck critique_image pack from auto-critique.
if (userTextMentionsParams(t) || parseAspectFromUserText(t)) {
return cur === 'critique_image' || cur === 'describe_ref' ? 'ordinary' : 'form_params';
}
if (cyrTokenRe('поправь|исправь|перепиши|улучши').test(t)
|| /\b(fix\s+it|make\s+it\s+better|rewrite)\b/i.test(t)) {
return 'write_prompt';
@@ -3061,8 +3289,9 @@
|| /init\s*image/i.test(t)) {
return 'inpaint_edit';
}
if (userTextMentionsParams(t)) {
return 'form_params';
// Any non-critique follow-up after auto-critique should leave critique mode.
if (cur === 'critique_image') {
return 'ordinary';
}
if (/\b(moodboard|compose|scene)\b/i.test(t)
|| cyrTokenRe('сцен[а-яё]*|атмосфер[а-яё]*|мизансцен[а-яё]*').test(t)) {
@@ -3071,6 +3300,16 @@
return 'write_prompt';
}
function restoreDefaultPackAfterHop() {
if (state.packUserTouched) {
return;
}
const cur = $('sa_pack')?.value || '';
if (cur === 'critique_image' || cur === 'describe_ref') {
setPackValue(defaultPackId(), { flash: true });
}
}
function patchHasGenTrigger(patch) {
if (!patch) {
return false;
@@ -3689,6 +3928,7 @@
}
setStatus('Auto-critique…');
await sendChat({ fromAutoCritique: true, forceSlotIds: [GEN_ID] });
restoreDefaultPackAfterHop();
}
/** After Generate: send look_at with JPEG when sa_auto_vision is on (skipped if auto-critique already attaches vision). */
@@ -3713,6 +3953,7 @@
}
setStatus('Auto look_at…');
await sendChat({ fromVisionHop: true, forceSlotIds: [GEN_ID], skipAutoPack: true });
restoreDefaultPackAfterHop();
}
/** Board action: attach the finished Generate frame and ask for a verdict. */
@@ -3775,7 +4016,7 @@
chip.textContent = persona.title || persona.id;
chip.title = `Характер: ${persona.title || persona.id}${pack ? ` · режим ${pack}` : ''}`;
row.appendChild(chip);
if (pack && pack !== 'write_prompt') {
if (pack && pack !== 'ordinary' && pack !== 'write_prompt') {
const packEl = document.createElement('span');
packEl.className = 'sa-pack-mark';
packEl.textContent = pack.replace(/_/g, ' ');
@@ -3785,19 +4026,6 @@
div.insertBefore(row, div.firstChild);
}
function setAssistantBody(div, text) {
if (!div) {
return;
}
let body = div.querySelector('.sa-msg-body');
if (!body) {
body = document.createElement('div');
body.className = 'sa-msg-body';
div.appendChild(body);
}
body.textContent = text || '';
}
function appendMessage(role, text, patch, civitaiResults, meta) {
const box = $('sa_messages');
if (!box) {
@@ -3856,6 +4084,7 @@
if (state.streamEl) {
if (state.streamEl.classList.contains('sa-typing')) {
state.streamEl.classList.remove('sa-typing');
state.streamText = '';
setAssistantBody(state.streamEl, '');
}
state.gotDelta = true;
@@ -3863,12 +4092,8 @@
if (state.busyPhase !== 'refining') {
setBusyPhase('streaming');
}
let body = state.streamEl.querySelector('.sa-msg-body');
if (!body) {
setAssistantBody(state.streamEl, '');
body = state.streamEl.querySelector('.sa-msg-body');
}
body.textContent += delta;
state.streamText = (state.streamText || '') + (delta || '');
setAssistantBody(state.streamEl, state.streamText);
const box = $('sa_messages');
if (box) {
box.scrollTop = box.scrollHeight;
@@ -3881,6 +4106,7 @@
const meta = state.streamMeta;
state.streamEl = null;
state.streamMeta = null;
state.streamText = '';
if (!el) {
appendMessage('assistant', fullReply, null, civitaiResults, meta || undefined);
return;
@@ -4234,6 +4460,13 @@
}
function loadSettings() {
// One-shot: old default was write_prompt → migrate to ordinary комбайн.
if (!localStorage.getItem(LS_PACK_ORDINARY_MIG)) {
if (localStorage.getItem(LS_PACK) === 'write_prompt') {
localStorage.setItem(LS_PACK, 'ordinary');
}
localStorage.setItem(LS_PACK_ORDINARY_MIG, '1');
}
const base = localStorage.getItem(LS_BASE);
const model = localStorage.getItem(LS_MODEL);
const pack = localStorage.getItem(LS_PACK);
@@ -4295,7 +4528,7 @@
function collectUiState() {
return {
pack: $('sa_pack')?.value || 'write_prompt',
pack: $('sa_pack')?.value || defaultPackId(),
persona: $('sa_persona')?.value || 'neutral',
auto_vision: !!$('sa_auto_vision')?.checked,
auto_apply: !!$('sa_auto_apply')?.checked,
@@ -4381,7 +4614,7 @@
localStorage.setItem(LS_BASE, $('sa_base_url')?.value || '');
localStorage.setItem(LS_MODEL, $('sa_model')?.value || '');
localStorage.setItem(LS_EMBED, $('sa_embed_model')?.value || state.preferredEmbed || '');
localStorage.setItem(LS_PACK, $('sa_pack')?.value || 'write_prompt');
localStorage.setItem(LS_PACK, $('sa_pack')?.value || defaultPackId());
localStorage.setItem(LS_PERSONA, $('sa_persona')?.value || 'neutral');
localStorage.setItem(LS_VIEW, state.view || 'chat');
localStorage.setItem(LS_AUTO_VISION, $('sa_auto_vision')?.checked ? '1' : '0');
@@ -4804,7 +5037,7 @@
if (!sel) {
return;
}
const cur = preferred || sel.value || localStorage.getItem(LS_PACK) || 'write_prompt';
const cur = preferred || sel.value || localStorage.getItem(LS_PACK) || defaultPackId();
sel.innerHTML = '';
const list = (packs || []).slice().sort((a, b) => (a.order || 100) - (b.order || 100));
for (const p of list) {
@@ -4896,14 +5129,69 @@
);
}
function setModelOptions(models, { error } = {}) {
/** Larger param tags win (32b > 8b > 7b); instruct / qwen3 preferred over thinking/:latest. */
function chatModelSeniority(name) {
const n = String(name || '').toLowerCase();
let score = 0;
const m = n.match(/(?:^|[:\-/])(\d+)\s*b\b/);
if (m) {
score += Number(m[1]) * 1e6;
}
if (n.includes('instruct')) {
score += 5e4;
}
if (n.includes('qwen3')) {
score += 2e4;
}
if (n.includes('thinking') || n.endsWith(':latest')) {
score -= 1e4;
}
return score;
}
function pickSeniorChatModel(names) {
const list = (names || []).map((n) => String(n || '').trim()).filter(Boolean);
if (!list.length) {
return '';
}
return [...list].sort((a, b) => chatModelSeniority(b) - chatModelSeniority(a) || a.localeCompare(b))[0];
}
/**
* Resolve which chat tag to select / warm.
* Priority: api preferred (ollama-roles default_chat) senior heuristic LS (after one-shot junior upgrade).
*/
function resolveChatModel(names, apiPreferred) {
const list = (names || []).map((n) => String(n || '').trim()).filter(Boolean);
if (!list.length) {
return '';
}
const preferred = apiPreferred && list.includes(apiPreferred)
? apiPreferred
: pickSeniorChatModel(list);
const ls = state.preferredModel || localStorage.getItem(LS_MODEL) || '';
if (!localStorage.getItem(LS_MODEL_SENIOR_MIG)) {
localStorage.setItem(LS_MODEL_SENIOR_MIG, '1');
if (preferred && (!ls || !list.includes(ls) || chatModelSeniority(ls) < chatModelSeniority(preferred))) {
state.preferredModel = preferred;
return preferred;
}
}
if (ls && list.includes(ls)) {
return ls;
}
return preferred || list[0];
}
function setModelOptions(models, { error, preferred } = {}) {
const sel = $('sa_model');
const sel2 = $('sa_settings_chat_model');
const apply = (target) => {
if (!target) {
return;
}
const names = (models || []).map((n) => String(n || '').trim()).filter(Boolean);
let names = (models || []).map((n) => String(n || '').trim()).filter(Boolean);
names = [...names].sort((a, b) => chatModelSeniority(b) - chatModelSeniority(a) || a.localeCompare(b));
target.innerHTML = '';
if (error) {
const opt = document.createElement('option');
@@ -4927,9 +5215,9 @@
opt.textContent = name;
target.appendChild(opt);
}
const prefer = state.preferredModel || localStorage.getItem(LS_MODEL);
if (prefer && names.includes(prefer)) {
target.value = prefer;
const pick = resolveChatModel(names, preferred);
if (pick) {
target.value = pick;
}
};
apply(sel);
@@ -4982,11 +5270,17 @@
(data) => {
const models = data.models || [];
const memoryModels = data.memory_models || [];
setModelOptions(models);
const preferred = (data.preferred || '').trim();
setModelOptions(models, { preferred });
setEmbedModelOptions(memoryModels);
const prefer = state.preferredModel || localStorage.getItem(LS_MODEL);
if (prefer && models.includes(prefer) && $('sa_model')) {
$('sa_model').value = prefer;
const pick = resolveChatModel(models, preferred);
if (pick && $('sa_model')) {
$('sa_model').value = pick;
if ($('sa_settings_chat_model')) {
$('sa_settings_chat_model').value = pick;
}
state.preferredModel = pick;
localStorage.setItem(LS_MODEL, pick);
}
setStatus(models.length ? `${models.length} chat · ${memoryModels.length} memory` : 'No Ollama models (gpu-rent: ollama pull)');
if (models.length) {
@@ -6648,10 +6942,21 @@
return;
}
const { patch } = extractPatch(reply);
if (patch) {
rememberLastPatch(patch);
let effective = patch;
if (!effective && !fromVisionHop && !fromAutoCritique && !fromDebug) {
const aspect = parseAspectFromUserText(opts.userText || '');
if (aspect && (replyMissingJsonPatch(reply) || isSameButAspectRequest(opts.userText || ''))) {
effective = { aspect, actions: ['generate'] };
if (state.lastPatch?.prompt) {
effective.prompt = state.lastPatch.prompt;
}
appendSystemNote(`Патч пустой — применил aspect ${aspect} сам.`);
}
}
if (Array.isArray(patch?.actions) && patch.actions.map(String).includes('interrupt')) {
if (effective) {
rememberLastPatch(effective);
}
if (Array.isArray(effective?.actions) && effective.actions.map(String).includes('interrupt')) {
doInterruptNow();
}
if (civitaiResults && civitaiResults.length && $('sa_auto_download')?.checked) {
@@ -6661,8 +6966,8 @@
downloadCivitaiLoRA(pick, null);
}
}
if (patch && !fromVisionHop && !fromAutoCritique) {
const hopped = await maybeVisionHop(patch, opts.attachedSlotIds || []);
if (effective && !fromVisionHop && !fromAutoCritique) {
const hopped = await maybeVisionHop(effective, opts.attachedSlotIds || []);
if (hopped) {
return;
}
@@ -6673,20 +6978,20 @@
return;
}
const wantsGen = !!(opts.userWantsGenerate || state.pendingSilentGen
|| (Array.isArray(patch?.actions) && patch.actions.map(String).includes('generate')));
const doApply = !!(patch && (wantsGen || $('sa_auto_apply')?.checked));
|| (Array.isArray(effective?.actions) && effective.actions.map(String).includes('generate')));
const doApply = !!(effective && (wantsGen || $('sa_auto_apply')?.checked));
if (doApply) {
if (wantsGen) {
startBusyUi('silent_gen');
} else {
setBusyPhase('applying');
}
await applyPatch(patch, 'all');
await applyPatch(effective, 'all');
syncLiveParamsBar();
updateTasteFromPatch(patch, opts.userText || '');
updateTasteFromPatch(effective, opts.userText || '');
if (!fromAutoCritique && (wantsGen || $('sa_auto_generate')?.checked)) {
const src = await runGenerateFromPatch(
{ ...patch, actions: Array.isArray(patch.actions) ? patch.actions : ['generate'] },
{ ...effective, actions: Array.isArray(effective.actions) ? effective.actions : ['generate'] },
{ force: wantsGen },
);
if (src) {
@@ -6696,6 +7001,8 @@
} else if (!state.generating) {
stopBusyUi(wantsGen ? 'Применено' : '');
}
} else if (replyMissingJsonPatch(reply) && !fromAutoCritique && !fromVisionHop) {
setStatus('Ответ без JSON-патча — ничего не применено');
}
state.pendingSilentGen = false;
}
@@ -6767,7 +7074,7 @@
function buildDebugSummary() {
const persona = $('sa_persona')?.value || 'neutral';
const pack = $('sa_pack')?.value || 'write_prompt';
const pack = $('sa_pack')?.value || defaultPackId();
const chatModel = $('sa_model')?.value || '—';
const embed = $('sa_embed_model')?.value || state.preferredEmbed || '—';
const profile = detectKreaProfileName();
@@ -6817,6 +7124,9 @@
` init=${!!ctx.has_init_image} mask=${!!ctx.has_mask_image} prompt_images=${ctx.prompt_image_count || 0}`,
` has_vision_image=${!!ctx.has_vision_image} · images_in_request=${!!ctx.images_in_request} · vision_ready=${visionReadySlots().length}`,
` context_json_chars≈${JSON.stringify(ctx).length} · last_system_chars=${state.lastSystemChars || '—'} · last_context_chars=${state.lastContextChars || '—'}`,
state.lastSystemLayers
? ` system_layers: ${Object.entries(state.lastSystemLayers).map(([k, v]) => `${k}=${v}`).join(' · ')}`
: ' system_layers: — (отправь сообщение, чтобы заполнить)',
'',
'Exact defaults (merged):',
` generation=${JSON.stringify(exactGen)}`,
@@ -7027,7 +7337,7 @@
if ($('sa_input')) {
$('sa_input').value = `Find a Krea 2 LoRA for: ${arg}`;
}
setPackValue('write_prompt', { flash: true });
setPackValue(defaultPackId(), { flash: true });
await sendChat({
skipAutoPack: true,
forcedUserText: `Search Civitai for Krea-compatible LoRA: ${arg}. Prefer actions search_civitai.`,
@@ -7113,7 +7423,7 @@
}
if (!opts.fromAutoCritique && !opts.fromDownload && !opts.fromVisionHop && !opts.fromDebug) {
state.lastUserParamIntent = userTextMentionsParams(text);
state.pendingSilentGen = userAsksGenerate(text);
state.pendingSilentGen = userAsksGenerate(text) || isSameButAspectRequest(text);
}
if (!opts.fromAutoCritique && !opts.fromDownload && !opts.fromVisionHop && !opts.skipSlash) {
@@ -7128,6 +7438,31 @@
}
}
// «такую же, только 9 на 16» — apply aspect + Generate without waiting for an empty LLM critique.
if (!opts.fromAutoCritique && !opts.fromDownload && !opts.fromVisionHop && !opts.fromDebug
&& !opts.fromCards && isSameButAspectRequest(text)) {
const aspect = parseAspectFromUserText(text);
if (aspect) {
if ($('sa_input')) {
$('sa_input').value = '';
}
appendMessage('user', text);
state.history.push({ role: 'user', content: text });
persistHistory();
restoreDefaultPackAfterHop();
const patch = { aspect, actions: ['generate'] };
if (state.lastPatch?.prompt) {
patch.prompt = state.lastPatch.prompt;
}
if (Array.isArray(state.lastPatch?.loras) && state.lastPatch.loras.length) {
patch.loras = state.lastPatch.loras;
}
appendSystemNote(`Ставлю ${aspect} и Generate (тот же промпт) — без повторной критики.`);
await applyQuickPatch(patch, `Aspect ${aspect}`);
return;
}
}
if (!updateGate()) {
setStatus('Выбери модель Krea 2');
return;
@@ -7145,7 +7480,7 @@
setPackValue('catalog_card', { flash: false });
}
const pack = $('sa_pack')?.value || 'write_prompt';
const pack = $('sa_pack')?.value || defaultPackId();
const persona = $('sa_persona')?.value || localStorage.getItem(LS_PERSONA) || 'neutral';
const model = $('sa_model')?.value;
if (!model) {
@@ -7283,6 +7618,9 @@
if (meta.system_chars != null) {
state.lastSystemChars = Number(meta.system_chars) || 0;
}
if (meta.system_layers && typeof meta.system_layers === 'object') {
state.lastSystemLayers = meta.system_layers;
}
try {
state.lastContextChars = (context && JSON.stringify(context).length) || 0;
} catch (e) {
@@ -7374,7 +7712,7 @@
const reply = data.reply || (state.streamEl?.querySelector('.sa-msg-body')?.textContent) || '';
const civitai = data.civitai_results || [];
finalizeStreamMessage(reply, civitai);
finishOk(reply, civitai, { system_chars: data.system_chars });
finishOk(reply, civitai, { system_chars: data.system_chars, system_layers: data.system_layers });
}
},
0,
@@ -7402,7 +7740,7 @@
}
const reply = data.reply || '';
appendMessage('assistant', reply, null, data.civitai_results || [], msgMeta);
finishOk(reply, data.civitai_results || [], { system_chars: data.system_chars });
finishOk(reply, data.civitai_results || [], { system_chars: data.system_chars, system_layers: data.system_layers });
},
0,
(err2) => finishErr(String(err2 || err || 'Chat failed')),
@@ -7425,7 +7763,7 @@
}
const reply = data.reply || '';
appendMessage('assistant', reply, null, data.civitai_results || [], msgMeta);
finishOk(reply, data.civitai_results || [], { system_chars: data.system_chars });
finishOk(reply, data.civitai_results || [], { system_chars: data.system_chars, system_layers: data.system_layers });
},
0,
(err) => finishErr(String(err || 'Chat failed')),
@@ -7554,7 +7892,7 @@
preferSelected: false,
});
const pack = $('sa_pack');
if (pack && pack.value === 'write_prompt') {
if (pack && (pack.value === 'ordinary' || pack.value === 'write_prompt')) {
pack.value = 'critique_image';
saveSettings();
}
@@ -7813,7 +8151,7 @@
}
await setInitFromSrc(src);
const pack = $('sa_pack');
if (pack && pack.value === 'write_prompt') {
if (pack && (pack.value === 'ordinary' || pack.value === 'write_prompt')) {
setPackValue('inpaint_edit', { flash: true });
}
});