feat(assistant): диалог сохраняется между заходами (localStorage, per-user)
_chat теперь персистится в localStorage (ключ asst_chat_<uid>, последние 30 сообщений) при каждом ответе и восстанавливается при загрузке виджета. Живёт, пока ученик сам не нажмёт «Очистить» (чистит и хранилище). Сноска обновлена. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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 @@
|
||||
'<div class="asst-ask-row"><input class="asst-ask-in" type="text" placeholder="' + MODE_PH.answer + '" maxlength="500" />' +
|
||||
'<button class="asst-send" type="button" title="Отправить" aria-label="Отправить"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 2 11 13"/><path d="M22 2 15 22l-4-9-9-4 20-7z"/></svg></button></div>' +
|
||||
'<div class="asst-memnote">' + _svg('<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M12 7v5l4 2"/>') +
|
||||
'<span>Держу в голове ход беседы — последние <b>~14 сообщений</b>. Что обсуждали раньше — учту, давнее плавно забывается. «Очистить» — начать с чистого листа.</span></div>', {});
|
||||
'<span>Держу в голове ход беседы — последние <b>~14 сообщений</b>. Диалог сохраняется между заходами и обновлениями страницы, пока ты сам не нажмёшь «Очистить».</span></div>', {});
|
||||
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 = '<img src="' + r.url + '" alt="" style="width:100%;border-radius:10px;display:block">'; _chat.push({ role: 'assistant', content: '[картинка]', img: r.url }); setNameFace('ecstatic'); }
|
||||
if (r && r.url) { d.innerHTML = '<img src="' + r.url + '" alt="" style="width:100%;border-radius:10px;display:block">'; _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 = '<div class="asst-rich"></div>'; 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);
|
||||
|
||||
Reference in New Issue
Block a user