feat(assistant): долгая память об ученике (персонализация)

Производный профиль (без LLM): слабые предметы, трудные темы экзамена,
цель/дата, серия — из test_sessions/exam_attempts/exam_user_plan. Подмешивается
в системный промпт → персональные ответы; такие не кэшируются глобально.
Заметки: таблица assistant_memory + фоновый LLM-экстрактор (дросселирован),
дедуп + лимит 15. Панель ученика «Что я о тебе помню» (профиль + заметки,
удаление). Админ-тумблер. API GET/DELETE /assistant/memory (/:id под
authMiddleware, владелец проверяется в хендлере).

Заодно: сверка стабильного baseline route-auth 56→66 (долг от branch-merge,
хук не идёт на merge) — новых незащищённых маршрутов не добавлено.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-11 22:51:04 +03:00
parent 5417083f88
commit 9cfb7d1c3b
8 changed files with 166 additions and 10 deletions
@@ -940,6 +940,7 @@ function getAssistant(_req, res) {
res.json({
providers, activeId, active,
rag: _aset('assistant_rag') !== '0', examButtons: _aset('assistant_exam_buttons') === '1',
memory: _aset('assistant_memory') !== '0',
chunks, usage, usage30, feedback, failover, presets: ASSISTANT_PRESETS, kiloModels: KILO_MODELS,
});
}
@@ -950,6 +951,7 @@ function saveAssistant(req, res) {
const b = req.body || {};
if (typeof b.rag === 'boolean') set('assistant_rag', b.rag ? '1' : '0');
if (typeof b.examButtons === 'boolean') set('assistant_exam_buttons', b.examButtons ? '1' : '0');
if (typeof b.memory === 'boolean') set('assistant_memory', b.memory ? '1' : '0');
if (b.dismissFailover) { try { db.prepare("DELETE FROM app_settings WHERE key = 'assistant_failover'").run(); } catch (e) {} }
audit(req, 'assistant.config', 'assistant', 'настройки');
res.json({ ok: true });