From 7c0ccc728244f0887124a88acc137ba9f074ec2d Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Wed, 24 Jun 2026 23:04:43 +0300 Subject: [PATCH] =?UTF-8?q?feat(assistant):=20=D0=B4=D0=B8=D0=B0=D0=BB?= =?UTF-8?q?=D0=BE=D0=B3=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D1=8F=D0=B5?= =?UTF-8?q?=D1=82=D1=81=D1=8F=20=D0=BC=D0=B5=D0=B6=D0=B4=D1=83=20=D0=B7?= =?UTF-8?q?=D0=B0=D1=85=D0=BE=D0=B4=D0=B0=D0=BC=D0=B8=20(localStorage,=20p?= =?UTF-8?q?er-user)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _chat теперь персистится в localStorage (ключ asst_chat_, последние 30 сообщений) при каждом ответе и восстанавливается при загрузке виджета. Живёт, пока ученик сам не нажмёт «Очистить» (чистит и хранилище). Сноска обновлена. Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/js/assistant.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/frontend/js/assistant.js b/frontend/js/assistant.js index e03a65f..2caa739 100644 --- a/frontend/js/assistant.js +++ b/frontend/js/assistant.js @@ -546,6 +546,11 @@ var SUGGESTIONS = ['Как вырезать кусок учебника?', 'Как создать карточки?', 'Как начать тест?', 'Объясни теорему Пифагора', 'Где мои домашние задания?', 'Как включить тёмную тему?']; var TEACHER_SUGGESTIONS = ['Как создать класс и выдать задание?', 'Идеи заданий по теме…', 'Составь план урока по теме…', 'Как работает журнал и аналитика?', 'Как провести онлайн-урок?']; var _chat = []; // многоходовой диалог: [{role:'user'|'assistant', content}] + // Диалог переживает обновление/переходы (localStorage, per-user), пока ученик сам не нажмёт «Очистить». + function _chatKey() { try { var u = LS.getUser && LS.getUser(); return u && u.id ? 'asst_chat_' + u.id : null; } catch (e) { return null; } } + function saveChat() { var k = _chatKey(); if (k) lsSet(k, JSON.stringify(_chat.slice(-30))); } + function loadChat() { var k = _chatKey(); if (!k) return; try { var a = JSON.parse(lsGet(k) || '[]'); if (Array.isArray(a)) _chat = a.filter(function (m) { return m && (m.role === 'user' || m.role === 'assistant') && typeof m.content === 'string'; }); } catch (e) {} } + function clearChatStore() { var k = _chatKey(); if (k) try { localStorage.removeItem(k); } catch (e) {} } function msgEl(role) { var d = document.createElement('div'); d.className = 'asst-msg asst-msg-' + role; return d; } function renderChat(chatEl) { chatEl.innerHTML = ''; @@ -593,7 +598,7 @@ '
' + '
' + '
' + _svg('') + - 'Держу в голове ход беседы — последние ~14 сообщений. Что обсуждали раньше — учту, давнее плавно забывается. «Очистить» — начать с чистого листа.
', {}); + 'Держу в голове ход беседы — последние ~14 сообщений. Диалог сохраняется между заходами и обновлениями страницы, пока ты сам не нажмёшь «Очистить».', {}); var inp = bubble.querySelector('.asst-ask-in'); var chatEl = bubble.querySelector('.asst-chat'); var chipsEl = bubble.querySelector('.asst-chips'); @@ -622,7 +627,7 @@ }); }); var clr = bubble.querySelector('[data-a="clear"]'); - if (clr) clr.onclick = function () { _chat = []; openAsk(); }; + if (clr) clr.onclick = function () { _chat = []; clearChatStore(); openAsk(); }; var memBtn = bubble.querySelector('[data-a="mem"]'); if (memBtn) memBtn.onclick = openMemory; if (prefill && prefill.q) go(prefill.q, prefill.context, prefill.mode); @@ -671,7 +676,7 @@ LS.imageGen(prompt).then(function (r) { ph.remove(); var d = msgEl('assistant'); - if (r && r.url) { d.innerHTML = ''; _chat.push({ role: 'assistant', content: '[картинка]', img: r.url }); setNameFace('ecstatic'); } + if (r && r.url) { d.innerHTML = ''; _chat.push({ role: 'assistant', content: '[картинка]', img: r.url }); saveChat(); setNameFace('ecstatic'); } else { d.textContent = 'Не получилось нарисовать.'; setNameFace('sad'); } chatEl.appendChild(d); chatEl.scrollTop = chatEl.scrollHeight; }).catch(function (err) { @@ -722,7 +727,7 @@ var sources = done.sources || meta.sources || []; var content = isModel ? (full || done.answer) : ((ansArr[0] && (ansArr[0].q + '\n' + ansArr[0].a)) || 'Не нашёл точного ответа. Попробуй переформулировать или поищи (Ctrl+K).'); ensureMsg(); richEl.classList.remove('asst-streaming'); - _chat.push({ role: 'assistant', content: content }); + _chat.push({ role: 'assistant', content: content }); saveChat(); renderRich(richEl, content); if (isModel && sources.length) { var sc = document.createElement('div'); sc.className = 'asst-src'; @@ -786,7 +791,7 @@ var sources = r0.sources || []; var found = (res[1] && res[1].results) || []; var content = model || (ans[0] && (ans[0].q + '\n' + ans[0].a)) || 'Не нашёл точного ответа. Попробуй переформулировать или поищи (Ctrl+K).'; - _chat.push({ role: 'assistant', content: content }); + _chat.push({ role: 'assistant', content: content }); saveChat(); var d = msgEl('assistant'); d.innerHTML = '
'; chatEl.appendChild(d); renderRich(d.querySelector('.asst-rich'), content); // источники (RAG) @@ -1058,6 +1063,7 @@ SRV = ctx || {}; _role = (SRV && SRV.role) || 'student'; if (SRV.enabled === false) return; // выключено пользователем + loadChat(); // восстановить диалог прошлой сессии (per-user) return (LS.api ? LS.api('/api/pet') : Promise.resolve(null)).then(function (pet) { PET = pet || null; ensurePet(mount);