feat: новая функция

This commit is contained in:
2025-10-08 18:01:14 +03:00
parent 2c53bce32a
commit 725e7fd5a2
60 changed files with 5427 additions and 3921 deletions

View File

@@ -0,0 +1,293 @@
/**
* Copyright (c) 2024-2025 Тарабанов Александр Викторович
* All rights reserved.
*
* This software is proprietary and confidential.
* Unauthorized copying, modification, or distribution is prohibited.
*
* For licensing inquiries: info@hb3-accelerator.com
* Website: https://hb3-accelerator.com
* GitHub: https://github.com/HB3-ACCELERATOR
*/
const db = require('../db');
const logger = require('../utils/logger');
const encryptionUtils = require('../utils/encryptionUtils');
const aiAssistant = require('./ai-assistant');
const conversationService = require('./conversationService');
const { broadcastMessagesUpdate } = require('../wsHub');
/**
* Унифицированный процессор сообщений для всех каналов
* Обрабатывает сообщения из web, telegram, email
*/
/**
* Обработать сообщение от пользователя
* @param {Object} messageData - Данные сообщения
* @param {number} messageData.userId - ID пользователя
* @param {string} messageData.content - Текст сообщения
* @param {string} messageData.channel - Канал (web/telegram/email)
* @param {Array} messageData.attachments - Вложения
* @param {number} messageData.conversationId - ID беседы (опционально)
* @returns {Promise<Object>}
*/
async function processMessage(messageData) {
try {
const {
userId,
content,
channel = 'web',
attachments = [],
conversationId: inputConversationId,
guestId
} = messageData;
logger.info('[UnifiedMessageProcessor] Обработка сообщения:', {
userId,
channel,
contentLength: content?.length,
hasAttachments: attachments.length > 0
});
const encryptionKey = encryptionUtils.getEncryptionKey();
// 1. Получаем или создаем беседу
let conversation;
if (inputConversationId) {
conversation = await conversationService.getConversationById(inputConversationId);
}
if (!conversation && userId) {
conversation = await conversationService.getOrCreateConversation(userId, 'Беседа');
}
const conversationId = conversation?.id || null;
// 2. Сохраняем входящее сообщение пользователя
let userMessage;
// Обработка вложений
let attachment_filename = null;
let attachment_mimetype = null;
let attachment_size = null;
let attachment_data = null;
if (attachments && attachments.length > 0) {
const firstAttachment = attachments[0];
attachment_filename = firstAttachment.filename;
attachment_mimetype = firstAttachment.mimetype;
attachment_size = firstAttachment.size;
attachment_data = firstAttachment.data;
}
if (userId) {
const { rows } = await db.getQuery()(
`INSERT INTO messages (
user_id,
conversation_id,
sender_type_encrypted,
content_encrypted,
channel_encrypted,
role_encrypted,
direction_encrypted,
attachment_filename_encrypted,
attachment_mimetype_encrypted,
attachment_size,
attachment_data,
created_at
) VALUES (
$1, $2,
encrypt_text($3, $12),
encrypt_text($4, $12),
encrypt_text($5, $12),
encrypt_text($6, $12),
encrypt_text($7, $12),
encrypt_text($8, $12),
encrypt_text($9, $12),
$10, $11,
NOW()
) RETURNING id`,
[
userId,
conversationId,
'user',
content,
channel,
'user',
'incoming',
attachment_filename,
attachment_mimetype,
attachment_size,
attachment_data,
encryptionKey
]
);
userMessage = rows[0];
logger.info('[UnifiedMessageProcessor] Сообщение пользователя сохранено:', userMessage.id);
}
// 3. Получаем историю беседы для контекста
let conversationHistory = [];
if (conversationId && userId) {
const { rows } = await db.getQuery()(
`SELECT
decrypt_text(role_encrypted, $2) as role,
decrypt_text(content_encrypted, $2) as content,
created_at
FROM messages
WHERE conversation_id = $1 AND user_id = $3
ORDER BY created_at ASC
LIMIT 20`,
[conversationId, encryptionKey, userId]
);
conversationHistory = rows.map(row => ({
role: row.role,
content: row.content
}));
}
// 4. Генерируем AI ответ
logger.info('[UnifiedMessageProcessor] Генерация AI ответа...');
const aiResponse = await aiAssistant.generateResponse({
channel,
messageId: userMessage?.id || `guest_${Date.now()}`,
userId: userId || guestId,
userQuestion: content,
conversationHistory,
conversationId,
metadata: {
hasAttachments: attachments.length > 0,
channel
}
});
if (!aiResponse || !aiResponse.success) {
logger.warn('[UnifiedMessageProcessor] AI не вернул ответ или ошибка:', aiResponse?.reason);
// Возвращаем результат без AI ответа
return {
success: true,
userMessageId: userMessage?.id,
conversationId,
noAiResponse: true,
reason: aiResponse?.reason
};
}
// 5. Сохраняем ответ AI
if (userId && aiResponse.response) {
const { rows: aiMessageRows } = await db.getQuery()(
`INSERT INTO messages (
user_id,
conversation_id,
sender_type_encrypted,
content_encrypted,
channel_encrypted,
role_encrypted,
direction_encrypted,
created_at
) VALUES (
$1, $2,
encrypt_text($3, $8),
encrypt_text($4, $8),
encrypt_text($5, $8),
encrypt_text($6, $8),
encrypt_text($7, $8),
NOW()
) RETURNING id`,
[
userId,
conversationId,
'assistant',
aiResponse.response,
channel,
'assistant',
'outgoing',
encryptionKey
]
);
logger.info('[UnifiedMessageProcessor] Ответ AI сохранен:', aiMessageRows[0].id);
// 6. Обновляем время беседы
if (conversationId) {
await conversationService.touchConversation(conversationId);
}
// 7. Отправляем уведомление через WebSocket
try {
broadcastMessagesUpdate(userId);
} catch (wsError) {
logger.warn('[UnifiedMessageProcessor] Ошибка отправки WebSocket:', wsError.message);
}
}
// 8. Возвращаем результат
return {
success: true,
userMessageId: userMessage?.id,
conversationId,
aiResponse: {
response: aiResponse.response,
ragData: aiResponse.ragData
}
};
} catch (error) {
logger.error('[UnifiedMessageProcessor] Ошибка обработки сообщения:', error);
throw error;
}
}
/**
* Обработать сообщение от гостя
* @param {Object} messageData - Данные сообщения
* @returns {Promise<Object>}
*/
async function processGuestMessage(messageData) {
try {
const guestService = require('./guestService');
// Создаем guest ID если нет
const guestId = messageData.guestId || guestService.createGuestId();
// Сохраняем гостевое сообщение
await guestService.saveGuestMessage({
guestId,
content: messageData.content,
channel: messageData.channel || 'web'
});
// Генерируем AI ответ для гостя (без сохранения в messages)
const aiResponse = await aiAssistant.generateResponse({
channel: messageData.channel || 'web',
messageId: `guest_${guestId}_${Date.now()}`,
userId: guestId,
userQuestion: messageData.content,
conversationHistory: [],
metadata: { isGuest: true }
});
return {
success: true,
guestId,
aiResponse: aiResponse?.success ? {
response: aiResponse.response
} : null
};
} catch (error) {
logger.error('[UnifiedMessageProcessor] Ошибка обработки гостевого сообщения:', error);
throw error;
}
}
module.exports = {
processMessage,
processGuestMessage
};