feat(assistant): Квантик-ассистент — Ф0/Ф1 + «Спроси» (правиловый движок)

Плавающий помощник на всех страницах (через sidebar.js + inject в учебник):
контекстные подсказки по странице, проактивные напоминания из реальных данных
(домашка с дедлайном, карточки к повторению, серия под угрозой, квест дня),
поздравления (левелап/серия) и панель «Спроси Квантика» (поиск по FAQ + точка
расширения под локальную модель). Консервативно: дневной лимит, кулдауны,
«не показывать», выключатель в профиле. Лицо — pet-sprite, данные — /api/pet.

Бэкенд: миграция 062 (assistant_enabled + assistant_seen, cross-device «видел»),
GET /api/assistant/context, POST seen/dismiss/ask, PATCH settings — гейт фичи 'pet'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-04 16:17:37 +03:00
parent 26c0ac0e58
commit 3f8009c59d
8 changed files with 599 additions and 0 deletions
+34
View File
@@ -1285,6 +1285,27 @@
</div>
</div>
<!-- Помощник Квантик -->
<div class="p-card">
<div class="p-card-header">
<div class="p-card-icon"><i data-lucide="sparkles" style="width:15px;height:15px"></i></div>
<div>
<div class="p-card-title">Помощник Квантик</div>
<div class="p-card-sub">Подсказки и напоминания по системе</div>
</div>
</div>
<div class="pref-row">
<div class="pref-row-info">
<div class="pref-row-label">Показывать помощника</div>
<div class="pref-row-desc">Плавающий Квантик с подсказками на страницах</div>
</div>
<label class="pref-toggle">
<input type="checkbox" id="pref-assistant" onchange="prefAssistant(this.checked)">
<span class="pref-toggle-track"></span>
</label>
</div>
</div>
<!-- Внешний вид -->
<div class="p-card">
<div class="p-card-header">
@@ -2075,6 +2096,11 @@
/* ── Настройки (prefs tab) ── */
function loadPrefs() {
// Ассистент Квантик (независимо от наличия LS.sfx)
const asstEl = document.getElementById('pref-assistant');
if (asstEl && window.LS && LS.assistantContext) {
LS.assistantContext().then(c => { asstEl.checked = !(c && c.enabled === false); }).catch(() => {});
}
if (!window.LS || !LS.sfx) return;
const sfx = LS.sfx;
const setChk = (id, v) => { const el = document.getElementById(id); if (el) el.checked = v; };
@@ -2108,6 +2134,14 @@
if (v) setTimeout(() => LS.sfx.play('success'), 100);
}
function prefAssistant(v) {
if (!window.LS || !LS.assistantSettings) return;
LS.assistantSettings({ enabled: !!v })
.then(() => { if (LS.toast) LS.toast(v ? 'Помощник включён' : 'Помощник отключён', 'success'); })
.catch(() => { if (LS.toast) LS.toast('Не удалось сохранить', 'error'); });
}
window.prefAssistant = prefAssistant;
function prefSfxVolume(v) {
if (!window.LS || !LS.sfx) return;
LS.sfx.setVolume(v / 100);