feat(assistant): индексация системы из админки — Квантик знает актуальные модули
Кнопка «Сохранить и проиндексировать систему» в /admin#assistant собирает снимок: - статус модулей по фича-флагам (что ВКЛЮЧЕНО/ВЫКЛЮЧЕНО сейчас) + каталог разделов; - редактируемое «Описание системы» админа. Снимок кладётся в app_settings.assistant_system_kb и подмешивается в ответы: systemContext(q) ищет по знаниям (стем-префикс под русскую морфологию) и добавляет в контекст — Квантик опирается на актуальное состояние и не предлагает отключённое. Бэкенд: MODULE_CATALOG + buildSystemKb + indexSystem (POST /admin/assistant/index-system), saveAssistant(+systemDoc), getAssistant(+systemDoc/Count/At), systemContext в ask и askStream. Клиент: LS.adminAssistantIndexSystem. Без миграции (хранение в app_settings). Проверено: логика снимка/поиска 5/5, node --check всех файлов. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -396,6 +396,22 @@ function ragContext(q) {
|
||||
} catch (e) { return empty; }
|
||||
}
|
||||
|
||||
/* Знания о системе (индексируются из админки): статус модулей + описание.
|
||||
* Поиск по ключевым словам вопроса; добавляется в контекст ответа. */
|
||||
function _systemKb() { try { const r = _setting('assistant_system_kb'); return r ? (JSON.parse(r) || []) : []; } catch (e) { return []; } }
|
||||
function systemContext(q) {
|
||||
const kb = _systemKb(); if (!kb.length) return '';
|
||||
// стем-префикс (русская морфология): отбрасываем окончание, но не короче 4 симв.
|
||||
// «флешкартами»→«флешкарт», «лабораторию»→«лаборато» ловят «флешкарты»/«лаборатория».
|
||||
const stem = (w) => (w.length >= 7 ? w.slice(0, Math.max(4, w.length - 3))
|
||||
: w.length >= 5 ? w.slice(0, Math.max(4, w.length - 2)) : w);
|
||||
const words = q.toLowerCase().split(/[^a-zа-яё0-9]+/i).filter(w => w.length >= 4).map(stem);
|
||||
if (!words.length) return '';
|
||||
const scored = kb.map(c => { const t = ((c.title || '') + ' ' + (c.text || '')).toLowerCase(); return { c, s: words.reduce((a, w) => a + (t.indexOf(w) >= 0 ? 1 : 0), 0) }; })
|
||||
.filter(x => x.s > 0).sort((a, b) => b.s - a.s).slice(0, 4);
|
||||
return scored.map(x => x.c.text).join('\n');
|
||||
}
|
||||
|
||||
/* Суточный счётчик использования (для админки). */
|
||||
const USAGE_FIELDS = { model_calls: 1, cache_hits: 1, faq: 1 };
|
||||
function bumpUsage(field) {
|
||||
@@ -633,6 +649,8 @@ async function ask(req, res) {
|
||||
|
||||
let context = pageCtx;
|
||||
if (rag.text) context = (context ? context + '\n\n' : '') + 'Из учебников:\n' + rag.text;
|
||||
const sysCtx = systemContext(q);
|
||||
if (sysCtx) context = (context ? context + '\n\n' : '') + 'Состояние платформы (актуально, опирайся на это о модулях):\n' + sysCtx;
|
||||
|
||||
const socratic = _socraticFor(req.user && req.user.role, mode, q);
|
||||
let r = { text: null, error: 'network' };
|
||||
@@ -693,6 +711,8 @@ async function askStream(req, res) {
|
||||
|
||||
let context = pageCtx;
|
||||
if (rag.text) context = (context ? context + '\n\n' : '') + 'Из учебников:\n' + rag.text;
|
||||
const sysCtx = systemContext(q);
|
||||
if (sysCtx) context = (context ? context + '\n\n' : '') + 'Состояние платформы (актуально, опирайся на это о модулях):\n' + sysCtx;
|
||||
const socratic = _socraticFor(req.user && req.user.role, mode, q);
|
||||
const { msgs, cap } = buildAskMessages(q, hits, context, history, req.user && req.user.role, mode, mem, socratic);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user