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

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

@@ -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;
}