Ship Assistent 0.10.18: settings as a peer tab, stream/VRAM chat fixes.
Move settings beside Chat/Cards; stop fence ramble, sticky scroll, horny echo filter, and force-warm after Generate. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+24
-5
@@ -731,11 +731,17 @@
|
||||
.sa-settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.45rem;
|
||||
padding: 0.7rem 0.75rem;
|
||||
border-bottom: 1px solid color-mix(in srgb, currentColor 18%, transparent);
|
||||
background: color-mix(in srgb, currentColor 5%, transparent);
|
||||
max-height: 50vh;
|
||||
gap: 0.55rem;
|
||||
padding: 0.75rem 0.85rem 1rem;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
border-bottom: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#sa_view_settings {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sa-settings-head {
|
||||
@@ -743,12 +749,14 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sa-settings-tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sa-stab {
|
||||
@@ -1164,6 +1172,12 @@
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.sa-msg-body.sa-prose-live {
|
||||
display: block;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.sa-prose-h {
|
||||
font-weight: 650;
|
||||
letter-spacing: 0.01em;
|
||||
@@ -1485,6 +1499,11 @@
|
||||
background: color-mix(in srgb, currentColor 12%, transparent);
|
||||
}
|
||||
|
||||
#sa_btn_settings.sa-subtab-active {
|
||||
opacity: 1;
|
||||
background: color-mix(in srgb, currentColor 12%, transparent);
|
||||
}
|
||||
|
||||
.sa-view {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
|
||||
+221
-72
@@ -128,6 +128,7 @@
|
||||
exact: null,
|
||||
sessionExact: {},
|
||||
lastUserParamIntent: false,
|
||||
lastUserControlIntent: false,
|
||||
lastPatch: null,
|
||||
pendingSilentGen: false,
|
||||
enabledSkills: [],
|
||||
@@ -147,6 +148,7 @@
|
||||
streamEl: null,
|
||||
streamMeta: null,
|
||||
streamText: '',
|
||||
streamFenceDone: false,
|
||||
critiqueHopUsed: false,
|
||||
visionHopUsed: false,
|
||||
lastSystemChars: 0,
|
||||
@@ -215,6 +217,37 @@
|
||||
}
|
||||
}
|
||||
|
||||
let scrollMessagesRaf = 0;
|
||||
|
||||
/** Autoscroll only when already near the bottom — otherwise streaming fights the user's scroll (shakes). */
|
||||
function messagesNearBottom(thresholdPx = 96) {
|
||||
const box = $('sa_messages');
|
||||
if (!box) {
|
||||
return true;
|
||||
}
|
||||
return (box.scrollHeight - box.scrollTop - box.clientHeight) <= thresholdPx;
|
||||
}
|
||||
|
||||
function scrollMessagesToBottom({ force = false } = {}) {
|
||||
const box = $('sa_messages');
|
||||
if (!box) {
|
||||
return;
|
||||
}
|
||||
if (!force && !messagesNearBottom()) {
|
||||
return;
|
||||
}
|
||||
if (scrollMessagesRaf) {
|
||||
return;
|
||||
}
|
||||
scrollMessagesRaf = requestAnimationFrame(() => {
|
||||
scrollMessagesRaf = 0;
|
||||
const el = $('sa_messages');
|
||||
if (el && (force || messagesNearBottom(120))) {
|
||||
el.scrollTop = el.scrollHeight;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showChatEmptyIfIdle() {
|
||||
const box = $('sa_messages');
|
||||
const empty = $('sa_chat_empty');
|
||||
@@ -574,7 +607,7 @@
|
||||
return parts.join('');
|
||||
}
|
||||
|
||||
function setAssistantBody(div, text) {
|
||||
function setAssistantBody(div, text, { live = false } = {}) {
|
||||
if (!div) {
|
||||
return;
|
||||
}
|
||||
@@ -585,7 +618,15 @@
|
||||
div.appendChild(body);
|
||||
}
|
||||
const raw = text || '';
|
||||
body.classList.add('sa-prose');
|
||||
// While streaming, plain text — full HTML reformat every token reflows and shakes scroll.
|
||||
if (live) {
|
||||
body.classList.add('sa-prose', 'sa-prose-live');
|
||||
body.classList.remove('sa-prose-rich');
|
||||
body.textContent = raw;
|
||||
return;
|
||||
}
|
||||
body.classList.add('sa-prose', 'sa-prose-rich');
|
||||
body.classList.remove('sa-prose-live');
|
||||
const html = formatAssistantProseHtml(raw);
|
||||
if (html) {
|
||||
body.innerHTML = html;
|
||||
@@ -674,6 +715,56 @@
|
||||
|| /смени\s+(размер|aspect|соотношен)/i.test(t);
|
||||
}
|
||||
|
||||
function userTextMentionsControls(text) {
|
||||
const t = String(text || '');
|
||||
if (/\b(horny|controls?|preference[_\s-]?bias)\b/i.test(t)) {
|
||||
return true;
|
||||
}
|
||||
return cyrTokenRe('хорни|остынь|вкус|слайдер').test(t)
|
||||
|| /\/\s*(остынь|ostyn|horny-game)/i.test(t);
|
||||
}
|
||||
|
||||
function patchLooksLikeGeneration(patch) {
|
||||
if (!patch || typeof patch !== 'object') {
|
||||
return false;
|
||||
}
|
||||
if (patch.prompt != null || patch.loras || patch.aspect != null
|
||||
|| patch.width != null || patch.height != null || patch.steps != null
|
||||
|| patch.cfg != null || patch.seed != null) {
|
||||
return true;
|
||||
}
|
||||
return Array.isArray(patch.actions) && patch.actions.map(String).includes('generate');
|
||||
}
|
||||
|
||||
/** Drop Generate-patch echo of schema defaults that would wipe a user-tuned slider. */
|
||||
function filterControlPatch(incoming, patch) {
|
||||
const schema = state.config?.controls || {};
|
||||
const out = {};
|
||||
if (!incoming || typeof incoming !== 'object') {
|
||||
return out;
|
||||
}
|
||||
const likeGen = patchLooksLikeGeneration(patch) && !state.lastUserControlIntent;
|
||||
for (const [id, raw] of Object.entries(incoming)) {
|
||||
if (!schema[id]) {
|
||||
continue;
|
||||
}
|
||||
const n = Number(raw);
|
||||
if (!Number.isFinite(n)) {
|
||||
continue;
|
||||
}
|
||||
const def = Number(schema[id]?.default);
|
||||
const cur = getControlValue(id, Number.isFinite(def) ? def : n);
|
||||
if (Math.abs(n - cur) < 0.0005) {
|
||||
continue;
|
||||
}
|
||||
if (likeGen && Number.isFinite(def) && Math.abs(n - def) < 0.0005 && Math.abs(cur - def) > 0.0005) {
|
||||
continue;
|
||||
}
|
||||
out[id] = n;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function userTextMentionsParams(text) {
|
||||
const t = String(text || '');
|
||||
if (parseAspectFromUserText(t)) {
|
||||
@@ -1597,7 +1688,7 @@
|
||||
div.className = 'sa-msg assistant sa-welcome';
|
||||
div.innerHTML = WELCOME_HTML;
|
||||
box.appendChild(div);
|
||||
box.scrollTop = box.scrollHeight;
|
||||
scrollMessagesToBottom({ force: true });
|
||||
}
|
||||
|
||||
function chatUid() {
|
||||
@@ -2411,8 +2502,7 @@
|
||||
fillEmptyParamsFromExact();
|
||||
renderPersonaControls(data?.controls || {}, data?.control_values || data?.exact?.controls || {});
|
||||
syncPersonaDeleteButton(data?.persona_source || data?.personas?.find((p) => p.id === id)?.source);
|
||||
const settings = $('sa_settings');
|
||||
if (settings && !settings.hidden) {
|
||||
if (state.view === 'settings') {
|
||||
if (state.settingsTab === 'user') {
|
||||
refreshUserPrefs();
|
||||
}
|
||||
@@ -3598,17 +3688,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Persona Exact controls (model or user patch). Ignore persona_delete.
|
||||
// Persona Exact controls (model or user patch). Ignore default-echo inside Generate patches.
|
||||
if (patch.controls && typeof patch.controls === 'object' && !Array.isArray(patch.controls)) {
|
||||
const schema = state.config?.controls || {};
|
||||
const next = { ...(state.config?.control_values || state.exact?.controls || {}) };
|
||||
for (const [k, v] of Object.entries(patch.controls)) {
|
||||
if (schema[k]) {
|
||||
next[k] = v;
|
||||
}
|
||||
const filtered = filterControlPatch(patch.controls, patch);
|
||||
if (Object.keys(filtered).length) {
|
||||
const next = { ...(state.config?.control_values || state.exact?.controls || {}), ...filtered };
|
||||
savePersonaControls(filtered);
|
||||
renderPersonaControls(schema, next);
|
||||
}
|
||||
savePersonaControls(next);
|
||||
renderPersonaControls(schema, next);
|
||||
}
|
||||
|
||||
const acts = Array.isArray(patch.actions) ? patch.actions.map(String) : [];
|
||||
@@ -3728,11 +3816,15 @@
|
||||
});
|
||||
}
|
||||
|
||||
/** Re-load chat model after park. Returns a Promise (await after Generate so the next Send is warm). */
|
||||
function warmLlm() {
|
||||
/** Re-load chat model into VRAM. force=true after Generate even without park — Krea often evicts Ollama. */
|
||||
function warmLlm({ force = false } = {}) {
|
||||
return new Promise((resolve) => {
|
||||
const model = $('sa_model')?.value;
|
||||
if (!model || !state.llmParked || typeof genericRequest !== 'function') {
|
||||
if (!model || typeof genericRequest !== 'function') {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
if (!force && !state.llmParked) {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
@@ -3749,7 +3841,7 @@
|
||||
}
|
||||
resolve(!!ok);
|
||||
};
|
||||
// VL 7B cold-load can exceed a minute — don't time out the flag early.
|
||||
// VL cold-load can exceed a minute — don't time out the flag early.
|
||||
setTimeout(() => finish(false), 180000);
|
||||
genericRequest('AssistentWarmLlm', { baseUrl, model }, () => finish(true), 0, () => finish(false));
|
||||
});
|
||||
@@ -3864,12 +3956,15 @@
|
||||
const src = await waitForNewImage(prev);
|
||||
state.generating = false;
|
||||
setInterruptVisible(state.busy);
|
||||
// Auto-critique / next Send load the model themselves — skip warm if critique will run.
|
||||
// Krea Generate almost always evicts the VL chat weights from VRAM — even when Park LLM is off.
|
||||
state.expectColdLoad = true;
|
||||
const paneVisible = !!document.getElementById('swarm_assistent_root')?.offsetParent;
|
||||
if (state.llmParked && state.view === 'chat' && paneVisible && !$('sa_auto_critique')?.checked) {
|
||||
const willAutoCritique = !!$('sa_auto_critique')?.checked;
|
||||
// Auto-critique's own chat request will cold-load; otherwise preload before the user types.
|
||||
if (state.view === 'chat' && paneVisible && !willAutoCritique) {
|
||||
startBusyUi('warming');
|
||||
setStatus('Возвращаю LLM в GPU…');
|
||||
await warmLlm();
|
||||
await warmLlm({ force: true });
|
||||
}
|
||||
if (!state.busy) {
|
||||
stopBusyUi(src ? 'Generate готов' : 'Generate завершён (новое изображение не найдено)');
|
||||
@@ -4053,7 +4148,7 @@
|
||||
div.appendChild(buildCivitaiCards(civitaiResults));
|
||||
}
|
||||
box.appendChild(div);
|
||||
box.scrollTop = box.scrollHeight;
|
||||
scrollMessagesToBottom({ force: true });
|
||||
return div;
|
||||
}
|
||||
|
||||
@@ -4071,13 +4166,42 @@
|
||||
body.innerHTML = '<span class="sa-dots" aria-hidden="true"><i></i><i></i><i></i></span><span class="sa-typing-label">Waiting for the model…</span>';
|
||||
div.appendChild(body);
|
||||
box.appendChild(div);
|
||||
box.scrollTop = box.scrollHeight;
|
||||
scrollMessagesToBottom({ force: true });
|
||||
state.streamEl = div;
|
||||
state.streamMeta = meta || null;
|
||||
state.streamFenceDone = false;
|
||||
return div;
|
||||
}
|
||||
|
||||
function streamHasClosedPatchFence(text) {
|
||||
const t = String(text || '');
|
||||
if (!/```[\s\S]*```/.test(t)) {
|
||||
return false;
|
||||
}
|
||||
const { patch } = extractPatch(t);
|
||||
return !!patch;
|
||||
}
|
||||
|
||||
function trimToClosedPatchFence(text) {
|
||||
const t = String(text || '');
|
||||
const re = /```(?:json)?\s*([\s\S]*?)```/gi;
|
||||
let match;
|
||||
let lastEnd = -1;
|
||||
while ((match = re.exec(t)) !== null) {
|
||||
try {
|
||||
const obj = JSON.parse(match[1].trim());
|
||||
if (isPatchObject(obj) || isCardObject(obj)) {
|
||||
lastEnd = match.index + match[0].length;
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
return lastEnd > 0 ? t.slice(0, lastEnd).trimEnd() : t;
|
||||
}
|
||||
|
||||
function appendStreamDelta(delta) {
|
||||
if (state.streamFenceDone) {
|
||||
return;
|
||||
}
|
||||
if (!state.streamEl) {
|
||||
beginStreamMessage(state.streamMeta || undefined);
|
||||
}
|
||||
@@ -4085,7 +4209,7 @@
|
||||
if (state.streamEl.classList.contains('sa-typing')) {
|
||||
state.streamEl.classList.remove('sa-typing');
|
||||
state.streamText = '';
|
||||
setAssistantBody(state.streamEl, '');
|
||||
setAssistantBody(state.streamEl, '', { live: true });
|
||||
}
|
||||
state.gotDelta = true;
|
||||
state.expectColdLoad = false;
|
||||
@@ -4093,11 +4217,12 @@
|
||||
setBusyPhase('streaming');
|
||||
}
|
||||
state.streamText = (state.streamText || '') + (delta || '');
|
||||
setAssistantBody(state.streamEl, state.streamText);
|
||||
const box = $('sa_messages');
|
||||
if (box) {
|
||||
box.scrollTop = box.scrollHeight;
|
||||
if (streamHasClosedPatchFence(state.streamText)) {
|
||||
state.streamText = trimToClosedPatchFence(state.streamText);
|
||||
state.streamFenceDone = true;
|
||||
}
|
||||
setAssistantBody(state.streamEl, state.streamText, { live: true });
|
||||
scrollMessagesToBottom();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4107,6 +4232,7 @@
|
||||
state.streamEl = null;
|
||||
state.streamMeta = null;
|
||||
state.streamText = '';
|
||||
state.streamFenceDone = false;
|
||||
if (!el) {
|
||||
appendMessage('assistant', fullReply, null, civitaiResults, meta || undefined);
|
||||
return;
|
||||
@@ -4131,10 +4257,7 @@
|
||||
if (civitaiResults && civitaiResults.length) {
|
||||
el.appendChild(buildCivitaiCards(civitaiResults));
|
||||
}
|
||||
const box = $('sa_messages');
|
||||
if (box) {
|
||||
box.scrollTop = box.scrollHeight;
|
||||
}
|
||||
scrollMessagesToBottom();
|
||||
}
|
||||
|
||||
function buildCivitaiCards(results) {
|
||||
@@ -4517,7 +4640,7 @@
|
||||
if (paneW) {
|
||||
document.documentElement.style.setProperty('--sa-image-width', paneW);
|
||||
}
|
||||
if (view === 'cards' || view === 'chat') {
|
||||
if (view === 'cards' || view === 'chat' || view === 'settings') {
|
||||
state.view = view;
|
||||
}
|
||||
const boardTab = localStorage.getItem(LS_BOARD_TAB);
|
||||
@@ -4577,7 +4700,7 @@
|
||||
fill(LS_PACK, ui.pack, (v) => { if ($('sa_pack')) { $('sa_pack').value = v; } });
|
||||
fill(LS_PERSONA, ui.persona, (v) => { if ($('sa_persona')) { $('sa_persona').value = v; } });
|
||||
fill(LS_PANE_WIDTH, ui.pane_width, (v) => document.documentElement.style.setProperty('--sa-image-width', v));
|
||||
if (ui.view === 'cards' || ui.view === 'chat') {
|
||||
if (ui.view === 'cards' || ui.view === 'chat' || ui.view === 'settings') {
|
||||
fill(LS_VIEW, ui.view, (v) => { state.view = v; });
|
||||
}
|
||||
if (ui.board_tab === 'refs' || ui.board_tab === 'generate') {
|
||||
@@ -4817,16 +4940,26 @@
|
||||
controlsPointerDown = true;
|
||||
});
|
||||
const endPointer = () => {
|
||||
const v = Number(input.value);
|
||||
applyLocal(v);
|
||||
controlsPointerDown = false;
|
||||
// Discard mid-drag rebuilds that carried stale server defaults — keep local values.
|
||||
if (pendingControlsRender) {
|
||||
const pending = pendingControlsRender;
|
||||
const schema = pendingControlsRender.schema;
|
||||
pendingControlsRender = null;
|
||||
renderPersonaControls(pending.schema, pending.values);
|
||||
renderPersonaControls(
|
||||
schema,
|
||||
state.config?.control_values || state.exact?.controls || {},
|
||||
);
|
||||
}
|
||||
if (controlSaveTimer) {
|
||||
clearTimeout(controlSaveTimer);
|
||||
}
|
||||
controlSaveTimer = setTimeout(() => savePersonaControls({ [id]: v }), 50);
|
||||
};
|
||||
input.addEventListener('pointerup', endPointer);
|
||||
input.addEventListener('pointercancel', endPointer);
|
||||
// Live label while dragging; persist only on release (change) so mid-drag saves cannot snap back.
|
||||
// Live label while dragging; also persist on change for keyboard tweaks.
|
||||
input.addEventListener('input', () => {
|
||||
applyLocal(Number(input.value));
|
||||
});
|
||||
@@ -4857,6 +4990,7 @@
|
||||
setStatus('/остынь только для Leonid');
|
||||
return;
|
||||
}
|
||||
state.lastUserControlIntent = true;
|
||||
const schema = state.config?.controls || {};
|
||||
if (!schema.horny) {
|
||||
setStatus('У этой личности нет слайдера Хорни');
|
||||
@@ -4889,6 +5023,7 @@
|
||||
return;
|
||||
}
|
||||
const cur = getControlValue('horny', 35);
|
||||
state.lastUserControlIntent = true;
|
||||
await sendChat({
|
||||
skipSlash: true,
|
||||
skipAutoPack: true,
|
||||
@@ -5549,22 +5684,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function openSettings(tab) {
|
||||
const s = $('sa_settings');
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
s.hidden = false;
|
||||
setSettingsTab(tab || state.settingsTab || 'behavior');
|
||||
}
|
||||
|
||||
function closeSettings() {
|
||||
const s = $('sa_settings');
|
||||
if (s) {
|
||||
s.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
function fillKnobsFromConfig(data) {
|
||||
const asst = data?.assistant || state.config?.assistant || {};
|
||||
const exact = data?.exact || state.config?.exact || state.exact || {};
|
||||
@@ -6133,26 +6252,51 @@
|
||||
}
|
||||
|
||||
function setView(view) {
|
||||
state.view = view === 'cards' ? 'cards' : 'chat';
|
||||
if (view === 'cards') {
|
||||
state.view = 'cards';
|
||||
} else if (view === 'settings') {
|
||||
state.view = 'settings';
|
||||
} else {
|
||||
state.view = 'chat';
|
||||
}
|
||||
const chat = $('sa_view_chat');
|
||||
const cards = $('sa_view_cards');
|
||||
const settings = $('sa_view_settings');
|
||||
if (chat) {
|
||||
chat.hidden = state.view !== 'chat';
|
||||
}
|
||||
if (cards) {
|
||||
cards.hidden = state.view !== 'cards';
|
||||
}
|
||||
if (settings) {
|
||||
settings.hidden = state.view !== 'settings';
|
||||
}
|
||||
$('sa_tab_chat')?.classList.toggle('sa-subtab-active', state.view === 'chat');
|
||||
$('sa_tab_cards')?.classList.toggle('sa-subtab-active', state.view === 'cards');
|
||||
$('sa_tab_settings')?.classList.toggle('sa-subtab-active', state.view === 'settings');
|
||||
$('sa_btn_settings')?.classList.toggle('sa-subtab-active', state.view === 'settings');
|
||||
saveSettings();
|
||||
if (state.view === 'cards') {
|
||||
renderCardsList();
|
||||
} else if (state.llmParked && !state.generating) {
|
||||
// Back in the chat — bring the model home before the user hits Send.
|
||||
warmLlm();
|
||||
} else if (state.view === 'settings') {
|
||||
setSettingsTab(state.settingsTab || 'behavior');
|
||||
} else if ((state.llmParked || state.expectColdLoad) && !state.generating) {
|
||||
// Back in the chat — bring the model home (Krea may have evicted it).
|
||||
warmLlm({ force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function openSettings(tab) {
|
||||
if (tab) {
|
||||
state.settingsTab = tab;
|
||||
}
|
||||
setView('settings');
|
||||
}
|
||||
|
||||
function closeSettings() {
|
||||
setView('chat');
|
||||
}
|
||||
|
||||
function refreshPersonas() {
|
||||
loadConfig($('sa_persona')?.value || localStorage.getItem(LS_PERSONA) || 'neutral', (data) => {
|
||||
if (data?.personas) {
|
||||
@@ -7050,7 +7194,7 @@
|
||||
div.className = 'sa-msg assistant sa-system-note';
|
||||
div.textContent = text;
|
||||
box.appendChild(div);
|
||||
box.scrollTop = box.scrollHeight;
|
||||
scrollMessagesToBottom();
|
||||
}
|
||||
|
||||
function clipDebug(s, max) {
|
||||
@@ -7423,6 +7567,7 @@
|
||||
}
|
||||
if (!opts.fromAutoCritique && !opts.fromDownload && !opts.fromVisionHop && !opts.fromDebug) {
|
||||
state.lastUserParamIntent = userTextMentionsParams(text);
|
||||
state.lastUserControlIntent = userTextMentionsControls(text);
|
||||
state.pendingSilentGen = userAsksGenerate(text) || isSameButAspectRequest(text);
|
||||
}
|
||||
|
||||
@@ -7491,13 +7636,21 @@
|
||||
|
||||
const chatEpoch = bumpChatEpoch();
|
||||
state.busy = true;
|
||||
// This request will keep_alive 15m; clear parked only after we know load started.
|
||||
// Keep expectColdLoad so the UI can show a real GPU-load message if we just parked.
|
||||
if (!state.llmParked) {
|
||||
state.expectColdLoad = false;
|
||||
}
|
||||
// If Krea just ran, expectColdLoad stays true until warm / first token — don't clear it here.
|
||||
state.llmParked = false;
|
||||
setInterruptVisible(true);
|
||||
if (state.expectColdLoad && !opts.fromVisionHop && !opts.fromAutoCritique) {
|
||||
startBusyUi('warming');
|
||||
setStatus('Возвращаю LLM в GPU…');
|
||||
try {
|
||||
await warmLlm({ force: true });
|
||||
} catch (e) {
|
||||
console.warn('Assistent warm before send', e);
|
||||
}
|
||||
if (chatEpoch !== state.chatEpoch) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
startBusyUi(state.expectColdLoad ? 'loading' : 'thinking');
|
||||
saveSettings();
|
||||
|
||||
@@ -8015,6 +8168,7 @@
|
||||
|
||||
$('sa_tab_chat')?.addEventListener('click', () => setView('chat'));
|
||||
$('sa_tab_cards')?.addEventListener('click', () => setView('cards'));
|
||||
$('sa_tab_settings')?.addEventListener('click', () => openSettings(state.settingsTab || 'behavior'));
|
||||
$('sa_board_tab_gen')?.addEventListener('click', () => setBoardTab('generate'));
|
||||
$('sa_board_tab_refs')?.addEventListener('click', () => setBoardTab('refs'));
|
||||
$('sa_persona')?.addEventListener('change', onPersonaChanged);
|
||||
@@ -8025,15 +8179,11 @@
|
||||
$('sa_btn_card_generate')?.addEventListener('click', () => generateCardWithAssistent());
|
||||
$('sa_btn_card_save')?.addEventListener('click', () => saveCurrentCard());
|
||||
$('sa_btn_card_wanted')?.addEventListener('click', () => enqueueWantedOnly());
|
||||
|
||||
$('sa_btn_settings')?.addEventListener('click', () => {
|
||||
const s = $('sa_settings');
|
||||
if (s) {
|
||||
if (s.hidden) {
|
||||
openSettings(state.settingsTab || 'behavior');
|
||||
} else {
|
||||
closeSettings();
|
||||
}
|
||||
if (state.view === 'settings') {
|
||||
closeSettings();
|
||||
} else {
|
||||
openSettings(state.settingsTab || 'behavior');
|
||||
}
|
||||
});
|
||||
$('sa_settings_close')?.addEventListener('click', () => closeSettings());
|
||||
@@ -8105,8 +8255,7 @@
|
||||
return;
|
||||
}
|
||||
let closed = false;
|
||||
const settings = $('sa_settings');
|
||||
if (settings && !settings.hidden) {
|
||||
if (state.view === 'settings') {
|
||||
closeSettings();
|
||||
closed = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user