feat(assistant): контент по всем разделам, FAQ x5, поиск по платформе, умный проактив
Контент: контекстные подсказки на ВСЕ разделы (PAGE_HINTS), «Совет дня» (ротация), FAQ расширен ~10 -> ~50 записей по всем фичам. «Спроси Квантика» теперь ищет и по платформе (LS.globalSearch) рядом с FAQ. Умный проактив: weakSubject (слабый предмет по тестам, /api/assistant/context) -> «потренируемся» и daily-plan (план на сегодня из квестов и карточек к повторению). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+124
-34
@@ -34,6 +34,21 @@
|
||||
if (p === '/my-materials') return 'materials';
|
||||
if (p === '/lab') return 'lab';
|
||||
if (p === '/theory' || p.indexOf('/course') === 0 || p.indexOf('/lesson') === 0) return 'theory';
|
||||
if (p === '/textbooks') return 'textbooks';
|
||||
if (p === '/library') return 'library';
|
||||
if (p === '/knowledge-map') return 'knowledge';
|
||||
if (p === '/biochem') return 'biochem';
|
||||
if (p === '/red-book') return 'redbook';
|
||||
if (p === '/crossword') return 'crossword';
|
||||
if (p === '/hangman') return 'hangman';
|
||||
if (p === '/collection') return 'collection';
|
||||
if (p === '/analytics') return 'analytics';
|
||||
if (p === '/gradebook') return 'gradebook';
|
||||
if (p === '/lesson-history') return 'lessonhistory';
|
||||
if (p === '/question-bank') return 'qbank';
|
||||
if (p === '/classes') return 'classes';
|
||||
if (p === '/homework') return 'homework';
|
||||
if (p === '/pet') return 'pet';
|
||||
return 'other';
|
||||
}
|
||||
var PAGE = pageId();
|
||||
@@ -56,32 +71,7 @@
|
||||
/* ── каталог правил ──────────────────────────────────────────────────── */
|
||||
// scope: page | proactive | celebration. when(C) → bool. action(C) → {label,url}|null.
|
||||
var RULES = [
|
||||
// — контекстные —
|
||||
{ id: 'p-textbook', scope: 'page', cooldownDays: 14, maxShows: 2,
|
||||
when: function () { return PAGE === 'textbook'; },
|
||||
text: function () { return 'Любой кусок страницы можно вырезать картинкой в «Мои материалы» — кнопка «Вырезать область» внизу.'; },
|
||||
action: function () { return null; } },
|
||||
{ id: 'p-exam', scope: 'page', cooldownDays: 14, maxShows: 2,
|
||||
when: function () { return PAGE === 'exam'; },
|
||||
text: function () { return 'Три режима: экзамен (как на ЦТ/ЦЭ), тренировка (с разбором) и случайный. Выбирай под свою цель.'; },
|
||||
action: function () { return null; } },
|
||||
{ id: 'p-flashcards', scope: 'page', cooldownDays: 14, maxShows: 2,
|
||||
when: function () { return PAGE === 'flashcards'; },
|
||||
text: function () { return 'Формулы в карточках вводятся через KaTeX-палитру, а ещё можно добавить картинку.'; },
|
||||
action: function () { return null; } },
|
||||
{ id: 'p-materials', scope: 'page', cooldownDays: 14, maxShows: 2,
|
||||
when: function () { return PAGE === 'materials'; },
|
||||
text: function () { return 'Раскладывай материалы по папкам, а поверх фото можно рисовать — кнопка с карандашом.'; },
|
||||
action: function () { return null; } },
|
||||
{ id: 'p-lab', scope: 'page', cooldownDays: 14, maxShows: 2,
|
||||
when: function () { return PAGE === 'lab'; },
|
||||
text: function () { return 'Симуляции запускаются прямо в браузере — ничего ставить не нужно.'; },
|
||||
action: function () { return null; } },
|
||||
{ id: 'p-dashboard', scope: 'page', cooldownDays: 30, maxShows: 1,
|
||||
when: function () { return PAGE === 'dashboard'; },
|
||||
text: function () { return 'Виджеты на дашборде можно включать и переставлять под себя.'; },
|
||||
action: function () { return null; } },
|
||||
|
||||
// — контекстные подсказки генерируются из PAGE_HINTS ниже —
|
||||
// — проактивные (из реальных данных) —
|
||||
{ id: 'hw-overdue', scope: 'proactive', cooldownDays: 1, maxShows: 30,
|
||||
when: function () { return !!(SRV && SRV.homework && SRV.homework.overdue); },
|
||||
@@ -102,13 +92,34 @@
|
||||
{ id: 'streak-risk', scope: 'proactive', cooldownDays: 1, maxShows: 60,
|
||||
when: function () { return !!(PET && PET.streakCurrent >= 1 && !activeToday() && new Date().getHours() >= 18); },
|
||||
text: function () { return 'Серия ' + PET.streakCurrent + ' ' + plural(PET.streakCurrent, 'день', 'дня', 'дней') + ' под угрозой — позанимайся сегодня, чтобы не потерять.'; },
|
||||
action: function () { return { label: 'Заниматься', url: '/exam-prep' }; } },
|
||||
action: function () { return { label: 'Заниматься', url: '/exam-prep/math9' }; } },
|
||||
{ id: 'quest', scope: 'proactive', cooldownDays: 1, maxShows: 90,
|
||||
when: function () { return !!(quest(true) && new Date().getHours() >= 16); },
|
||||
text: function () { var q = quest(true); return 'Остался квест дня: «' + (q.label || 'задание') + '».'; },
|
||||
action: function () { return null; } },
|
||||
{ id: 'weak-subject', scope: 'proactive', cooldownDays: 2, maxShows: 40,
|
||||
when: function () { return !!(SRV && SRV.weakSubject); },
|
||||
text: function () { var w = SRV.weakSubject; return 'Слабее всего идёт ' + (w.name || 'предмет') + ' (' + w.avg + '%). Потренируемся?'; },
|
||||
action: function () { return { label: 'Потренироваться', url: '/exam-prep/math9' }; } },
|
||||
{ id: 'daily-plan', scope: 'proactive', cooldownDays: 1, maxShows: 120,
|
||||
when: function () { return PAGE === 'dashboard' && dailyPlan().length > 0; },
|
||||
text: function () { return 'План на сегодня: ' + dailyPlan().join(', ') + '. Начнём?'; },
|
||||
action: function () { var p = dailyPlanAction(); return p; } },
|
||||
];
|
||||
|
||||
/* План на сегодня — из дневных квестов и карточек к повторению */
|
||||
function dailyPlan() {
|
||||
var out = [];
|
||||
var qs = (PET && PET.quests) || [];
|
||||
qs.forEach(function (q) { if (!q.done && q.label) out.push(q.label.toLowerCase()); });
|
||||
if (SRV && SRV.dueCards > 0) out.push('повторить ' + SRV.dueCards + ' ' + plural(SRV.dueCards, 'карточку', 'карточки', 'карточек'));
|
||||
return out.slice(0, 3);
|
||||
}
|
||||
function dailyPlanAction() {
|
||||
if (SRV && SRV.dueCards > 0) return { label: 'Повторить карточки', url: '/flashcards' };
|
||||
return { label: 'К занятиям', url: '/exam-prep/math9' };
|
||||
}
|
||||
|
||||
function plural(n, one, few, many) {
|
||||
var m10 = n % 10, m100 = n % 100;
|
||||
if (m10 === 1 && m100 !== 11) return one;
|
||||
@@ -116,6 +127,69 @@
|
||||
return many;
|
||||
}
|
||||
|
||||
/* ── контентные подсказки по страницам (генерируются в RULES) ────────── */
|
||||
var PAGE_HINTS = {
|
||||
textbook: ['Любой кусок страницы можно вырезать картинкой в «Мои материалы» — кнопка «Вырезать область» внизу.',
|
||||
'Ссылка из адресной строки ведёт прямо на этот параграф — удобно делиться.'],
|
||||
exam: ['Три режима: экзамен (как на ЦТ/ЦЭ), тренировка (с разбором) и случайный.',
|
||||
'После теста доступен разбор: правильные ответы и решения.'],
|
||||
flashcards: ['Формулы вводятся через KaTeX-палитру, можно добавить и картинку.',
|
||||
'Система сама напоминает, какие карточки пора повторить.'],
|
||||
materials: ['Раскладывай материалы по папкам, а поверх фото можно рисовать — кнопка с карандашом.',
|
||||
'Клик по картинке открывает её в просмотрщике прямо на странице.'],
|
||||
lab: ['Симуляции запускаются прямо в браузере — ничего ставить не нужно.',
|
||||
'У оптической скамьи есть режим «Конструктор» — собирай системы из линз и зеркал.'],
|
||||
dashboard: ['Виджеты на дашборде можно включать и переставлять под себя.',
|
||||
'Блок «Активность» показывает серию и занятия за недели.'],
|
||||
textbooks: ['Внутри учебника — главы и параграфы; прочитанное отмечается и идёт в прогресс.'],
|
||||
library: ['В библиотеке — файлы и материалы от учителя; ищи по предмету.'],
|
||||
knowledge: ['Карта знаний показывает связи тем — видно, что уже освоено.'],
|
||||
biochem: ['Интерактивные модели молекул и реакций — крути и разбирай.'],
|
||||
redbook: ['Красная книга — карточки видов; листай и запоминай.'],
|
||||
crossword: ['Кроссворд по терминам предмета — отгадывай и получай XP.'],
|
||||
hangman: ['«Виселица» по терминам — угадывай слово по буквам.'],
|
||||
collection: ['Коллекция — твои собранные награды и предметы.'],
|
||||
board: ['Инструмент «выделение» двигает и поворачивает объекты; есть фигуры, формулы и линейка.',
|
||||
'Страницу доски можно сохранить в «Мои материалы».'],
|
||||
lessonhistory: ['Архив онлайн-уроков — доска и заметки сохраняются после занятия.'],
|
||||
qbank: ['Банк вопросов — основа для тестов и заданий.'],
|
||||
theory: ['Можно создать одиночный «Быстрый урок» без курса.',
|
||||
'Прогресс по урокам отслеживается — продолжай с того же места.'],
|
||||
classes: ['Создавай классы и выдавай задания; ученики входят по коду.'],
|
||||
analytics: ['Аналитика: динамика результатов и слабые темы.'],
|
||||
gradebook: ['Журнал — оценки и сдачи по заданиям класса.'],
|
||||
homework: ['Здесь все задания и дедлайны; работу можно загрузить прямо тут.'],
|
||||
pet: ['Гладь и корми Квантика, закрывай квесты — за это XP и монеты.'],
|
||||
};
|
||||
Object.keys(PAGE_HINTS).forEach(function (pg) {
|
||||
PAGE_HINTS[pg].forEach(function (text, idx) {
|
||||
RULES.push({
|
||||
id: 'p-' + pg + '-' + idx, scope: 'page', cooldownDays: 12, maxShows: 2,
|
||||
when: (function (p) { return function () { return PAGE === p; }; })(pg),
|
||||
text: (function (t) { return function () { return t; }; })(text),
|
||||
action: function () { return null; },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/* ── «Совет дня» — ротация фич-открытий (дашборд) ────────────────────── */
|
||||
var TIPS = [
|
||||
'фрагмент учебника можно сохранить картинкой в «Мои материалы».',
|
||||
'в «Теории» есть «Быстрый урок» — один урок без создания курса.',
|
||||
'поверх сохранённого фото можно рисовать пометки.',
|
||||
'формулы в карточках и на доске вводятся через KaTeX.',
|
||||
'серия за ежедневные занятия делает Квантика счастливее.',
|
||||
'поиск по платформе — Ctrl+K: уроки, курсы, файлы и вопросы.',
|
||||
'заметку из «Мои материалы» можно превратить во флешкарту.',
|
||||
'тёмную тему и звуки включишь в Профиль → Настройки.',
|
||||
];
|
||||
RULES.push({
|
||||
id: 'tip-daily', scope: 'page', cooldownDays: 1, maxShows: 200,
|
||||
when: function () { return PAGE === 'dashboard'; },
|
||||
text: function () { return 'А ты знал: ' + TIPS[(new Date().getDate()) % TIPS.length]; },
|
||||
action: function () { return null; },
|
||||
});
|
||||
|
||||
/* ── выбор подсказки ─────────────────────────────────────────────────── */
|
||||
function eligible(rule) {
|
||||
if (SUPPRESS_PAGE && rule.scope !== 'celebration') return false;
|
||||
@@ -217,6 +291,8 @@
|
||||
'.asst-ans:first-of-type{border-top:none;}',
|
||||
'.asst-ans-q{font-weight:700;color:#0F172A;margin-bottom:2px;}',
|
||||
'.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-box{max-height:46vh;overflow:auto;}',
|
||||
'.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;}}',
|
||||
@@ -291,13 +367,27 @@
|
||||
q = (q || '').trim();
|
||||
if (q.length < 3) { box.innerHTML = ''; return; }
|
||||
box.innerHTML = '<div class="asst-empty">Ищу…</div>';
|
||||
LS.assistantAsk(q).then(function (r) {
|
||||
var ans = (r && r.answers) || [];
|
||||
if (!ans.length) { box.innerHTML = '<div class="asst-empty">Не нашёл точного ответа. Попробуй переформулировать.</div>'; return; }
|
||||
box.innerHTML = ans.map(function (a) {
|
||||
return '<div class="asst-ans"><div class="asst-ans-q">' + esc(a.q) + '</div>' + esc(a.a) +
|
||||
(a.url ? '<br><a class="asst-ans-link" href="' + esc(a.url) + '">Открыть</a>' : '') + '</div>';
|
||||
}).join('');
|
||||
Promise.all([
|
||||
LS.assistantAsk(q).catch(function () { return { answers: [] }; }),
|
||||
(LS.globalSearch ? LS.globalSearch(q, 'all', 4) : Promise.resolve({ results: [] })).catch(function () { return { results: [] }; }),
|
||||
]).then(function (res) {
|
||||
var ans = (res[0] && res[0].answers) || [];
|
||||
var found = (res[1] && res[1].results) || [];
|
||||
var html = '';
|
||||
if (ans.length) {
|
||||
html += ans.map(function (a) {
|
||||
return '<div class="asst-ans"><div class="asst-ans-q">' + esc(a.q) + '</div>' + esc(a.a) +
|
||||
(a.url ? '<br><a class="asst-ans-link" href="' + esc(a.url) + '">Открыть</a>' : '') + '</div>';
|
||||
}).join('');
|
||||
}
|
||||
if (found.length) {
|
||||
html += '<div class="asst-ans-sec">На платформе</div>';
|
||||
html += found.slice(0, 4).map(function (f) {
|
||||
return '<div class="asst-ans"><a class="asst-ans-link" style="margin-top:0" href="' + esc(f.url || '#') + '">' + esc(f.title || 'Без названия') + '</a>' +
|
||||
(f.subtitle ? ' <span style="color:#8a94a6">— ' + esc(f.subtitle) + '</span>' : '') + '</div>';
|
||||
}).join('');
|
||||
}
|
||||
box.innerHTML = html || '<div class="asst-empty">Ничего не нашёл. Попробуй переформулировать.</div>';
|
||||
}).catch(function () { box.innerHTML = '<div class="asst-empty">Не удалось получить ответ.</div>'; });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user