Files
DLE/backend/routes/chat.js

348 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const express = require('express');
const router = express.Router();
const aiAssistant = require('../services/ai-assistant');
const db = require('../db');
const { requireAuth, requireAdmin } = require('../middleware/auth');
const logger = require('../utils/logger');
const crypto = require('crypto');
const { saveGuestMessageToDatabase } = require('../db');
const { v4: uuidv4 } = require('uuid');
// Функция для обработки гостевых сообщений после аутентификации
async function processGuestMessages(userId, guestId) {
try {
console.log(`Processing guest messages for user ${userId} with guest ID ${guestId}`);
// Получаем все гостевые сообщения
const guestMessagesResult = await db.query(
'SELECT * FROM guest_messages WHERE guest_id = $1 ORDER BY created_at ASC',
[guestId]
);
if (guestMessagesResult.rows.length === 0) {
console.log('No guest messages found');
return { success: true, message: 'No guest messages found' };
}
const guestMessages = guestMessagesResult.rows;
console.log(`Found ${guestMessages.length} guest messages`);
// Получаем идентификаторы пользователя
const userIdentities = await db.query(
`SELECT provider, provider_id FROM user_identities WHERE user_id = $1`,
[userId]
);
console.log(`Found ${userIdentities.rows.length} identities for user ${userId}`, userIdentities.rows);
// Сохраняем guestId как отдельный идентификатор для пользователя
// если он еще не привязан к пользователю
const existingGuestIdentity = userIdentities.rows.find(
identity => identity.provider === 'guest' && identity.provider_id === guestId
);
if (!existingGuestIdentity) {
await db.query(
`INSERT INTO user_identities (user_id, provider, provider_id)
VALUES ($1, 'guest', $2)
ON CONFLICT (provider, provider_id) DO NOTHING`,
[userId, guestId]
);
console.log(`Linked guest ID ${guestId} to user ${userId}`);
}
// Создаем новый диалог для этих сообщений
const firstMessage = guestMessages[0];
const title = firstMessage.content.length > 30
? `${firstMessage.content.substring(0, 30)}...`
: firstMessage.content;
const newConversationResult = await db.query(
'INSERT INTO conversations (user_id, title) VALUES ($1, $2) RETURNING *',
[userId, title]
);
const conversation = newConversationResult.rows[0];
console.log('Created new conversation for guest messages:', conversation);
// Обрабатываем каждое гостевое сообщение
for (const guestMessage of guestMessages) {
console.log(`Processing guest message ID ${guestMessage.id}: ${guestMessage.content}`);
// Проверяем, не было ли это сообщение уже обработано
const existingMessage = await db.query(
'SELECT id FROM messages WHERE guest_message_id = $1',
[guestMessage.id]
);
if (existingMessage.rows.length > 0) {
console.log(`Guest message ${guestMessage.id} already processed, skipping`);
continue;
}
// Сохраняем сообщение пользователя
const userMessageResult = await db.query(
`INSERT INTO messages
(conversation_id, content, sender_type, role, channel, guest_message_id, created_at)
VALUES
($1, $2, $3, $4, $5, $6, $7)
RETURNING *`,
[
conversation.id,
guestMessage.content,
'user',
'user',
'web',
guestMessage.id,
guestMessage.created_at
]
);
console.log(`Saved user message with ID ${userMessageResult.rows[0].id}`);
// Получаем ответ от ИИ только для сообщений пользователя (не AI)
if (!guestMessage.is_ai) {
console.log('Getting AI response for:', guestMessage.content);
const language = guestMessage.language || 'auto';
const aiResponse = await aiAssistant.getResponse(guestMessage.content, language);
console.log('AI response received:', aiResponse);
// Сохраняем ответ от ИИ
const aiMessageResult = await db.query(
`INSERT INTO messages
(conversation_id, content, sender_type, role, channel, created_at)
VALUES
($1, $2, $3, $4, $5, $6)
RETURNING *`,
[
conversation.id,
aiResponse,
'assistant',
'assistant',
'web',
new Date()
]
);
console.log(`Saved AI response with ID ${aiMessageResult.rows[0].id}`);
}
}
return {
success: true,
message: `Processed ${guestMessages.length} guest messages`,
conversationId: conversation.id
};
} catch (error) {
console.error('Error processing guest messages:', error);
throw error;
}
}
// Обработчик для гостевых сообщений
router.post('/guest-message', async (req, res) => {
try {
const { message, language } = req.body;
const guestId = req.session.guestId || crypto.randomBytes(16).toString('hex');
// Сохраняем ID гостя в сессии
req.session.guestId = guestId;
await req.session.save();
console.log('Saving guest message:', { guestId, message });
// Сохраняем сообщение пользователя
const result = await db.query(
'INSERT INTO guest_messages (guest_id, content, language, is_ai) VALUES ($1, $2, $3, false) RETURNING id',
[guestId, message, language]
);
console.log('Guest message saved:', result.rows[0]);
res.json({
success: true,
messageId: result.rows[0].id
});
} catch (error) {
console.error('Error saving guest message:', error);
res.status(500).json({ success: false, error: 'Internal server error' });
}
});
// Маршрут для обычных сообщений (для аутентифицированных пользователей)
router.post('/message', requireAuth, async (req, res) => {
const { message, conversationId, language = 'auto' } = req.body;
if (!message) {
return res.status(400).json({ error: 'Message is required' });
}
try {
console.log('Processing message:', { message, conversationId, language, userId: req.session.userId });
const userId = req.session.userId;
let conversation;
// Если указан ID диалога, проверяем его существование и принадлежность пользователю
if (conversationId) {
const conversationResult = await db.query(
'SELECT * FROM conversations WHERE id = $1 AND user_id = $2',
[conversationId, userId]
);
if (conversationResult.rows.length === 0) {
return res.status(404).json({ error: 'Conversation not found or access denied' });
}
conversation = conversationResult.rows[0];
console.log('Using existing conversation:', conversation);
} else {
// Создаем новый диалог
const title = message.length > 30 ? `${message.substring(0, 30)}...` : message;
const newConversationResult = await db.query(
'INSERT INTO conversations (user_id, title) VALUES ($1, $2) RETURNING *',
[userId, title]
);
conversation = newConversationResult.rows[0];
console.log('Created new conversation:', conversation);
}
// Сохраняем сообщение пользователя
console.log('Saving user message');
const userMessageResult = await db.query(
`INSERT INTO messages
(conversation_id, content, sender_type, role, tokens_used, channel, created_at)
VALUES
($1, $2, $3, $4, $5, $6, $7)
RETURNING *`,
[conversation.id, message, 'user', 'user', 0, 'web', new Date()]
);
// Получаем ответ от ИИ
console.log('Getting AI response');
const aiResponse = await aiAssistant.getResponse(message, language);
console.log('AI response received:', aiResponse);
// Сохраняем ответ от ИИ
console.log('Saving AI response');
const aiMessageResult = await db.query(
`INSERT INTO messages
(conversation_id, content, sender_type, role, tokens_used, channel, created_at)
VALUES
($1, $2, $3, $4, $5, $6, $7)
RETURNING *`,
[conversation.id, aiResponse, 'assistant', 'assistant', 0, 'web', new Date()]
);
const response = {
success: true,
userMessage: userMessageResult.rows[0],
aiMessage: aiMessageResult.rows[0],
conversation
};
res.json(response);
} catch (error) {
console.error('Error processing message:', error);
res.status(500).json({ error: 'Error processing message' });
}
});
// Добавьте этот маршрут для проверки доступных моделей
router.get('/models', async (req, res) => {
try {
const models = await aiAssistant.getAvailableModels();
res.json({
success: true,
models: models,
});
} catch (error) {
console.error('Ошибка при получении списка моделей:', error);
res.status(500).json({ success: false, message: 'Ошибка сервера' });
}
});
// Получение истории сообщений
router.get('/history', async (req, res) => {
try {
console.log('Session in history route:', {
id: req.sessionID,
userId: req.session.userId,
address: req.session.address,
authenticated: req.session.authenticated,
guestId: req.session.guestId
});
const limit = parseInt(req.query.limit) || 50;
const offset = parseInt(req.query.offset) || 0;
// Если пользователь аутентифицирован и у него есть гостевые сообщения,
// автоматически связываем их перед получением истории
if (req.session.authenticated && req.session.userId && req.session.guestId) {
try {
console.log('Automatically linking guest messages before fetching history');
await processGuestMessages(req.session.userId, req.session.guestId);
// Очищаем guestId из сессии после связывания
req.session.guestId = null;
await req.session.save();
console.log('Guest messages automatically linked');
} catch (linkError) {
console.error('Error auto-linking guest messages:', linkError);
// Продолжаем выполнение, даже если связывание не удалось
}
}
let messages = [];
let total = 0;
// Если пользователь аутентифицирован, получаем его сообщения
if (req.session.authenticated && req.session.userId) {
const countResult = await db.query(
`SELECT COUNT(*) as total FROM messages m
JOIN conversations c ON m.conversation_id = c.id
WHERE c.user_id = $1`,
[req.session.userId]
);
total = parseInt(countResult.rows[0].total) || 0;
const result = await db.query(
`SELECT
m.id,
m.content,
m.sender_type,
m.role,
m.created_at,
c.user_id,
c.id as conversation_id
FROM messages m
JOIN conversations c ON m.conversation_id = c.id
WHERE c.user_id = $1
ORDER BY m.created_at ASC
LIMIT $2 OFFSET $3`,
[req.session.userId, limit, offset]
);
messages = result.rows;
console.log(`Found ${messages.length} messages for authenticated user`);
}
return res.json({
success: true,
messages: messages,
total: total
});
} catch (error) {
logger.error('Error getting chat history:', error);
return res.status(500).json({ error: 'Internal server error' });
}
});
// Экспортируем маршрутизатор и функцию processGuestMessages отдельно
module.exports = router;
module.exports.processGuestMessages = processGuestMessages;