ваше сообщение коммита

This commit is contained in:
2025-06-26 20:34:58 +03:00
parent 25f1286c93
commit 1f4024d5be
36 changed files with 1709 additions and 967 deletions

View File

@@ -0,0 +1 @@
ALTER TABLE ai_providers_settings ADD COLUMN embedding_model VARCHAR(128);

View File

@@ -0,0 +1 @@
ALTER TABLE ai_assistant_settings ADD COLUMN embedding_model VARCHAR(128);

View File

@@ -423,9 +423,8 @@ router.post('/message', requireAuth, upload.array('attachments'), async (req, re
const userMessage = userMessageResult.rows[0];
logger.info('User message saved', { messageId: userMessage.id, conversationId });
// Получаем ответ от ИИ, только если это было текстовое сообщение
// --- Новая логика автоответа ИИ по RAG ---
let aiMessage = null;
// --- Новая логика автоответа ИИ ---
let shouldGenerateAiReply = true;
if (senderType === 'admin') {
// Если админ пишет не себе, не отвечаем
@@ -441,41 +440,70 @@ router.post('/message', requireAuth, upload.array('attachments'), async (req, re
if (aiSettings && aiSettings.rules_id) {
rules = await aiAssistantRulesService.getRuleById(aiSettings.rules_id);
}
logger.info('AI System Prompt:', aiSettings ? aiSettings.system_prompt : 'not set');
logger.info('AI Rules:', rules ? JSON.stringify(rules.rules) : 'not set');
// Получаем последние 10 сообщений из диалога для истории (до текущего сообщения)
const historyResult = await db.getQuery()(
'SELECT sender_type, content FROM messages WHERE conversation_id = $1 AND id < $2 ORDER BY created_at DESC LIMIT 10',
[conversationId, userMessage.id]
);
const history = historyResult.rows.reverse().map(msg => ({
role: msg.sender_type === 'user' ? 'user' : 'assistant',
content: msg.content
}));
const detectedLanguage = language === 'auto' ? aiAssistant.detectLanguage(messageContent) : language;
logger.info('Getting AI response for:', messageContent);
const aiResponseContent = await aiAssistant.getResponse(
messageContent,
detectedLanguage,
history,
aiSettings ? aiSettings.system_prompt : '',
rules ? rules.rules : null
);
logger.info('AI response received' + (aiResponseContent ? '' : ' (empty)'), { conversationId });
if (aiResponseContent) {
const aiMessageResult = await db.getQuery()(
`INSERT INTO messages
(conversation_id, user_id, content, sender_type, role, channel)
VALUES ($1, $2, $3, 'assistant', 'assistant', 'web')
RETURNING *`,
[conversationId, userId, aiResponseContent]
);
aiMessage = aiMessageResult.rows[0];
logger.info('AI response saved', { messageId: aiMessage.id, conversationId });
// --- RAG автоответ ---
let ragTableId = null;
if (aiSettings && aiSettings.selected_rag_tables) {
ragTableId = Array.isArray(aiSettings.selected_rag_tables)
? aiSettings.selected_rag_tables[0]
: aiSettings.selected_rag_tables;
}
let ragResult = null;
if (ragTableId) {
const { ragAnswer, generateLLMResponse } = require('../services/ragService');
const threshold = 0.3;
logger.info(`[RAG] Запуск поиска по RAG: tableId=${ragTableId}, вопрос="${messageContent}", threshold=${threshold}`);
const ragResult = await ragAnswer({ tableId: ragTableId, userQuestion: messageContent, threshold });
logger.info(`[RAG] Результат поиска по RAG:`, ragResult);
if (ragResult && ragResult.answer && ragResult.score && ragResult.score > threshold) {
logger.info(`[RAG] Найден confident-ответ (score=${ragResult.score}), отправляем ответ из базы.`);
// Прямой ответ из RAG
const aiMessageResult = await db.getQuery()(
`INSERT INTO messages
(conversation_id, user_id, content, sender_type, role, channel)
VALUES ($1, $2, $3, 'assistant', 'assistant', 'web')
RETURNING *`,
[conversationId, userId, ragResult.answer]
);
aiMessage = aiMessageResult.rows[0];
} else if (ragResult) {
logger.info(`[RAG] Нет confident-ответа (score=${ragResult.score}), переходим к генерации через LLM.`);
// Генерация через LLM с подстановкой значений из RAG
const historyResult = await db.getQuery()(
'SELECT sender_type, content FROM messages WHERE conversation_id = $1 AND id < $2 ORDER BY created_at DESC LIMIT 10',
[conversationId, userMessage.id]
);
const history = historyResult.rows.reverse().map(msg => ({
role: msg.sender_type === 'user' ? 'user' : 'assistant',
content: msg.content
}));
const llmResponse = await generateLLMResponse({
userQuestion: messageContent,
context: ragResult.context,
answer: ragResult.answer,
clarifyingAnswer: ragResult.clarifyingAnswer,
objectionAnswer: ragResult.objectionAnswer,
systemPrompt: aiSettings ? aiSettings.system_prompt : '',
history,
model: aiSettings ? aiSettings.model : undefined,
language: aiSettings && aiSettings.languages && aiSettings.languages.length > 0 ? aiSettings.languages[0] : 'ru'
});
if (llmResponse) {
const aiMessageResult = await db.getQuery()(
`INSERT INTO messages
(conversation_id, user_id, content, sender_type, role, channel)
VALUES ($1, $2, $3, 'assistant', 'assistant', 'web')
RETURNING *`,
[conversationId, userId, llmResponse]
);
aiMessage = aiMessageResult.rows[0];
} else {
logger.info(`[RAG] Нет ни одного результата, прошедшего порог (${threshold}).`);
}
}
}
// --- конец RAG автоответа ---
} catch (aiError) {
logger.error('Error getting or saving AI response:', aiError);
logger.error('Error getting or saving AI response (RAG):', aiError);
// Не прерываем основной ответ, но логируем ошибку
}
}
@@ -702,14 +730,32 @@ router.post('/ai-draft', requireAuth, async (req, res) => {
role: msg.sender_type === 'user' ? 'user' : 'assistant',
content: msg.content
}));
// --- RAG draft ---
let ragTableId = null;
if (aiSettings && aiSettings.selected_rag_tables) {
ragTableId = Array.isArray(aiSettings.selected_rag_tables)
? aiSettings.selected_rag_tables[0]
: aiSettings.selected_rag_tables;
}
let ragResult = null;
if (ragTableId) {
const { ragAnswer } = require('../services/ragService');
logger.info(`[RAG] [DRAFT] Запуск поиска по RAG: tableId=${ragTableId}, draft prompt="${promptText}"`);
ragResult = await ragAnswer({ tableId: ragTableId, userQuestion: promptText });
logger.info(`[RAG] [DRAFT] Результат поиска по RAG:`, ragResult);
}
const { generateLLMResponse } = require('../services/ragService');
const detectedLanguage = language === 'auto' ? aiAssistant.detectLanguage(promptText) : language;
const aiResponseContent = await aiAssistant.getResponse(
promptText,
detectedLanguage,
const aiResponseContent = await generateLLMResponse({
userQuestion: promptText,
context: ragResult && ragResult.context ? ragResult.context : '',
answer: ragResult && ragResult.answer ? ragResult.answer : '',
systemPrompt: aiSettings ? aiSettings.system_prompt : '',
history,
aiSettings ? aiSettings.system_prompt : '',
rules ? rules.rules : null
);
model: aiSettings ? aiSettings.model : undefined,
language: aiSettings && aiSettings.languages && aiSettings.languages.length > 0 ? aiSettings.languages[0] : 'ru',
rules: rules ? rules.rules : null
});
res.json({ success: true, aiMessage: aiResponseContent });
} catch (error) {
logger.error('Error generating AI draft:', error);

View File

@@ -10,6 +10,10 @@ const aiAssistant = require('../services/ai-assistant');
const dns = require('node:dns').promises;
const aiAssistantSettingsService = require('../services/aiAssistantSettingsService');
const aiAssistantRulesService = require('../services/aiAssistantRulesService');
const telegramBot = require('../services/telegramBot');
const EmailBotService = require('../services/emailBot');
const emailBotService = new EmailBotService();
const dbSettingsService = require('../services/dbSettingsService');
// Логируем версию ethers для отладки
logger.info(`Ethers version: ${ethers.version || 'unknown'}`);
@@ -185,8 +189,8 @@ router.get('/ai-settings/:provider', requireAdmin, async (req, res, next) => {
router.put('/ai-settings/:provider', requireAdmin, async (req, res, next) => {
try {
const { provider } = req.params;
const { api_key, base_url, selected_model } = req.body;
const updated = await aiProviderSettingsService.upsertProviderSettings({ provider, api_key, base_url, selected_model });
const { api_key, base_url, selected_model, embedding_model } = req.body;
const updated = await aiProviderSettingsService.upsertProviderSettings({ provider, api_key, base_url, selected_model, embedding_model });
res.json({ success: true, settings: updated });
} catch (error) {
logger.error('Ошибка при сохранении AI-настроек:', error);
@@ -252,7 +256,25 @@ router.get('/ai-assistant', requireAdmin, async (req, res, next) => {
router.put('/ai-assistant', requireAdmin, async (req, res, next) => {
try {
const updated = await aiAssistantSettingsService.upsertSettings({ ...req.body, updated_by: req.session.userId || null });
let { selected_rag_tables, ...rest } = req.body;
// Приведение к массиву чисел
if (typeof selected_rag_tables === 'string') {
try {
selected_rag_tables = JSON.parse(selected_rag_tables);
} catch {
selected_rag_tables = [Number(selected_rag_tables)];
}
}
if (!Array.isArray(selected_rag_tables)) {
selected_rag_tables = [Number(selected_rag_tables)];
}
selected_rag_tables = selected_rag_tables.map(Number);
const updated = await aiAssistantSettingsService.upsertSettings({
...rest,
selected_rag_tables,
updated_by: req.session.userId || null
});
res.json({ success: true, settings: updated });
} catch (error) {
next(error);
@@ -309,23 +331,101 @@ router.delete('/ai-assistant-rules/:id', requireAdmin, async (req, res, next) =>
}
});
// Получить все email_settings для выпадающего списка
router.get('/email-settings', requireAdmin, async (req, res, next) => {
// Получить текущие настройки Email (для страницы Email)
router.get('/email-settings', requireAdmin, async (req, res) => {
try {
const { rows } = await require('../db').getQuery()('SELECT id, from_email FROM email_settings ORDER BY id');
res.json({ success: true, items: rows });
const settings = await emailBotService.getSettingsFromDb();
res.json({ success: true, settings });
} catch (error) {
res.status(404).json({ success: false, error: error.message });
}
});
// Получить список всех email (для ассистента)
router.get('/email-settings/list', requireAdmin, async (req, res) => {
try {
const emails = await emailBotService.getAllEmailSettings();
res.json({ success: true, items: emails });
} catch (error) {
res.status(404).json({ success: false, error: error.message });
}
});
// Получить текущие настройки Telegram-бота (для страницы Telegram)
router.get('/telegram-settings', requireAdmin, async (req, res, next) => {
try {
const settings = await telegramBot.getTelegramSettings();
res.json({ success: true, settings });
} catch (error) {
res.status(404).json({ success: false, error: error.message });
}
});
// Получить список всех Telegram-ботов (для ассистента)
router.get('/telegram-settings/list', requireAdmin, async (req, res, next) => {
try {
const bots = await telegramBot.getAllBots();
res.json({ success: true, items: bots });
} catch (error) {
res.status(404).json({ success: false, error: error.message });
}
});
// Получение списка моделей для выбранного AI-провайдера
router.get('/ai-provider-models', requireAdmin, async (req, res, next) => {
try {
const provider = req.query.provider;
if (!provider) return res.status(400).json({ error: 'provider is required' });
const settings = await aiProviderSettingsService.getProviderSettings(provider);
if (!settings) return res.status(404).json({ error: 'Provider not found' });
const models = await aiProviderSettingsService.getProviderModels(provider, {
api_key: settings.api_key,
base_url: settings.base_url,
});
res.json({ models });
} catch (error) {
next(error);
}
});
// Получить все telegram_settings для выпадающего списка
router.get('/telegram-settings', requireAdmin, async (req, res, next) => {
// Получить настройки базы данных
router.get('/db-settings', requireAdmin, async (req, res) => {
try {
const { rows } = await require('../db').getQuery()('SELECT id, bot_username FROM telegram_settings ORDER BY id');
res.json({ success: true, items: rows });
const settings = await dbSettingsService.getSettings();
res.json({ success: true, settings });
} catch (error) {
next(error);
res.status(404).json({ success: false, error: error.message });
}
});
// Обновить настройки базы данных
router.put('/db-settings', requireAdmin, async (req, res) => {
try {
const { db_host, db_port, db_name, db_user, db_password } = req.body;
const updated = await dbSettingsService.upsertSettings({ db_host, db_port, db_name, db_user, db_password });
res.json({ success: true, settings: updated });
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});
// Получить все LLM-модели
router.get('/llm-models', requireAdmin, async (req, res) => {
try {
const models = await aiProviderSettingsService.getAllLLMModels();
res.json({ success: true, models });
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});
// Получить все embedding-модели
router.get('/embedding-models', requireAdmin, async (req, res) => {
try {
const models = await aiProviderSettingsService.getAllEmbeddingModels();
res.json({ success: true, models });
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});

View File

@@ -54,9 +54,7 @@ router.post('/:id/columns', async (req, res, next) => {
try {
const tableId = req.params.id;
const { name, type, options, order, tagIds, purpose } = req.body;
let finalOptions = options;
// Собираем options
finalOptions = finalOptions || {};
let finalOptions = options || {};
if (type === 'tags' && Array.isArray(tagIds)) {
finalOptions.tagIds = tagIds;
}

View File

@@ -20,19 +20,21 @@ async function getSettings() {
return {
...settings,
telegramBot,
supportEmail
supportEmail,
embedding_model: settings.embedding_model
};
}
async function upsertSettings({ system_prompt, selected_rag_tables, languages, model, rules, updated_by, telegram_settings_id, email_settings_id, system_message }) {
async function upsertSettings({ system_prompt, selected_rag_tables, languages, model, embedding_model, rules, updated_by, telegram_settings_id, email_settings_id, system_message }) {
const { rows } = await db.getQuery()(
`INSERT INTO ${TABLE} (id, system_prompt, selected_rag_tables, languages, model, rules, updated_at, updated_by, telegram_settings_id, email_settings_id, system_message)
VALUES (1, $1, $2, $3, $4, $5, NOW(), $6, $7, $8, $9)
`INSERT INTO ${TABLE} (id, system_prompt, selected_rag_tables, languages, model, embedding_model, rules, updated_at, updated_by, telegram_settings_id, email_settings_id, system_message)
VALUES (1, $1, $2, $3, $4, $5, $6, NOW(), $7, $8, $9, $10)
ON CONFLICT (id) DO UPDATE SET
system_prompt = EXCLUDED.system_prompt,
selected_rag_tables = EXCLUDED.selected_rag_tables,
languages = EXCLUDED.languages,
model = EXCLUDED.model,
embedding_model = EXCLUDED.embedding_model,
rules = EXCLUDED.rules,
updated_at = NOW(),
updated_by = EXCLUDED.updated_by,
@@ -40,7 +42,7 @@ async function upsertSettings({ system_prompt, selected_rag_tables, languages, m
email_settings_id = EXCLUDED.email_settings_id,
system_message = EXCLUDED.system_message
RETURNING *`,
[system_prompt, selected_rag_tables, languages, model, rules, updated_by, telegram_settings_id, email_settings_id, system_message]
[system_prompt, selected_rag_tables, languages, model, embedding_model, rules, updated_by, telegram_settings_id, email_settings_id, system_message]
);
return rows[0];
}

View File

@@ -12,17 +12,18 @@ async function getProviderSettings(provider) {
return rows[0] || null;
}
async function upsertProviderSettings({ provider, api_key, base_url, selected_model }) {
async function upsertProviderSettings({ provider, api_key, base_url, selected_model, embedding_model }) {
const { rows } = await db.getQuery()(
`INSERT INTO ${TABLE} (provider, api_key, base_url, selected_model, updated_at)
VALUES ($1, $2, $3, $4, NOW())
`INSERT INTO ${TABLE} (provider, api_key, base_url, selected_model, embedding_model, updated_at)
VALUES ($1, $2, $3, $4, $5, NOW())
ON CONFLICT (provider) DO UPDATE SET
api_key = EXCLUDED.api_key,
base_url = EXCLUDED.base_url,
selected_model = EXCLUDED.selected_model,
embedding_model = EXCLUDED.embedding_model,
updated_at = NOW()
RETURNING *`,
[provider, api_key, base_url, selected_model]
[provider, api_key, base_url, selected_model, embedding_model]
);
return rows[0];
}
@@ -97,10 +98,28 @@ async function verifyProviderKey(provider, { api_key, base_url } = {}) {
}
}
async function getAllLLMModels() {
const { rows } = await db.getQuery()(
`SELECT provider, selected_model FROM ${TABLE} WHERE selected_model IS NOT NULL AND selected_model <> ''`
);
// Возвращаем массив объектов { id, provider }
return rows.map(r => ({ id: r.selected_model, provider: r.provider }));
}
async function getAllEmbeddingModels() {
const { rows } = await db.getQuery()(
`SELECT provider, embedding_model FROM ${TABLE} WHERE embedding_model IS NOT NULL AND embedding_model <> ''`
);
// Возвращаем массив объектов { id, provider }
return rows.map(r => ({ id: r.embedding_model, provider: r.provider }));
}
module.exports = {
getProviderSettings,
upsertProviderSettings,
deleteProviderSettings,
getProviderModels,
verifyProviderKey,
getAllLLMModels,
getAllEmbeddingModels,
};

View File

@@ -0,0 +1,27 @@
const db = require('../db');
class DbSettingsService {
async getSettings() {
const { rows } = await db.getQuery()('SELECT * FROM db_settings WHERE id = 1');
return rows[0];
}
async upsertSettings({ db_host, db_port, db_name, db_user, db_password }) {
const { rows } = await db.getQuery()(
`INSERT INTO db_settings (id, db_host, db_port, db_name, db_user, db_password, updated_at)
VALUES (1, $1, $2, $3, $4, $5, NOW())
ON CONFLICT (id) DO UPDATE SET
db_host = EXCLUDED.db_host,
db_port = EXCLUDED.db_port,
db_name = EXCLUDED.db_name,
db_user = EXCLUDED.db_user,
db_password = EXCLUDED.db_password,
updated_at = NOW()
RETURNING *`,
[db_host, db_port, db_name, db_user, db_password]
);
return rows[0];
}
}
module.exports = new DbSettingsService();

View File

@@ -309,6 +309,11 @@ class EmailBotService {
throw err;
}
}
async getAllEmailSettings() {
const { rows } = await db.getQuery()('SELECT id, from_email FROM email_settings ORDER BY id');
return rows;
}
}
console.log('[EmailBot] module.exports = EmailBotService');

View File

@@ -1,10 +1,15 @@
const { OpenAIEmbeddings } = require('@langchain/openai');
const { HNSWLib } = require('@langchain/community/vectorstores/hnswlib');
const db = require('../db');
const { ChatOllama } = require('@langchain/ollama');
const { OllamaEmbeddings } = require('@langchain/ollama');
const { getProviderSettings } = require('./aiProviderSettingsService');
const { OpenAIEmbeddings } = require('@langchain/openai');
console.log('[RAG] ragService.js loaded');
async function getTableData(tableId) {
const columns = (await db.getQuery()('SELECT * FROM user_columns WHERE table_id = $1', [tableId])).rows;
console.log('RAG getTableData: columns:', columns);
const rows = (await db.getQuery()('SELECT * FROM user_rows WHERE table_id = $1', [tableId])).rows;
const cellValues = (await db.getQuery()('SELECT * FROM user_cell_values WHERE row_id IN (SELECT id FROM user_rows WHERE table_id = $1)', [tableId])).rows;
@@ -17,7 +22,7 @@ async function getTableData(tableId) {
const priorityColId = getColId('priority');
const dateColId = getColId('date');
return rows.map(row => {
const data = rows.map(row => {
const cells = cellValues.filter(cell => cell.row_id === row.id);
return {
id: row.id,
@@ -30,35 +35,107 @@ async function getTableData(tableId) {
date: cells.find(c => c.column_id === dateColId)?.value,
};
});
const questions = data.map(row => row.question);
console.log('RAG getTableData: questions:', questions);
if (!questions.length) {
console.warn('RAG getTableData: questions array is empty! Проверьте структуру колонок и наличие данных.');
}
return data;
}
async function ragAnswer({ tableId, userQuestion, userTags = [], product = null }) {
async function getEmbeddingsProvider(providerName = 'ollama') {
const settings = await getProviderSettings(providerName);
if (!settings) throw new Error('Embeddings provider settings not found');
switch (providerName) {
case 'openai':
return new OpenAIEmbeddings({
apiKey: settings.api_key,
baseURL: settings.base_url,
model: settings.selected_model || undefined,
});
case 'ollama': {
// Fallback: если не задан base_url, пробуем env, host.docker.internal, localhost
let baseUrl = settings.base_url;
if (!baseUrl) {
baseUrl = process.env.OLLAMA_BASE_URL;
}
if (!baseUrl) {
// Если в Docker — используем host.docker.internal
baseUrl = 'http://host.docker.internal:11434';
}
// Если всё равно нет — последний fallback
if (!baseUrl) {
baseUrl = 'http://localhost:11434';
}
return new OllamaEmbeddings({
model: settings.embedding_model || process.env.OLLAMA_EMBED_MODEL || 'mxbai-embed-large',
baseUrl,
});
}
// case 'gemini':
// return new GeminiEmbeddings({ apiKey: settings.api_key });
// Добавьте другие провайдеры по аналогии
default:
throw new Error('Unknown embeddings provider: ' + providerName);
}
}
async function ragAnswer({ tableId, userQuestion, userTags = [], product = null, embeddingsProvider = 'ollama', threshold = 0.3 }) {
console.log('[RAG] Используется провайдер эмбеддингов:', embeddingsProvider);
const data = await getTableData(tableId);
const questions = data.map(row => row.question);
// Триммируем вопросы для чистоты сравнения
const questions = data.map(row => row.question && typeof row.question === 'string' ? row.question.trim() : row.question);
// Получаем embeddings-инстанс динамически
const embeddingsInstance = await getEmbeddingsProvider(embeddingsProvider);
// Получаем embedding для всех вопросов
const embeddings = await new OpenAIEmbeddings().embedDocuments(questions);
const embeddings = await embeddingsInstance.embedDocuments(questions);
console.log('Questions embedding length:', embeddings[0]?.length, 'Total questions:', questions.length);
// Получаем embedding для вопроса пользователя (trim)
const userQuestionTrimmed = userQuestion && typeof userQuestion === 'string' ? userQuestion.trim() : userQuestion;
const [userEmbedding] = await embeddingsInstance.embedDocuments([userQuestionTrimmed]);
console.log('User embedding length:', userEmbedding?.length, 'User question:', userQuestionTrimmed);
// Явно сравниваем embeddings (отладка)
console.log('[RAG] Embedding сравнение:');
embeddings.forEach((emb, idx) => {
const dot = emb.reduce((sum, v, i) => sum + v * userEmbedding[i], 0);
console.log(` [${idx}] dot-product: ${dot} | question: "${questions[idx]}"`);
});
// Создаём массив метаданных для каждого вопроса
const metadatas = data.map(row => ({
id: row.id,
answer: row.answer,
userTags: row.userTags,
context: row.context,
product: row.product,
priority: row.priority,
date: row.date,
}));
// Создаём векторное хранилище
const vectorStore = await HNSWLib.fromTexts(questions, data, new OpenAIEmbeddings());
// Получаем embedding для вопроса пользователя
const [userEmbedding] = await new OpenAIEmbeddings().embedDocuments([userQuestion]);
const vectorStore = await HNSWLib.fromTexts(questions, metadatas, embeddingsInstance);
// Ищем наиболее похожие вопросы (top-3)
const results = await vectorStore.similaritySearchVectorWithScore(userEmbedding, 3);
console.log('[RAG] Результаты поиска по векторам (score):', results.map(([doc, score]) => ({ ...doc.metadata, score })));
// Фильтруем по тегам/продукту, если нужно
let filtered = results.map(([row, score]) => ({ ...row, score }));
let filtered = results.map(([doc, score]) => ({ ...doc.metadata, score }));
if (userTags.length) {
filtered = filtered.filter(row => row.userTags && userTags.some(tag => row.userTags.includes(tag)));
}
if (product) {
filtered = filtered.filter(row => row.product === product);
}
console.log('[RAG] Отфильтрованные результаты:', filtered);
// Берём лучший результат
const best = filtered[0];
// Берём лучший результат с учётом порога
const best = filtered.find(row => row.score >= threshold);
console.log(`[RAG] Выбранный ответ (порог ${threshold}):`, best);
// Формируем ответ
return {

View File

@@ -424,9 +424,16 @@ function clearSettingsCache() {
telegramSettingsCache = null;
}
async function getAllBots() {
const { rows } = await db.getQuery()('SELECT id, bot_username FROM telegram_settings ORDER BY id');
return rows;
}
module.exports = {
getTelegramSettings,
getBot,
stopBot,
initTelegramAuth,
clearSettingsCache,
getAllBots,
};

View File

@@ -110,10 +110,10 @@
dependencies:
"@types/json-schema" "^7.0.15"
"@eslint/core@^0.15.0":
version "0.15.0"
resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.15.0.tgz#8fc04709a7b9a179d9f7d93068fc000cb8c5603d"
integrity sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==
"@eslint/core@^0.15.1":
version "0.15.1"
resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.15.1.tgz#d530d44209cbfe2f82ef86d6ba08760196dd3b60"
integrity sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==
dependencies:
"@types/json-schema" "^7.0.15"
@@ -143,11 +143,11 @@
integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==
"@eslint/plugin-kit@^0.3.1":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.3.2.tgz#0cad96b134d23a653348e3342f485636b5ef4732"
integrity sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==
version "0.3.3"
resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz#32926b59bd407d58d817941e48b2a7049359b1fd"
integrity sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==
dependencies:
"@eslint/core" "^0.15.0"
"@eslint/core" "^0.15.1"
levn "^0.4.1"
"@ethereumjs/rlp@^4.0.1":
@@ -367,9 +367,9 @@
integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==
"@google/genai@^1.0.1":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@google/genai/-/genai-1.6.0.tgz#7a14d505faebe17957b272476debd574d2eae1e0"
integrity sha512-0vn8wMGesjiEsHeFsl10T8+SFqLj7q+RSE6mml66sE+jwI7U9wW2LQ3qYtwUEaI+P8ZYeEYE5IpYmNLcRQUBPQ==
version "1.7.0"
resolved "https://registry.yarnpkg.com/@google/genai/-/genai-1.7.0.tgz#4af2fe86343e990eebfb4adcd0fa744cb0e6907e"
integrity sha512-s/OZLkrIfBwc+SFFaZoKdEogkw4in0YRTGc4Q483jnfchNBWzrNe560eZEfGJHQRPn6YfzJgECCx0sqEOMWvYw==
dependencies:
google-auth-library "^9.14.2"
ws "^8.18.0"
@@ -6056,9 +6056,9 @@ simple-update-notifier@^2.0.0:
semver "^7.5.3"
simple-wcswidth@^1.0.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/simple-wcswidth/-/simple-wcswidth-1.1.1.tgz#a96ff1b5cff660262ea33850e19a0e7249caed50"
integrity sha512-R3q3/eoeNBp24CNTASEUrffXi0j9TwPIEvSStlvSrsFimM17sV5EHcMOc86j3K+UWZyLYvH0hRmYGCpCoaJ4vw==
version "1.1.2"
resolved "https://registry.yarnpkg.com/simple-wcswidth/-/simple-wcswidth-1.1.2.tgz#66722f37629d5203f9b47c5477b1225b85d6525b"
integrity sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==
sisteransi@^1.0.5:
version "1.0.5"
@@ -7219,9 +7219,9 @@ zip-stream@^6.0.1:
readable-stream "^4.0.0"
zod-to-json-schema@^3.22.3, zod-to-json-schema@^3.22.4:
version "3.24.5"
resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz#d1095440b147fb7c2093812a53c54df8d5df50a3"
integrity sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==
version "3.24.6"
resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz#5920f020c4d2647edfbb954fa036082b92c9e12d"
integrity sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==
zod@^3.22.4, zod@^3.25.32:
version "3.25.67"