feat(assistant): RAG по учебникам, кэш+счётчик, режим учителя
- RAG: индексатор scripts/index-textbooks.js → textbook_chunks (миграция 063); ask() подмешивает релевантные куски учебников (LIKE-скоринг). Покрывает учебники со статическим текстом; JS-рендеримые — через контекст страницы. Админка: тумблер RAG + кнопка «Переиндексировать» + число фрагментов. - Кэш ответов (assistant_cache, 7 дней, только «чистые» вопросы без контекста/ истории) + суточный счётчик (assistant_usage: ИИ/кэш/FAQ) в админке. - Режим учителя: роль в /context, системный промпт для учителей (задания, план урока, учительские инструменты), подсказки-чипы для учителей. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -892,7 +892,18 @@ function getAssistant(_req, res) {
|
||||
const dbKey = _aset('assistant_llm_key');
|
||||
const hasKey = !!(dbKey || process.env.ASSISTANT_LLM_KEY);
|
||||
const local = /\/\/(localhost|127\.0\.0\.1)/.test(url);
|
||||
res.json({ url, model, hasKey, keyFromEnv: !dbKey && !!process.env.ASSISTANT_LLM_KEY, active: !!(hasKey || local), presets: ASSISTANT_PRESETS });
|
||||
let chunks = 0, usage = { model_calls: 0, cache_hits: 0, faq: 0 }, usage30 = { model_calls: 0, cache_hits: 0, faq: 0 };
|
||||
try { chunks = db.prepare('SELECT COUNT(*) n FROM textbook_chunks').get().n; } catch (e) {}
|
||||
try {
|
||||
const t = db.prepare('SELECT model_calls, cache_hits, faq FROM assistant_usage WHERE day = ?').get(new Date().toISOString().slice(0, 10));
|
||||
if (t) usage = t;
|
||||
const s = db.prepare("SELECT COALESCE(SUM(model_calls),0) model_calls, COALESCE(SUM(cache_hits),0) cache_hits, COALESCE(SUM(faq),0) faq FROM assistant_usage WHERE day > date('now','-30 days')").get();
|
||||
if (s) usage30 = s;
|
||||
} catch (e) {}
|
||||
res.json({
|
||||
url, model, hasKey, keyFromEnv: !dbKey && !!process.env.ASSISTANT_LLM_KEY, active: !!(hasKey || local),
|
||||
rag: _aset('assistant_rag') !== '0', chunks, usage, usage30, presets: ASSISTANT_PRESETS,
|
||||
});
|
||||
}
|
||||
|
||||
function saveAssistant(req, res) {
|
||||
@@ -901,12 +912,23 @@ function saveAssistant(req, res) {
|
||||
const b = req.body || {};
|
||||
if (typeof b.url === 'string') set('assistant_llm_url', b.url.trim().slice(0, 300));
|
||||
if (typeof b.model === 'string') set('assistant_llm_model', b.model.trim().slice(0, 120));
|
||||
if (typeof b.rag === 'boolean') set('assistant_rag', b.rag ? '1' : '0');
|
||||
if (b.clearKey) del('assistant_llm_key');
|
||||
else if (typeof b.key === 'string' && b.key.trim()) set('assistant_llm_key', b.key.trim().slice(0, 400));
|
||||
audit(req, 'assistant.config', 'assistant', b.clearKey ? 'ключ очищен' : 'обновлено');
|
||||
res.json({ ok: true });
|
||||
}
|
||||
|
||||
/* POST /api/admin/assistant/reindex — переиндексировать учебники для RAG */
|
||||
function reindexTextbooks(req, res) {
|
||||
try {
|
||||
const { reindex } = require('../../scripts/index-textbooks');
|
||||
const r = reindex();
|
||||
audit(req, 'assistant.reindex', 'assistant', `chunks:${r.chunks || 0}`);
|
||||
res.json(r);
|
||||
} catch (e) { res.status(500).json({ error: e.message || 'reindex failed' }); }
|
||||
}
|
||||
|
||||
async function testAssistant(req, res) {
|
||||
const a = require('./assistantController');
|
||||
const cfg = a.llmConfig();
|
||||
@@ -931,5 +953,5 @@ module.exports = {
|
||||
getSecurityLog, clearSecurityLog,
|
||||
getTopics, createTopic, updateTopic, deleteTopic,
|
||||
broadcast,
|
||||
getAssistant, saveAssistant, testAssistant,
|
||||
getAssistant, saveAssistant, testAssistant, reindexTextbooks,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user