feat(assistant): админ-тумблер, расширенный FAQ, подсказки «что спросить»
- Отдельный фича-флаг 'assistant' (вместо reuse 'pet'): админ может включать/ выключать помощника в Управление → фичи, независимо от питомца. Дефолт ON. - FAQ расширен (~50 -> ~60): профиль/пароль, колоды/массовый импорт/SRS, прогресс по предмету, поиск, экзамен9, питомец, «без класса», «о чём спросить». - В «Спроси Квантика» — чипы с примерами вопросов (что можно спросить). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -525,7 +525,7 @@ function getFeatures(_req, res) {
|
|||||||
function updateFeatures(req, res) {
|
function updateFeatures(req, res) {
|
||||||
const allowed = ['crossword', 'hangman', 'pet', 'red_book', 'collection',
|
const allowed = ['crossword', 'hangman', 'pet', 'red_book', 'collection',
|
||||||
'flashcards', 'knowledge_map', 'board', 'biochem', 'live_quiz', 'classroom',
|
'flashcards', 'knowledge_map', 'board', 'biochem', 'live_quiz', 'classroom',
|
||||||
'gamification'];
|
'gamification', 'assistant'];
|
||||||
const updates = req.body;
|
const updates = req.body;
|
||||||
const stmt = db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)");
|
const stmt = db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)");
|
||||||
const getOld = db.prepare("SELECT value FROM app_settings WHERE key = ?");
|
const getOld = db.prepare("SELECT value FROM app_settings WHERE key = ?");
|
||||||
|
|||||||
@@ -62,6 +62,21 @@ const FAQ = [
|
|||||||
// ── Сам помощник ──
|
// ── Сам помощник ──
|
||||||
{ id: 'assistant-off', q: 'Как выключить помощника?', a: 'Профиль → «Настройки» → «Помощник Квантик». Можно выключить совсем или скрыть конкретные подсказки кнопкой «Не показывать».', url: '/profile', keywords: ['помощник','выключ','ассистент','подсказк','квантик','скрыть'] },
|
{ id: 'assistant-off', q: 'Как выключить помощника?', a: 'Профиль → «Настройки» → «Помощник Квантик». Можно выключить совсем или скрыть конкретные подсказки кнопкой «Не показывать».', url: '/profile', keywords: ['помощник','выключ','ассистент','подсказк','квантик','скрыть'] },
|
||||||
{ id: 'assistant-tour', q: 'Как пройти тур заново?', a: 'Нажми на меня и выбери «Тур по системе» — проведу по разделам ещё раз.', url: null, keywords: ['тур','онбординг','обзор','заново'] },
|
{ id: 'assistant-tour', q: 'Как пройти тур заново?', a: 'Нажми на меня и выбери «Тур по системе» — проведу по разделам ещё раз.', url: null, keywords: ['тур','онбординг','обзор','заново'] },
|
||||||
|
{ id: 'assistant-can', q: 'О чём тебя можно спросить?', a: 'Спрашивай «как сделать…»: вырезать учебник, создать карточки, начать тест, сохранить доску, разобрать ошибки, включить тёмную тему. Ещё я ищу уроки, курсы и файлы по платформе.', url: null, keywords: ['умеешь','помощь','спросить','что можешь','help','команд','о чём'] },
|
||||||
|
// ── Профиль / безопасность ──
|
||||||
|
{ id: 'password', q: 'Как сменить пароль?', a: 'Профиль → «Безопасность» → смена пароля.', url: '/profile', keywords: ['пароль','сменить','смена','безопасн','password'] },
|
||||||
|
{ id: 'avatar', q: 'Как поменять имя или аватар?', a: 'Профиль → «Аккаунт»: отображаемое имя и аватар.', url: '/profile', keywords: ['имя','аватар','профиль','фото','никнейм'] },
|
||||||
|
// ── Флешкарты подробнее ──
|
||||||
|
{ id: 'deck-create', q: 'Как создать колоду карточек?', a: 'В «Флешкартах» нажми создать колоду и задай название — затем добавляй карточки.', url: '/flashcards', keywords: ['колод','создать','набор','карточк'] },
|
||||||
|
{ id: 'deck-bulk', q: 'Как добавить много карточек сразу?', a: 'В колоде есть «Добавить список» — вставь пары «вопрос — ответ» построчно, можно с картинками.', url: '/flashcards', keywords: ['массов','список','импорт','много','сразу'] },
|
||||||
|
{ id: 'srs', q: 'Как работает интервальное повторение?', a: 'После ответа карточка получает срок следующего показа: лёгкие — реже, трудные — чаще. Так запоминается надолго.', url: '/flashcards', keywords: ['интервал','повторен','srs','память','алгоритм'] },
|
||||||
|
// ── Прогресс / поиск / экзамен9 ──
|
||||||
|
{ id: 'progress-subject', q: 'Как посмотреть прогресс по предмету?', a: 'На дашборде есть виджет «Прогресс по предметам» (средний процент) и история результатов.', url: '/dashboard', keywords: ['прогресс','предмет','процент','средн','статистик'] },
|
||||||
|
{ id: 'find-topic', q: 'Как быстро найти тему или урок?', a: 'Поиск (Ctrl+K) ищет уроки, курсы, файлы и вопросы по названию. Я тоже поищу, если спросишь.', url: null, keywords: ['найти','тема','урок','поиск','быстро'] },
|
||||||
|
{ id: 'exam9', q: 'Что такое «Подготовка к экзамену 9»?', a: 'Тренажёр для ЦТ/ЦЭ по математике 9: задания по темам, режимы экзамена и тренировки, разбор ошибок.', url: '/exam-prep/math9', keywords: ['экзамен 9','math9','цт','цэ','подготовка','9 класс'] },
|
||||||
|
// ── Питомец / без класса ──
|
||||||
|
{ id: 'feed-pet', q: 'Как покормить и погладить питомца?', a: 'На странице питомца есть кнопки: погладить (раз в минуту) и покормить (раз в 30 минут) — за это монеты и XP.', url: '/pet', keywords: ['покорм','погладит','питомец','еда','уход'] },
|
||||||
|
{ id: 'no-class', q: 'Можно заниматься без класса?', a: 'Да — учебники, тесты, карточки, лаборатория и питомец доступны и без класса. Класс нужен для заданий от учителя.', url: '/dashboard', keywords: ['без класса','самостоят','один','сам'] },
|
||||||
];
|
];
|
||||||
|
|
||||||
/* ── Источники проактивных подсказок (всё уже есть в БД) ──────────────── */
|
/* ── Источники проактивных подсказок (всё уже есть в БД) ──────────────── */
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ app.use('/api/classroom', classroomRoutes);
|
|||||||
app.use('/api/games', gamesRoutes);
|
app.use('/api/games', gamesRoutes);
|
||||||
app.use('/api/knowledge-map', requireFeature('knowledge_map'), knowledgeMapRoutes);
|
app.use('/api/knowledge-map', requireFeature('knowledge_map'), knowledgeMapRoutes);
|
||||||
app.use('/api/pet', requireFeature('pet'), petRoutes);
|
app.use('/api/pet', requireFeature('pet'), petRoutes);
|
||||||
app.use('/api/assistant', requireFeature('pet'), require('./routes/assistant'));
|
app.use('/api/assistant', requireFeature('assistant'), require('./routes/assistant'));
|
||||||
app.use('/api/collection', requireFeature('collection'), collectionRoutes);
|
app.use('/api/collection', requireFeature('collection'), collectionRoutes);
|
||||||
app.use('/api/red-book', requireFeature('red_book'), redBookRoutes);
|
app.use('/api/red-book', requireFeature('red_book'), redBookRoutes);
|
||||||
app.use('/api/biochem', requireFeature('biochem'), require('./routes/biochem'));
|
app.use('/api/biochem', requireFeature('biochem'), require('./routes/biochem'));
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
{ key: 'biochem', label: 'Биохимия', desc: 'Молекулярный редактор, задачи на построение молекул и реакции', icon: 'flask-conical' },
|
{ key: 'biochem', label: 'Биохимия', desc: 'Молекулярный редактор, задачи на построение молекул и реакции', icon: 'flask-conical' },
|
||||||
{ key: 'live_quiz', label: 'Живая викторина', desc: 'Синхронная викторина в реальном времени для всего класса', icon: 'radio' },
|
{ key: 'live_quiz', label: 'Живая викторина', desc: 'Синхронная викторина в реальном времени для всего класса', icon: 'radio' },
|
||||||
{ key: 'classroom', label: 'Онлайн-уроки (classroom)', desc: 'Синхронные онлайн-уроки с доской и видео', icon: 'video' },
|
{ key: 'classroom', label: 'Онлайн-уроки (classroom)', desc: 'Синхронные онлайн-уроки с доской и видео', icon: 'video' },
|
||||||
|
{ key: 'assistant', label: 'Помощник «Квантик»', desc: 'Плавающий помощник: подсказки по разделам, напоминания и «Спроси Квантика»', icon: 'sparkles' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const FS_FEATURES = [
|
const FS_FEATURES = [
|
||||||
|
|||||||
@@ -293,6 +293,9 @@
|
|||||||
'.asst-ans-link{display:inline-block;margin-top:4px;color:#9B5DE5;font-weight:700;font-size:.78rem;text-decoration:none;}',
|
'.asst-ans-link{display:inline-block;margin-top:4px;color:#9B5DE5;font-weight:700;font-size:.78rem;text-decoration:none;}',
|
||||||
'.asst-ans-sec{font-size:.66rem;font-weight:800;color:#8a94a6;text-transform:uppercase;letter-spacing:.03em;margin:12px 0 2px;}',
|
'.asst-ans-sec{font-size:.66rem;font-weight:800;color:#8a94a6;text-transform:uppercase;letter-spacing:.03em;margin:12px 0 2px;}',
|
||||||
'.asst-ans-box{max-height:46vh;overflow:auto;}',
|
'.asst-ans-box{max-height:46vh;overflow:auto;}',
|
||||||
|
'.asst-chips{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:6px;}',
|
||||||
|
'.asst-chip{border:1px solid #e2e8f0;background:#f8fafc;border-radius:99px;padding:5px 10px;font:600 .72rem Manrope,sans-serif;color:#475569;cursor:pointer;text-align:left;}',
|
||||||
|
'.asst-chip:hover{border-color:#9B5DE5;color:#9B5DE5;}',
|
||||||
'.asst-empty{font-size:.82rem;color:#8a94a6;padding:6px 0;}',
|
'.asst-empty{font-size:.82rem;color:#8a94a6;padding:6px 0;}',
|
||||||
// на мобиле сайдбар — выезжающая шторка, контент во всю ширину → к левому краю
|
// на мобиле сайдбар — выезжающая шторка, контент во всю ширину → к левому краю
|
||||||
'@media(max-width:768px){.asst-root,.app-layout ~ .asst-root,.app-layout.sb-collapsed ~ .asst-root{left:12px;bottom:18px;}.asst-fab{width:48px;height:48px;}}',
|
'@media(max-width:768px){.asst-root,.app-layout ~ .asst-root,.app-layout.sb-collapsed ~ .asst-root{left:12px;bottom:18px;}.asst-fab{width:48px;height:48px;}}',
|
||||||
@@ -351,10 +354,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ── «Спроси Квантика» ───────────────────────────────────────────────── */
|
/* ── «Спроси Квантика» ───────────────────────────────────────────────── */
|
||||||
|
var SUGGESTIONS = [
|
||||||
|
'Как вырезать кусок учебника?',
|
||||||
|
'Как создать карточки?',
|
||||||
|
'Как начать тест?',
|
||||||
|
'Как сохранить доску себе?',
|
||||||
|
'Где мои домашние задания?',
|
||||||
|
'Как включить тёмную тему?',
|
||||||
|
];
|
||||||
function openAsk() {
|
function openAsk() {
|
||||||
|
var chips = '<div class="asst-chips">' +
|
||||||
|
SUGGESTIONS.map(function (q) { return '<button class="asst-chip" type="button">' + esc(q) + '</button>'; }).join('') +
|
||||||
|
'</div>';
|
||||||
openBubble(
|
openBubble(
|
||||||
'<div class="asst-name">Спроси Квантика</div>' +
|
'<div class="asst-name">Спроси Квантика</div>' +
|
||||||
'<input class="asst-ask-in" type="text" placeholder="Например: как сохранить кусок учебника" maxlength="200" />' +
|
'<input class="asst-ask-in" type="text" placeholder="Например: как сохранить кусок учебника" maxlength="200" />' +
|
||||||
|
chips +
|
||||||
'<div class="asst-ans-box"></div>', {});
|
'<div class="asst-ans-box"></div>', {});
|
||||||
var inp = bubble.querySelector('.asst-ask-in');
|
var inp = bubble.querySelector('.asst-ask-in');
|
||||||
var box = bubble.querySelector('.asst-ans-box');
|
var box = bubble.querySelector('.asst-ans-box');
|
||||||
@@ -362,6 +377,9 @@
|
|||||||
var t = null;
|
var t = null;
|
||||||
inp.addEventListener('input', function () { clearTimeout(t); t = setTimeout(function () { runAsk(inp.value, box); }, 350); });
|
inp.addEventListener('input', function () { clearTimeout(t); t = setTimeout(function () { runAsk(inp.value, box); }, 350); });
|
||||||
inp.addEventListener('keydown', function (e) { if (e.key === 'Enter') { clearTimeout(t); runAsk(inp.value, box); } });
|
inp.addEventListener('keydown', function (e) { if (e.key === 'Enter') { clearTimeout(t); runAsk(inp.value, box); } });
|
||||||
|
bubble.querySelectorAll('.asst-chip').forEach(function (c) {
|
||||||
|
c.addEventListener('click', function () { inp.value = c.textContent; runAsk(c.textContent, box); inp.focus(); });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
function runAsk(q, box) {
|
function runAsk(q, box) {
|
||||||
q = (q || '').trim();
|
q = (q || '').trim();
|
||||||
|
|||||||
Reference in New Issue
Block a user