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:
Maxim Dolgolyov
2026-06-24 21:27:53 +03:00
parent 64ea552cf8
commit 08da26afca
5 changed files with 108 additions and 2 deletions
+22
View File
@@ -139,6 +139,18 @@
'<div style="font-size:.78rem;color:#8a94a6">Оценки (30 дн): ' + (f.up || 0) + ' лайков, ' + (f.down || 0) + ' дизлайков' + ((f.recent || []).length ? '. Не помогло: ' + f.recent.map(function (x) { return '«' + esc(String(x.q || '').slice(0, 40)) + '»'; }).join(', ') : '') + '</div>';
host.appendChild(sc);
// ── Знания о системе (индексация модулей/флагов + описание) ──
var skb = document.createElement('div');
skb.className = 'perm-card'; skb.style.cssText = 'flex-direction:column;align-items:stretch;gap:9px;margin-top:14px';
var _skAt = cfg.systemKbAt ? (function () { try { return new Date(cfg.systemKbAt).toLocaleString('ru'); } catch (e) { return ''; } })() : '';
var _skInfo = cfg.systemKbCount ? (cfg.systemKbCount + ' фрагментов' + (_skAt ? ' · ' + _skAt : '')) : 'ещё не индексировалось';
skb.innerHTML =
'<div class="perm-label"><i data-lucide="boxes" style="width:14px;height:14px;vertical-align:-2px;margin-right:6px"></i>Знания о системе для Квантика</div>' +
'<div class="perm-desc">Снимок включённых модулей + каталог разделов + ваше описание индексируются, чтобы Квантик знал актуальное состояние платформы и не предлагал отключённое. Запускайте после смены фича-флагов.</div>' +
'<textarea id="asst-sysdoc" rows="5" placeholder="Опишите модули/правила платформы своими словами (необязательно) — это тоже попадёт в знания Квантика…" style="' + IN + ';resize:vertical">' + esc(cfg.systemDoc || '') + '</textarea>' +
'<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center"><button id="asst-index-sys" class="asst-ib primary">Сохранить и проиндексировать систему</button><span id="asst-sysidx-st" style="font-size:.78rem;color:#8a94a6">' + esc(_skInfo) + '</span></div>';
host.appendChild(skb);
if (window.lucide) lucide.createIcons();
var Q = function (s) { return host.querySelector(s); };
@@ -280,6 +292,16 @@
try { var r = await LS.adminReindexTextbooks(); Q('#asst-chunks').textContent = ((r && r.chunks) || 0) + ' фрагментов'; LS.toast('Готово', 'success'); }
catch (e) { LS.toast('Ошибка индексации', 'error'); } finally { btn.disabled = false; btn.textContent = 'Переиндексировать учебники'; }
});
Q('#asst-index-sys').addEventListener('click', async function () {
var btn = Q('#asst-index-sys'), st = Q('#asst-sysidx-st'), old = btn.textContent; btn.disabled = true; btn.textContent = 'Индексирую…';
try {
await LS.adminSaveAssistant({ systemDoc: Q('#asst-sysdoc').value });
var r = await LS.adminAssistantIndexSystem();
st.textContent = ((r && r.count) || 0) + ' фрагментов · только что';
LS.toast('Система проиндексирована', 'success');
} catch (e) { LS.toast('Ошибка индексации', 'error'); }
finally { btn.disabled = false; btn.textContent = old; }
});
// ── Сканер моделей ──
var scanProvId = null;