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:
@@ -36,7 +36,7 @@ async function run() {
|
||||
if (!exe) { console.error('Браузер не найден (Chrome/Edge)'); process.exit(1); }
|
||||
const books = db.prepare('SELECT slug, title FROM textbooks WHERE is_active = 1 ORDER BY slug').all();
|
||||
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 (?, ?, ?, ?)');
|
||||
const ins = db.prepare('INSERT INTO textbook_chunks (slug, textbook_title, section_title, text, section_ref) VALUES (?, ?, ?, ?, ?)');
|
||||
|
||||
const token = authToken();
|
||||
if (!token) { console.error('Не удалось выпустить токен (нет пользователя или JWT_SECRET)'); process.exit(1); }
|
||||
@@ -59,7 +59,7 @@ async function run() {
|
||||
await page.evaluate(id => { const c = document.querySelector('.psel-card[data-id="' + id + '"]'); if (c) c.click(); }, s.id);
|
||||
await sleep(550);
|
||||
const text = await page.evaluate(() => { const a = document.querySelector('.sec.active'); return a ? a.innerText.replace(/\s+/g, ' ').trim() : ''; });
|
||||
if (text && text.length >= 80) chunks.push({ section: s.name.slice(0, 160), text: text.slice(0, 2000) });
|
||||
if (text && text.length >= 80) chunks.push({ section: s.name.slice(0, 160), text: text.slice(0, 2000), ref: s.id });
|
||||
} catch (e) {}
|
||||
}
|
||||
} else {
|
||||
@@ -70,7 +70,7 @@ async function run() {
|
||||
|
||||
if (chunks.length) {
|
||||
del.run(b.slug);
|
||||
for (const c of chunks) ins.run(b.slug, b.title || b.slug, c.section, c.text);
|
||||
for (const c of chunks) ins.run(b.slug, b.title || b.slug, c.section, c.text, c.ref || null);
|
||||
okBooks++; totalChunks += chunks.length;
|
||||
console.log(` ${b.slug}: ${chunks.length}`);
|
||||
} else {
|
||||
|
||||
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user