feat(assistant): админ-панель LLM (ключ/URL/модель/тест) + многоходовой чат
Админка (Управление → игры/фичи): карточка «Помощник Квантик — модель» — пресеты провайдеров, URL/модель, поле ключа, кнопки Сохранить/Проверить/ Очистить ключ, индикатор статуса. Конфиг в app_settings (без рестарта), откат на ENV/дефолты; нет ключа → автоматически FAQ-режим. Эндпоинты GET/PUT/POST /api/admin/assistant(/test), admin-only. «Спроси Квантика» теперь многоходовой чат: история диалога (последние 6 реплик) уходит модели, ответы рендерятся как чат-лента, кнопка «Очистить». Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -877,6 +877,51 @@ function broadcast(req, res) {
|
||||
res.json({ ok: true, sent: users.length });
|
||||
}
|
||||
|
||||
/* ── Ассистент «Квантик»: конфиг LLM из админки ──────────────────────── */
|
||||
const ASSISTANT_PRESETS = [
|
||||
{ name: 'Google Gemini', url: 'https://generativelanguage.googleapis.com/v1beta/openai/chat/completions', model: 'gemini-2.5-flash' },
|
||||
{ name: 'Groq', url: 'https://api.groq.com/openai/v1/chat/completions', model: 'llama-3.3-70b-versatile' },
|
||||
{ name: 'OpenRouter', url: 'https://openrouter.ai/api/v1/chat/completions', model: 'meta-llama/llama-3.3-70b-instruct:free' },
|
||||
{ name: 'Ollama (локально)', url: 'http://localhost:11434/v1/chat/completions', model: 'qwen2.5:3b' },
|
||||
];
|
||||
function _aset(k) { const r = db.prepare('SELECT value FROM app_settings WHERE key = ?').get(k); return r && r.value != null ? r.value : null; }
|
||||
|
||||
function getAssistant(_req, res) {
|
||||
const url = _aset('assistant_llm_url') || process.env.ASSISTANT_LLM_URL || ASSISTANT_PRESETS[1].url;
|
||||
const model = _aset('assistant_llm_model') || process.env.ASSISTANT_LLM_MODEL || ASSISTANT_PRESETS[1].model;
|
||||
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 });
|
||||
}
|
||||
|
||||
function saveAssistant(req, res) {
|
||||
const set = (k, v) => db.prepare('INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)').run(k, v);
|
||||
const del = (k) => db.prepare('DELETE FROM app_settings WHERE key = ?').run(k);
|
||||
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 (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 });
|
||||
}
|
||||
|
||||
async function testAssistant(req, res) {
|
||||
const a = require('./assistantController');
|
||||
const cfg = a.llmConfig();
|
||||
const b = req.body || {};
|
||||
const override = {
|
||||
url: (typeof b.url === 'string' && b.url.trim()) ? b.url.trim() : cfg.url,
|
||||
model: (typeof b.model === 'string' && b.model.trim()) ? b.model.trim() : cfg.model,
|
||||
key: (typeof b.key === 'string' && b.key.trim()) ? b.key.trim() : cfg.key,
|
||||
};
|
||||
override.local = /\/\/(localhost|127\.0\.0\.1)/.test(override.url);
|
||||
override.on = !!(override.key || override.local);
|
||||
const r = await a.pingLLM(override);
|
||||
res.json(r);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getStats, getOverview, globalSearch,
|
||||
getUsers, updateRole, getUserSessions, getAllSessions, getSessionDetail,
|
||||
@@ -886,4 +931,5 @@ module.exports = {
|
||||
getSecurityLog, clearSecurityLog,
|
||||
getTopics, createTopic, updateTopic, deleteTopic,
|
||||
broadcast,
|
||||
getAssistant, saveAssistant, testAssistant,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user