Ship Assistent 0.11.8: remember session params, slim /debug ask, generate only for real frames.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-22 05:35:21 +03:00
co-authored by Cursor
parent 784426c9bf
commit a5e96f7430
11 changed files with 181 additions and 87 deletions
+94 -59
View File
@@ -1061,11 +1061,12 @@
/**
* Scene / edit / «ещё» Generate without the magic word «генерируй».
* Bare «давай» and trivia questions stay false.
* Chat, opinions, trivia, look-only stay false. No noun-only fallback
* («девушка в студии» in a comment must not start a frame).
*/
function userImpliesGenerate(text) {
const t = String(text || '').trim();
if (!t || userAsksNoGenerate(t)) {
if (!t || userAsksNoGenerate(t) || userIsChatNotFrame(t)) {
return false;
}
if (userAsksGenerate(t) || userAsksContinue(t) || isSameButAspectRequest(t)) {
@@ -1077,63 +1078,74 @@
const wantsLook = userAsksLook(t);
const wantsRedraw = cyrTokenRe('поправь|исправь|перегенерир|перерисуй|улучши|переделай').test(t)
|| /\b(fix|redo|redraw|improve)\b/i.test(t);
if (wantsLook && !wantsRedraw && !userAsksGenerate(t)) {
if (wantsLook && !wantsRedraw) {
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+(её|ее|его|мне)\\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)) {
if (/\b(draw|paint|render|make her|make him|another one|new frame)\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);
return false;
}
/** Opinion / thanks / trivia — not a new frame, even if the model sneaks actions:generate. */
function userIsChatNotFrame(text) {
const t = String(text || '').trim();
if (!t) {
return false;
}
if (/^(ок|окей|ok|okay|ладно|хорошо|понял|ясно|спасибо|thanks)([!.…\s]*)$/i.test(t)) {
return true;
}
if (cyrTokenRe(
'что\\s+такое|как\\s+работает|зачем\\s+|какие\\s+(лор|модел|чекпоинт)|'
+ 'список\\s+лор|где\\s+настрой|что\\s+значит|'
+ 'нравит|спасибо|благодар|почему\\s+так|что\\s+ты\\s+(сделал|изменил)|'
+ 'только\\s+(ответь|скажи|объясни)|без\\s+(кадр|генерац)|не\\s+надо\\s+кадр',
).test(t) && !userAsksGenerate(t) && !userAsksContinue(t)) {
return true;
}
return false;
}
function packBlocksAutoGenerate(pack) {
const p = String(pack || '');
return p === 'describe_ref' || p === 'catalog_card' || p === 'author_persona';
return p === 'describe_ref' || p === 'catalog_card' || p === 'author_persona' || p === 'debug_explain';
}
/** If the turn is a frame, put actions:["generate"] on the patch even when the model omitted it. */
/** Inject generate only when the user turn is a frame; strip it on chat/Q&A. */
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;
if (userAsksNoGenerate(userText) || userIsChatNotFrame(userText)) {
return stripGenerateAction(patch);
}
const pack = $('sa_pack')?.value || '';
if (packBlocksAutoGenerate(pack) && !userAsksGenerate(userText) && !userAsksContinue(userText)) {
return stripGenerateAction(patch);
}
const hasAction = Array.isArray(patch.actions) && patch.actions.map(String).includes('generate');
if (hasAction) {
return patch;
}
const hasFrame = patch.prompt != null
|| (Array.isArray(patch.variants) && patch.variants.length > 0);
const implied = userImpliesGenerate(userText);
if (!hasFrame && !implied) {
if (!userImpliesGenerate(userText)) {
return patch;
}
const out = { ...patch };
@@ -1343,6 +1355,21 @@
}
}
/** Remember applied params when they differ from Exact (or the user asked). */
function shouldRememberSessionParam(key, value) {
if (state.restoringChat || value == null) {
return false;
}
if (state.lastUserParamIntent) {
return true;
}
const exactVal = exactDefaultFor(key);
if (exactVal == null) {
return true;
}
return String(value) !== String(exactVal);
}
function fillEmptyParamsFromExact() {
const defaults = mergedGenerationDefaults();
if (isEmptyParamField(val('input_steps'), { treatZeroEmpty: true }) && defaults.steps != null) {
@@ -4207,14 +4234,13 @@
if (doParams) {
const defaults = mergedGenerationDefaults();
const capture = state.lastUserParamIntent;
const aspectSize = sizeFromAspect(patch.aspect);
if (patch.aspect != null && !shouldSkipSessionRollback('aspect', patch.aspect)) {
if (aspectSize) {
setVal('input_width', String(aspectSize[0]));
setVal('input_height', String(aspectSize[1]));
}
if (capture) {
if (shouldRememberSessionParam('aspect', patch.aspect)) {
rememberSessionExact({ aspect: patch.aspect });
}
} else if (patch.aspect == null && isEmptyParamField(val('input_width'), { treatZeroEmpty: true })
@@ -4227,7 +4253,7 @@
} else {
if (patch.width != null && !shouldSkipSessionRollback('width', patch.width)) {
setVal('input_width', String(patch.width));
if (capture) {
if (shouldRememberSessionParam('width', patch.width)) {
rememberSessionExact({ width: patch.width });
}
} else if (patch.width == null && isEmptyParamField(val('input_width'), { treatZeroEmpty: true }) && defaults.width != null) {
@@ -4235,7 +4261,7 @@
}
if (patch.height != null && !shouldSkipSessionRollback('height', patch.height)) {
setVal('input_height', String(patch.height));
if (capture) {
if (shouldRememberSessionParam('height', patch.height)) {
rememberSessionExact({ height: patch.height });
}
} else if (patch.height == null && isEmptyParamField(val('input_height'), { treatZeroEmpty: true }) && defaults.height != null) {
@@ -4244,7 +4270,7 @@
}
if (patch.steps != null && !shouldSkipSessionRollback('steps', patch.steps)) {
setVal('input_steps', String(patch.steps));
if (capture) {
if (shouldRememberSessionParam('steps', patch.steps)) {
rememberSessionExact({ steps: patch.steps });
}
} else if (patch.steps == null && isEmptyParamField(val('input_steps'), { treatZeroEmpty: true }) && defaults.steps != null) {
@@ -4256,7 +4282,7 @@
} else {
setVal('input_cfg', String(patch.cfg));
}
if (capture) {
if (shouldRememberSessionParam('cfg', patch.cfg)) {
rememberSessionExact({ cfg: patch.cfg });
}
} else if (patch.cfg == null) {
@@ -4278,13 +4304,13 @@
}
} else if (patch.seed != null && !shouldSkipSessionRollback('seed', patch.seed)) {
setVal('input_seed', String(patch.seed));
if (capture) {
if (shouldRememberSessionParam('seed', patch.seed)) {
rememberSessionExact({ seed: patch.seed });
}
}
if (patch.sigma_shift != null && !shouldSkipSessionRollback('sigma_shift', patch.sigma_shift)) {
setVal('input_sigmashift', String(patch.sigma_shift));
if (capture) {
if (shouldRememberSessionParam('sigma_shift', patch.sigma_shift)) {
rememberSessionExact({ sigma_shift: patch.sigma_shift });
}
} else if (patch.sigma_shift == null && isEmptyParamField(val('input_sigmashift')) && defaults.sigma_shift != null) {
@@ -4294,13 +4320,13 @@
if (document.getElementById('input_sampler')) {
setVal('input_sampler', String(patch.sampler));
}
if (capture) {
if (shouldRememberSessionParam('sampler', patch.sampler)) {
rememberSessionExact({ sampler: patch.sampler });
}
}
if (patch.scheduler != null && document.getElementById('input_scheduler')) {
setVal('input_scheduler', String(patch.scheduler));
if (capture) {
if (shouldRememberSessionParam('scheduler', patch.scheduler)) {
rememberSessionExact({ scheduler: patch.scheduler });
}
}
@@ -4311,7 +4337,7 @@
} else if (document.getElementById('input_batchsize')) {
setVal('input_batchsize', String(batch));
}
if (capture) {
if (shouldRememberSessionParam('images', batch)) {
rememberSessionExact({ images: batch });
}
} else if (batch == null) {
@@ -8133,11 +8159,12 @@
state.pendingSilentGen = false;
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));
const hasGenAction = Array.isArray(effective?.actions) && effective.actions.map(String).includes('generate');
const implied = userImpliesGenerate(opts.userText || '');
const wantsGen = !suppressGen && !!(opts.userWantsGenerate || state.pendingSilentGen || implied
|| (hasGenAction && !userIsChatNotFrame(opts.userText || '')));
// Auto-Generate = skip Apply buttons when this turn is a frame — not "every patch".
const willGen = !!(effective && !fromAutoCritique && !suppressGen && wantsGen);
// Maximize chat-model prep: structure + EN for Krea before Swarm Generate runs.
if (willGen && effective?.prompt && promptNeedsKreaPrep(effective.prompt)
&& !opts.fromPromptEnRetry && !fromVisionHop && !fromDebug) {
@@ -8165,7 +8192,7 @@
updateTasteFromPatch(effective, opts.userText || '');
// Auto-Generate must not fire on «запомни / шаблон» turns — even if the model
// echoed a prompt patch or sneaked actions:["generate"].
if (!fromAutoCritique && !suppressGen && (wantsGen || $('sa_auto_generate')?.checked)) {
if (!fromAutoCritique && !suppressGen && wantsGen) {
if (effective?.prompt && promptNeedsKreaPrep(effective.prompt)) {
setStatus('Промпт всё ещё не EN/Krea-ready — Generate с тем что есть');
}
@@ -8368,6 +8395,7 @@
skipSlash: true,
skipAutoPack: true,
fromDebug: true,
skipAppendUser: true,
});
} else {
setStatus('/debug');
@@ -8651,7 +8679,7 @@
return;
}
if (!opts.skipAutoPack && !opts.fromAutoCritique && !opts.fromDownload && !opts.fromVisionHop && !opts.fromCards) {
if (!opts.skipAutoPack && !opts.fromAutoCritique && !opts.fromDownload && !opts.fromVisionHop && !opts.fromCards && !opts.fromDebug) {
const guessed = autoSelectPack(text);
if (guessed) {
setPackValue(guessed, { flash: true });
@@ -8659,11 +8687,11 @@
}
// Cards mode must not be overridden by auto-pack; keep catalog_card.
if (opts.fromCards || state.view === 'cards') {
if (!opts.fromDebug && (opts.fromCards || state.view === 'cards')) {
setPackValue('catalog_card', { flash: false });
}
const pack = $('sa_pack')?.value || defaultPackId();
const pack = opts.fromDebug ? 'debug_explain' : ($('sa_pack')?.value || defaultPackId());
const persona = $('sa_persona')?.value || localStorage.getItem(LS_PERSONA) || 'neutral';
const model = $('sa_model')?.value;
if (!model) {
@@ -8744,14 +8772,18 @@
pack,
silentPatch: !!state.pendingSilentGen,
};
state.history.push({ role: 'user', content: text });
if (state.pendingPersonaNote) {
state.history.push({ role: 'user', content: state.pendingPersonaNote, systemish: true });
state.pendingPersonaNote = null;
if (!opts.skipAppendUser) {
state.history.push({ role: 'user', content: opts.historyUserText || text });
if (state.pendingPersonaNote) {
state.history.push({ role: 'user', content: state.pendingPersonaNote, systemish: true });
state.pendingPersonaNote = null;
}
appendMessage('user', opts.historyUserText || text);
if ($('sa_input')) {
$('sa_input').value = '';
}
persistHistory();
}
appendMessage('user', text);
$('sa_input').value = '';
persistHistory();
const context = collectLiveContext();
// has_vision_image = board has a real frame (even when JPEG is not in this request).
@@ -8782,6 +8814,9 @@
}
return { role: m.role, content: content.slice(0, 4000) };
});
if (opts.skipAppendUser) {
messages.push({ role: 'user', content: text });
}
if (images && messages.length) {
messages[messages.length - 1].images = images;
}
@@ -8795,10 +8830,10 @@
model,
pack,
persona,
includeBase: true,
includeBase: !opts.fromDebug,
messages,
context_json: JSON.stringify(context),
skills: state.enabledSkills || [],
skills: opts.fromDebug ? [] : (state.enabledSkills || []),
embed_model: $('sa_embed_model')?.value || state.preferredEmbed || '',
};