Ship Assistent disk persist, park LLM, and memory UI cleanup.

Split the extension into partials, persist chats on the data volume, park/warm the chat model around Generate, and drop dual raw/persona dump paths.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-22 01:03:40 +03:00
co-authored by Cursor
parent 880e2dbea2
commit 8702b64e12
12 changed files with 1192 additions and 114 deletions
+84 -6
View File
@@ -155,6 +155,8 @@
activeChatId: null,
restoringChat: false,
chatsPanelOpen: false,
chatsQuery: '',
chatsSearchHits: null,
slashIndex: 0,
llmParked: false,
memoryRows: [],
@@ -1769,6 +1771,23 @@
}
}
function chatMatchesQuery(chat, q) {
if (!q) {
return true;
}
const title = String(chat?.title || '').toLowerCase();
if (title.includes(q)) {
return true;
}
const msgs = chat?.messages || [];
for (const m of msgs) {
if (String(m?.content || '').toLowerCase().includes(q)) {
return true;
}
}
return false;
}
function renderChatsList() {
const root = $('sa_chats_list');
if (!root) {
@@ -1776,19 +1795,28 @@
}
root.innerHTML = '';
syncHistoryBadge();
const chats = (state.chats || [])
const q = (state.chatsQuery || '').trim().toLowerCase();
let chats = (state.chats || [])
.slice()
.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0))
.filter((c) => (c.messages || []).length > 0);
.filter((c) => (c.messages || []).length > 0 || (c.messages_count || 0) > 0);
if (q) {
const local = chats.filter((c) => chatMatchesQuery(c, q));
const seen = new Set(local.map((c) => c.id));
const extra = (state.chatsSearchHits || []).filter((h) => h && h.id && !seen.has(h.id));
chats = local.concat(extra);
}
if (!chats.length) {
root.innerHTML = '<div class="sa-chats-empty">Пока пусто. Напиши что-нибудь в чат — он появится здесь. Переключение вернёт и сообщения, и параметры Generate.</div>';
root.innerHTML = q
? '<div class="sa-chats-empty">Ничего не нашлось.</div>'
: '<div class="sa-chats-empty">Пока пусто. Напиши что-нибудь в чат — он появится здесь. Переключение вернёт и сообщения, и параметры Generate.</div>';
return;
}
for (const c of chats) {
const row = document.createElement('div');
row.className = 'sa-chat-row' + (c.id === state.activeChatId ? ' sa-chat-row-active' : '');
row.dataset.id = c.id;
const n = (c.messages || []).length;
const n = (c.messages || []).length || Number(c.messages_count) || 0;
const bits = [];
if (c.params?.width && c.params?.height) {
bits.push(`${c.params.width}×${c.params.height}`);
@@ -1818,6 +1846,11 @@
btn?.setAttribute('aria-expanded', state.chatsPanelOpen ? 'true' : 'false');
if (state.chatsPanelOpen) {
saveActiveChatToStore();
const search = $('sa_chats_search');
if (search) {
search.value = state.chatsQuery || '';
search.focus();
}
renderChatsList();
}
}
@@ -1878,7 +1911,23 @@
return;
}
saveActiveChatToStore({ dropEmpty: true });
const chat = findChat(id);
let chat = findChat(id);
if (!chat || !(chat.messages || []).length) {
try {
const full = await diskPersist()?.getChat?.(id);
if (full) {
const idx = (state.chats || []).findIndex((c) => c.id === id);
if (idx >= 0) {
state.chats[idx] = full;
} else {
state.chats.unshift(full);
}
chat = full;
}
} catch (e) {
console.warn('Assistent: getChat failed', id, e);
}
}
if (!chat) {
setStatus('Чат не найден');
return;
@@ -3346,7 +3395,9 @@
if (!state.busy) {
stopBusyUi(src ? 'Generate готов' : 'Generate завершён (новое изображение не найдено)');
}
if (state.view === 'chat') {
// Auto-critique loads the model itself on the next request — don't pay for it twice.
const paneVisible = !!document.getElementById('swarm_assistent_root')?.offsetParent;
if (state.view === 'chat' && paneVisible && !$('sa_auto_critique')?.checked) {
warmLlm();
}
if (src) {
@@ -5140,6 +5191,9 @@
? `Карточка Assistent сохранена · ${data.path}`
: `Черновик + wanted · ${data.path}`);
refreshInventory(() => renderCardsList());
if (enqueue || !data.installed) {
refreshWantedQueue();
}
},
0,
(err) => setCardStatus(String(err || 'Ошибка сохранения')),
@@ -5164,6 +5218,7 @@
},
(data) => {
setCardStatus(data.already ? 'Уже в wanted' : `Wanted → ${data.path}`);
refreshWantedQueue();
},
0,
(err) => setCardStatus(String(err || 'Ошибка enqueue')),
@@ -6275,6 +6330,7 @@
splitter.classList.remove('sa-dragging');
document.body.style.cursor = '';
document.body.style.userSelect = '';
saveUiStateToDisk();
});
}
@@ -6394,6 +6450,28 @@
switchToChat(id);
}
});
let chatsSearchTimer = null;
$('sa_chats_search')?.addEventListener('input', () => {
const q = ($('sa_chats_search')?.value || '').trim();
state.chatsQuery = q;
if (!q) {
state.chatsSearchHits = null;
renderChatsList();
return;
}
renderChatsList();
clearTimeout(chatsSearchTimer);
chatsSearchTimer = setTimeout(async () => {
try {
const hits = await diskPersist()?.searchChats?.(q);
if ((state.chatsQuery || '') !== q) {
return;
}
state.chatsSearchHits = Array.isArray(hits) ? hits : [];
renderChatsList();
} catch (e) { /* ignore */ }
}, 220);
});
$('sa_tab_chat')?.addEventListener('click', () => setView('chat'));
$('sa_tab_cards')?.addEventListener('click', () => setView('cards'));