feat(assistant): источники в ответах, режим-наставник, оценки, утренний бриф

- Источники: RAG возвращает sources (slug/§/ref), под ответом ссылка «по учебнику
  X, §N» на параграф (миграция 064: section_ref в textbook_chunks; headless-индексатор
  его заполняет). Статический индексатор теперь не затирает headless-данные.
- Режим-наставник: переключатель Ответ/Подсказка/Проверить решение в «Спроси»
  (mode в ask + промпт); на карточке экзамена кнопка «Подсказка» (mode hint).
- Оценка ответов: лайк/дизлайк под ответом (assistant_feedback) + сводка в админке.
- Утренний бриф на дашборде: «занимался N из 5 дн + план на сегодня».

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-04 19:38:47 +03:00
parent 0119ea0f15
commit 4224a22092
9 changed files with 155 additions and 40 deletions
+4 -2
View File
@@ -48,17 +48,19 @@ function reindex() {
let books;
try { books = db.prepare('SELECT slug, title, html_path FROM textbooks WHERE is_active = 1').all(); }
catch (e) { return { error: 'textbooks table missing', chunks: 0 }; }
const delAll = db.prepare('DELETE FROM textbook_chunks');
// Замещаем чанки только тех книг, что реально распарсились — не трогаем
// данные, наполненные headless-индексатором (JS-рендеримые учебники).
const del = db.prepare('DELETE FROM textbook_chunks WHERE slug = ?');
const ins = db.prepare('INSERT INTO textbook_chunks (slug, textbook_title, section_title, text) VALUES (?, ?, ?, ?)');
let total = 0, files = 0;
delAll.run();
for (const b of books) {
const fp = path.join(TEXTBOOKS_DIR, b.html_path || '');
let html;
try { html = fs.readFileSync(fp, 'utf8'); } catch (e) { continue; }
files++;
const chunks = chunksFromHtml(html);
if (!chunks.length) continue;
del.run(b.slug);
for (const c of chunks) { ins.run(b.slug, b.title || b.slug, c.section || '', c.text); total++; }
}
return { books: books.length, files, chunks: total };