ваше сообщение коммита
This commit is contained in:
21
backend/db/migrations/029_create_ai_assistant_settings.sql
Normal file
21
backend/db/migrations/029_create_ai_assistant_settings.sql
Normal file
@@ -0,0 +1,21 @@
|
||||
CREATE TABLE IF NOT EXISTS ai_assistant_settings (
|
||||
id SERIAL PRIMARY KEY,
|
||||
system_prompt TEXT,
|
||||
selected_rag_tables INTEGER[],
|
||||
languages TEXT[],
|
||||
model TEXT,
|
||||
rules JSONB,
|
||||
updated_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_by INTEGER,
|
||||
);
|
||||
|
||||
-- Вставить дефолтную строку (глобальные настройки)
|
||||
INSERT INTO ai_assistant_settings (system_prompt, selected_rag_tables, languages, model, rules)
|
||||
VALUES (
|
||||
'Вы — полезный ассистент. Отвечайте на русском языке.',
|
||||
ARRAY[]::INTEGER[],
|
||||
ARRAY['ru'],
|
||||
'qwen2.5',
|
||||
'{"checkUserTags": true, "searchRagFirst": true, "generateIfNoRag": true, "requireAdminApproval": true}'
|
||||
)
|
||||
ON CONFLICT DO NOTHING;
|
||||
11
backend/db/migrations/030_create_ai_assistant_rules.sql
Normal file
11
backend/db/migrations/030_create_ai_assistant_rules.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
CREATE TABLE IF NOT EXISTS ai_assistant_rules (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
rules JSONB NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
ALTER TABLE ai_assistant_settings
|
||||
ADD COLUMN IF NOT EXISTS rules_id INTEGER REFERENCES ai_assistant_rules(id);
|
||||
@@ -0,0 +1,5 @@
|
||||
-- Добавление недостающих полей для интеграции с Telegram и Email, а также для системного сообщения
|
||||
ALTER TABLE ai_assistant_settings
|
||||
ADD COLUMN IF NOT EXISTS telegram_settings_id INTEGER REFERENCES telegram_settings(id),
|
||||
ADD COLUMN IF NOT EXISTS email_settings_id INTEGER REFERENCES email_settings(id),
|
||||
ADD COLUMN IF NOT EXISTS system_message TEXT;
|
||||
@@ -6,6 +6,8 @@ const db = require('../db');
|
||||
const logger = require('../utils/logger');
|
||||
const { requireAuth } = require('../middleware/auth');
|
||||
const crypto = require('crypto');
|
||||
const aiAssistantSettingsService = require('../services/aiAssistantSettingsService');
|
||||
const aiAssistantRulesService = require('../services/aiAssistantRulesService');
|
||||
|
||||
// Настройка multer для обработки файлов в памяти
|
||||
const storage = multer.memoryStorage();
|
||||
@@ -61,19 +63,28 @@ async function processGuestMessages(userId, guestId) {
|
||||
const guestMessages = guestMessagesResult.rows;
|
||||
logger.info(`Found ${guestMessages.length} guest messages for guest ID ${guestId}`);
|
||||
|
||||
// Создаем новый диалог для этих сообщений
|
||||
const firstMessage = guestMessages[0];
|
||||
const title = firstMessage.content
|
||||
? (firstMessage.content.length > 30 ? `${firstMessage.content.substring(0, 30)}...` : firstMessage.content)
|
||||
: (firstMessage.attachment_filename ? `Файл: ${firstMessage.attachment_filename}` : 'Новый диалог');
|
||||
|
||||
const newConversationResult = await db.getQuery()(
|
||||
'INSERT INTO conversations (user_id, title) VALUES ($1, $2) RETURNING *',
|
||||
[userId, title]
|
||||
// --- Новый порядок: ищем последний диалог пользователя ---
|
||||
let conversation = null;
|
||||
const lastConvResult = await db.getQuery()(
|
||||
'SELECT * FROM conversations WHERE user_id = $1 ORDER BY updated_at DESC, created_at DESC LIMIT 1',
|
||||
[userId]
|
||||
);
|
||||
|
||||
const conversation = newConversationResult.rows[0];
|
||||
logger.info(`Created new conversation ${conversation.id} for guest messages`);
|
||||
if (lastConvResult.rows.length > 0) {
|
||||
conversation = lastConvResult.rows[0];
|
||||
} else {
|
||||
// Если нет ни одного диалога, создаём новый
|
||||
const firstMessage = guestMessages[0];
|
||||
const title = firstMessage.content
|
||||
? (firstMessage.content.length > 30 ? `${firstMessage.content.substring(0, 30)}...` : firstMessage.content)
|
||||
: (firstMessage.attachment_filename ? `Файл: ${firstMessage.attachment_filename}` : 'Новый диалог');
|
||||
const newConversationResult = await db.getQuery()(
|
||||
'INSERT INTO conversations (user_id, title) VALUES ($1, $2) RETURNING *',
|
||||
[userId, title]
|
||||
);
|
||||
conversation = newConversationResult.rows[0];
|
||||
logger.info(`Created new conversation ${conversation.id} for guest messages`);
|
||||
}
|
||||
// --- КОНЕЦ блока поиска/создания диалога ---
|
||||
|
||||
// Отслеживаем успешные сохранения сообщений
|
||||
const savedMessageIds = [];
|
||||
@@ -81,7 +92,6 @@ async function processGuestMessages(userId, guestId) {
|
||||
// Обрабатываем каждое гостевое сообщение
|
||||
for (const guestMessage of guestMessages) {
|
||||
logger.info(`Processing guest message ID ${guestMessage.id}: ${guestMessage.content || guestMessage.attachment_filename || '(empty)'}`);
|
||||
|
||||
try {
|
||||
// Сохраняем сообщение пользователя в таблицу messages, включая данные файла
|
||||
const userMessageResult = await db.getQuery()(
|
||||
@@ -103,39 +113,59 @@ async function processGuestMessages(userId, guestId) {
|
||||
guestMessage.attachment_data // BYTEA
|
||||
]
|
||||
);
|
||||
|
||||
const savedUserMessage = userMessageResult.rows[0];
|
||||
logger.info(`Saved user message with ID ${savedUserMessage.id}`);
|
||||
savedMessageIds.push(guestMessage.id);
|
||||
|
||||
// Получаем ответ от ИИ только для текстовых сообщений
|
||||
if (!guestMessage.is_ai && guestMessage.content) {
|
||||
logger.info('Getting AI response for:', guestMessage.content);
|
||||
const language = guestMessage.language || 'auto';
|
||||
// Предполагаем, что aiAssistant.getResponse принимает только текст
|
||||
const aiResponseContent = await aiAssistant.getResponse(guestMessage.content, language);
|
||||
logger.info('AI response received' + (aiResponseContent ? '' : ' (empty)'), 'for conversation', conversation.id);
|
||||
|
||||
if (aiResponseContent) {
|
||||
// Сохраняем ответ от ИИ (у него нет вложений)
|
||||
const aiMessageResult = await db.getQuery()(
|
||||
`INSERT INTO messages
|
||||
(conversation_id, content, sender_type, role, channel, created_at, user_id)
|
||||
VALUES
|
||||
($1, $2, 'assistant', 'assistant', 'web', $3, $4)
|
||||
RETURNING *`,
|
||||
[
|
||||
conversation.id,
|
||||
aiResponseContent,
|
||||
new Date(),
|
||||
userId
|
||||
]
|
||||
// --- Генерируем ответ ИИ на гостевое сообщение, если это текст ---
|
||||
if (guestMessage.content) {
|
||||
// Проверяем, что на это сообщение ещё нет ответа ассистента
|
||||
const aiReplyExists = await db.getQuery()(
|
||||
`SELECT 1 FROM messages WHERE conversation_id = $1 AND sender_type = 'assistant' AND created_at > $2 LIMIT 1`,
|
||||
[conversation.id, guestMessage.created_at]
|
||||
);
|
||||
if (!aiReplyExists.rows.length) {
|
||||
try {
|
||||
// Получаем настройки ассистента
|
||||
const aiSettings = await aiAssistantSettingsService.getSettings();
|
||||
let rules = null;
|
||||
if (aiSettings && aiSettings.rules_id) {
|
||||
rules = await aiAssistantRulesService.getRuleById(aiSettings.rules_id);
|
||||
}
|
||||
// Получаем историю сообщений до этого guestMessage (до created_at)
|
||||
const historyResult = await db.getQuery()(
|
||||
'SELECT sender_type, content FROM messages WHERE conversation_id = $1 AND created_at < $2 ORDER BY created_at DESC LIMIT 10',
|
||||
[conversation.id, guestMessage.created_at]
|
||||
);
|
||||
logger.info(`Saved AI response with ID ${aiMessageResult.rows[0].id}`);
|
||||
const history = historyResult.rows.reverse().map(msg => ({
|
||||
role: msg.sender_type === 'user' ? 'user' : 'assistant',
|
||||
content: msg.content
|
||||
}));
|
||||
// Язык guestMessage.language или auto
|
||||
const detectedLanguage = guestMessage.language === 'auto' ? aiAssistant.detectLanguage(guestMessage.content) : guestMessage.language;
|
||||
logger.info('Getting AI response for guest message:', guestMessage.content);
|
||||
const aiResponseContent = await aiAssistant.getResponse(
|
||||
guestMessage.content,
|
||||
detectedLanguage,
|
||||
history,
|
||||
aiSettings ? aiSettings.system_prompt : '',
|
||||
rules ? rules.rules : null
|
||||
);
|
||||
logger.info('AI response for guest message received' + (aiResponseContent ? '' : ' (empty)'), { conversationId: conversation.id });
|
||||
if (aiResponseContent) {
|
||||
await db.getQuery()(
|
||||
`INSERT INTO messages
|
||||
(conversation_id, user_id, content, sender_type, role, channel)
|
||||
VALUES ($1, $2, $3, 'assistant', 'assistant', 'web')`,
|
||||
[conversation.id, userId, aiResponseContent]
|
||||
);
|
||||
logger.info('AI response for guest message saved', { conversationId: conversation.id });
|
||||
}
|
||||
} catch (aiError) {
|
||||
logger.error('Error getting or saving AI response for guest message:', aiError);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.info(`Skipping AI response for guest message ID ${guestMessage.id} (is_ai: ${guestMessage.is_ai}, hasContent: ${!!guestMessage.content})`);
|
||||
}
|
||||
// --- конец блока генерации ответа ИИ ---
|
||||
} catch (error) {
|
||||
logger.error(`Error processing guest message ${guestMessage.id}: ${error.message}`, { stack: error.stack });
|
||||
// Продолжаем с другими сообщениями в случае ошибки
|
||||
@@ -254,10 +284,28 @@ router.post('/guest-message', upload.array('attachments'), async (req, res) => {
|
||||
// Не прерываем ответ пользователю из-за ошибки сессии
|
||||
}
|
||||
|
||||
// Получаем настройки ассистента для systemMessage
|
||||
let telegramBotUrl = null;
|
||||
let supportEmailAddr = null;
|
||||
try {
|
||||
const aiSettings = await aiAssistantSettingsService.getSettings();
|
||||
if (aiSettings && aiSettings.telegramBot && aiSettings.telegramBot.bot_username) {
|
||||
telegramBotUrl = `https://t.me/${aiSettings.telegramBot.bot_username}`;
|
||||
}
|
||||
if (aiSettings && aiSettings.supportEmail && aiSettings.supportEmail.from_email) {
|
||||
supportEmailAddr = aiSettings.supportEmail.from_email;
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('Ошибка получения настроек ассистента для systemMessage:', e);
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
messageId: savedMessageId, // Возвращаем ID сохраненного сообщения
|
||||
guestId: guestId // Возвращаем использованный guestId
|
||||
guestId: guestId, // Возвращаем использованный guestId
|
||||
systemMessage: 'Для продолжения диалога авторизуйтесь: подключите кошелек, перейдите в чат-бот Telegram или отправьте письмо на email.',
|
||||
telegramBotUrl,
|
||||
supportEmail: supportEmailAddr
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error saving guest message:', error);
|
||||
@@ -303,18 +351,27 @@ router.post('/message', requireAuth, upload.array('attachments'), async (req, re
|
||||
}
|
||||
conversation = convResult.rows[0];
|
||||
} else {
|
||||
// Создаем новый диалог, если ID не предоставлен
|
||||
const title = message
|
||||
? (message.length > 50 ? `${message.substring(0, 50)}...` : message)
|
||||
: (file ? `Файл: ${file.originalname}` : 'Новый диалог');
|
||||
|
||||
const newConvResult = await db.getQuery()(
|
||||
'INSERT INTO conversations (user_id, title) VALUES ($1, $2) RETURNING *',
|
||||
[userId, title]
|
||||
// Ищем последний диалог пользователя
|
||||
const lastConvResult = await db.getQuery()(
|
||||
'SELECT * FROM conversations WHERE user_id = $1 ORDER BY updated_at DESC, created_at DESC LIMIT 1',
|
||||
[userId]
|
||||
);
|
||||
conversation = newConvResult.rows[0];
|
||||
conversationId = conversation.id;
|
||||
logger.info('Created new conversation', { conversationId, userId });
|
||||
if (lastConvResult.rows.length > 0) {
|
||||
conversation = lastConvResult.rows[0];
|
||||
conversationId = conversation.id;
|
||||
} else {
|
||||
// Создаем новый диалог, если нет ни одного
|
||||
const title = message
|
||||
? (message.length > 50 ? `${message.substring(0, 50)}...` : message)
|
||||
: (file ? `Файл: ${file.originalname}` : 'Новый диалог');
|
||||
const newConvResult = await db.getQuery()(
|
||||
'INSERT INTO conversations (user_id, title) VALUES ($1, $2) RETURNING *',
|
||||
[userId, title]
|
||||
);
|
||||
conversation = newConvResult.rows[0];
|
||||
conversationId = conversation.id;
|
||||
logger.info('Created new conversation', { conversationId, userId });
|
||||
}
|
||||
}
|
||||
|
||||
// Подготавливаем данные для вставки сообщения пользователя
|
||||
@@ -348,9 +405,32 @@ router.post('/message', requireAuth, upload.array('attachments'), async (req, re
|
||||
let aiMessage = null;
|
||||
if (messageContent) { // Только для текстовых сообщений
|
||||
try {
|
||||
// Получаем настройки ассистента
|
||||
const aiSettings = await aiAssistantSettingsService.getSettings();
|
||||
let rules = null;
|
||||
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);
|
||||
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) {
|
||||
@@ -396,6 +476,12 @@ router.post('/message', requireAuth, upload.array('attachments'), async (req, re
|
||||
return formatted;
|
||||
};
|
||||
|
||||
// Обновляем updated_at у диалога
|
||||
await db.getQuery()(
|
||||
'UPDATE conversations SET updated_at = NOW() WHERE id = $1',
|
||||
[conversationId]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
conversationId: conversationId,
|
||||
@@ -541,6 +627,26 @@ router.get('/history', requireAuth, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// --- Новый роут для связывания гостя после аутентификации ---
|
||||
router.post('/process-guest', requireAuth, async (req, res) => {
|
||||
const userId = req.session.userId;
|
||||
const { guestId } = req.body;
|
||||
if (!guestId) {
|
||||
return res.status(400).json({ success: false, error: 'guestId is required' });
|
||||
}
|
||||
try {
|
||||
const result = await module.exports.processGuestMessages(userId, guestId);
|
||||
if (result && result.conversationId) {
|
||||
return res.json({ success: true, conversationId: result.conversationId });
|
||||
} else {
|
||||
return res.json({ success: false, error: result.error || 'No conversation created' });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error in /process-guest:', error);
|
||||
return res.status(500).json({ success: false, error: 'Internal error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Экспортируем маршрутизатор и функцию processGuestMessages отдельно
|
||||
module.exports = router;
|
||||
module.exports.processGuestMessages = processGuestMessages;
|
||||
|
||||
@@ -8,6 +8,8 @@ const authTokenService = require('../services/authTokenService');
|
||||
const aiProviderSettingsService = require('../services/aiProviderSettingsService');
|
||||
const aiAssistant = require('../services/ai-assistant');
|
||||
const dns = require('node:dns').promises;
|
||||
const aiAssistantSettingsService = require('../services/aiAssistantSettingsService');
|
||||
const aiAssistantRulesService = require('../services/aiAssistantRulesService');
|
||||
|
||||
// Логируем версию ethers для отладки
|
||||
logger.info(`Ethers version: ${ethers.version || 'unknown'}`);
|
||||
@@ -239,4 +241,92 @@ router.post('/ai-settings/:provider/verify', requireAdmin, async (req, res, next
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/ai-assistant', requireAdmin, async (req, res, next) => {
|
||||
try {
|
||||
const settings = await aiAssistantSettingsService.getSettings();
|
||||
res.json({ success: true, settings });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/ai-assistant', requireAdmin, async (req, res, next) => {
|
||||
try {
|
||||
const updated = await aiAssistantSettingsService.upsertSettings({ ...req.body, updated_by: req.session.userId || null });
|
||||
res.json({ success: true, settings: updated });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Получить все наборы правил
|
||||
router.get('/ai-assistant-rules', requireAdmin, async (req, res, next) => {
|
||||
try {
|
||||
const rules = await aiAssistantRulesService.getAllRules();
|
||||
res.json({ success: true, rules });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Получить набор правил по id
|
||||
router.get('/ai-assistant-rules/:id', requireAdmin, async (req, res, next) => {
|
||||
try {
|
||||
const rule = await aiAssistantRulesService.getRuleById(req.params.id);
|
||||
res.json({ success: true, rule });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Создать набор правил
|
||||
router.post('/ai-assistant-rules', requireAdmin, async (req, res, next) => {
|
||||
try {
|
||||
const created = await aiAssistantRulesService.createRule(req.body);
|
||||
res.json({ success: true, rule: created });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Обновить набор правил
|
||||
router.put('/ai-assistant-rules/:id', requireAdmin, async (req, res, next) => {
|
||||
try {
|
||||
const updated = await aiAssistantRulesService.updateRule(req.params.id, req.body);
|
||||
res.json({ success: true, rule: updated });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Удалить набор правил
|
||||
router.delete('/ai-assistant-rules/:id', requireAdmin, async (req, res, next) => {
|
||||
try {
|
||||
await aiAssistantRulesService.deleteRule(req.params.id);
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Получить все email_settings для выпадающего списка
|
||||
router.get('/email-settings', requireAdmin, async (req, res, next) => {
|
||||
try {
|
||||
const { rows } = await require('../db').getQuery()('SELECT id, from_email FROM email_settings ORDER BY id');
|
||||
res.json({ success: true, items: rows });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Получить все telegram_settings для выпадающего списка
|
||||
router.get('/telegram-settings', requireAdmin, async (req, res, next) => {
|
||||
try {
|
||||
const { rows } = await require('../db').getQuery()('SELECT id, bot_username FROM telegram_settings ORDER BY id');
|
||||
res.json({ success: true, items: rows });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -34,30 +34,51 @@ class AIAssistant {
|
||||
}
|
||||
|
||||
// Основной метод для получения ответа
|
||||
async getResponse(message, language = 'auto') {
|
||||
async getResponse(message, language = 'auto', history = null, systemPrompt = '', rules = null) {
|
||||
try {
|
||||
console.log('getResponse called with:', { message, language });
|
||||
console.log('getResponse called with:', { message, language, history, systemPrompt, rules });
|
||||
|
||||
// Определяем язык, если не указан явно
|
||||
const detectedLanguage = language === 'auto' ? this.detectLanguage(message) : language;
|
||||
|
||||
console.log('Detected language:', detectedLanguage);
|
||||
|
||||
// Сначала пробуем прямой API запрос
|
||||
// Формируем system prompt с учётом правил
|
||||
let fullSystemPrompt = systemPrompt || '';
|
||||
if (rules && typeof rules === 'object') {
|
||||
fullSystemPrompt += '\n' + JSON.stringify(rules, null, 2);
|
||||
}
|
||||
|
||||
// Формируем массив сообщений для Qwen2.5/OpenAI API
|
||||
const messages = [];
|
||||
if (fullSystemPrompt) {
|
||||
messages.push({ role: 'system', content: fullSystemPrompt });
|
||||
}
|
||||
if (Array.isArray(history) && history.length > 0) {
|
||||
for (const msg of history) {
|
||||
if (msg.role && msg.content) {
|
||||
messages.push({ role: msg.role, content: msg.content });
|
||||
}
|
||||
}
|
||||
}
|
||||
// Добавляем текущее сообщение пользователя
|
||||
messages.push({ role: 'user', content: message });
|
||||
|
||||
// Пробуем прямой API запрос (OpenAI-совместимый endpoint)
|
||||
try {
|
||||
console.log('Trying direct API request...');
|
||||
const response = await this.fallbackRequest(message, detectedLanguage);
|
||||
const response = await this.fallbackRequestOpenAI(messages, detectedLanguage);
|
||||
console.log('Direct API response received:', response);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('Error in direct API request:', error);
|
||||
}
|
||||
|
||||
// Если прямой запрос не удался, пробуем через ChatOllama
|
||||
// Если прямой запрос не удался, пробуем через ChatOllama (склеиваем сообщения в текст)
|
||||
const chat = this.createChat(detectedLanguage);
|
||||
try {
|
||||
const prompt = messages.map(m => `${m.role === 'user' ? 'Пользователь' : m.role === 'assistant' ? 'Ассистент' : 'Система'}: ${m.content}`).join('\n');
|
||||
console.log('Sending request to ChatOllama...');
|
||||
const response = await chat.invoke(message);
|
||||
const response = await chat.invoke(prompt);
|
||||
console.log('ChatOllama response:', response);
|
||||
return response.content;
|
||||
} catch (error) {
|
||||
@@ -70,24 +91,17 @@ class AIAssistant {
|
||||
}
|
||||
}
|
||||
|
||||
// Альтернативный метод запроса через прямой API
|
||||
async fallbackRequest(message, language) {
|
||||
// Новый метод для OpenAI/Qwen2.5 совместимого endpoint
|
||||
async fallbackRequestOpenAI(messages, language) {
|
||||
try {
|
||||
console.log('Using fallback request method with:', { message, language });
|
||||
|
||||
const systemPrompt =
|
||||
language === 'ru'
|
||||
? 'Вы - полезный ассистент. Отвечайте на русском языке.'
|
||||
: 'You are a helpful assistant. Respond in English.';
|
||||
|
||||
console.log('Sending request to Ollama API...');
|
||||
const response = await fetch(`${this.baseUrl}/api/generate`, {
|
||||
console.log('Using fallbackRequestOpenAI with:', { messages, language });
|
||||
const model = this.defaultModel;
|
||||
const response = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
model: this.defaultModel,
|
||||
prompt: message,
|
||||
system: systemPrompt,
|
||||
model,
|
||||
messages,
|
||||
stream: false,
|
||||
options: {
|
||||
temperature: 0.7,
|
||||
@@ -95,16 +109,17 @@ class AIAssistant {
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Ollama API response:', data);
|
||||
return data.response;
|
||||
// Qwen2.5/OpenAI API возвращает ответ в data.choices[0].message.content
|
||||
if (data.choices && data.choices[0] && data.choices[0].message && data.choices[0].message.content) {
|
||||
return data.choices[0].message.content;
|
||||
}
|
||||
return data.response || '';
|
||||
} catch (error) {
|
||||
console.error('Error in fallback request:', error);
|
||||
console.error('Error in fallbackRequestOpenAI:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
35
backend/services/aiAssistantRulesService.js
Normal file
35
backend/services/aiAssistantRulesService.js
Normal file
@@ -0,0 +1,35 @@
|
||||
const db = require('../db');
|
||||
const TABLE = 'ai_assistant_rules';
|
||||
|
||||
async function getAllRules() {
|
||||
const { rows } = await db.getQuery()(`SELECT * FROM ${TABLE} ORDER BY id`);
|
||||
return rows;
|
||||
}
|
||||
|
||||
async function getRuleById(id) {
|
||||
const { rows } = await db.getQuery()(`SELECT * FROM ${TABLE} WHERE id = $1`, [id]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
async function createRule({ name, description, rules }) {
|
||||
const { rows } = await db.getQuery()(
|
||||
`INSERT INTO ${TABLE} (name, description, rules, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, NOW(), NOW()) RETURNING *`,
|
||||
[name, description, rules]
|
||||
);
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
async function updateRule(id, { name, description, rules }) {
|
||||
const { rows } = await db.getQuery()(
|
||||
`UPDATE ${TABLE} SET name = $1, description = $2, rules = $3, updated_at = NOW() WHERE id = $4 RETURNING *`,
|
||||
[name, description, rules, id]
|
||||
);
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
async function deleteRule(id) {
|
||||
await db.getQuery()(`DELETE FROM ${TABLE} WHERE id = $1`, [id]);
|
||||
}
|
||||
|
||||
module.exports = { getAllRules, getRuleById, createRule, updateRule, deleteRule };
|
||||
48
backend/services/aiAssistantSettingsService.js
Normal file
48
backend/services/aiAssistantSettingsService.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const db = require('../db');
|
||||
const TABLE = 'ai_assistant_settings';
|
||||
|
||||
async function getSettings() {
|
||||
const { rows } = await db.getQuery()(`SELECT * FROM ${TABLE} ORDER BY id LIMIT 1`);
|
||||
const settings = rows[0] || null;
|
||||
if (!settings) return null;
|
||||
|
||||
// Получаем связанные данные из telegram_settings и email_settings
|
||||
let telegramBot = null;
|
||||
let supportEmail = null;
|
||||
if (settings.telegram_settings_id) {
|
||||
const tg = await db.getQuery()('SELECT * FROM telegram_settings WHERE id = $1', [settings.telegram_settings_id]);
|
||||
telegramBot = tg.rows[0] || null;
|
||||
}
|
||||
if (settings.email_settings_id) {
|
||||
const em = await db.getQuery()('SELECT * FROM email_settings WHERE id = $1', [settings.email_settings_id]);
|
||||
supportEmail = em.rows[0] || null;
|
||||
}
|
||||
return {
|
||||
...settings,
|
||||
telegramBot,
|
||||
supportEmail
|
||||
};
|
||||
}
|
||||
|
||||
async function upsertSettings({ system_prompt, selected_rag_tables, languages, 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)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
system_prompt = EXCLUDED.system_prompt,
|
||||
selected_rag_tables = EXCLUDED.selected_rag_tables,
|
||||
languages = EXCLUDED.languages,
|
||||
model = EXCLUDED.model,
|
||||
rules = EXCLUDED.rules,
|
||||
updated_at = NOW(),
|
||||
updated_by = EXCLUDED.updated_by,
|
||||
telegram_settings_id = EXCLUDED.telegram_settings_id,
|
||||
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]
|
||||
);
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
module.exports = { getSettings, upsertSettings };
|
||||
Reference in New Issue
Block a user