Files
DLE/backend/routes/chat.js

399 lines
13 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');
// Добавьте эту функцию в начало файла chat.js
async function getAIResponse(message, language = 'ru') {
// Определяем язык сообщения, если не указан явно
let detectedLanguage = language;
if (!language || language === 'auto') {
// Простая эвристика для определения языка
const cyrillicPattern = /[а-яА-ЯёЁ]/;
detectedLanguage = cyrillicPattern.test(message) ? 'ru' : 'en';
}
// Формируем системный промпт в зависимости от языка
let systemPrompt = '';
if (detectedLanguage === 'ru') {
systemPrompt = 'Вы - полезный ассистент. Отвечайте на русском языке.';
} else {
systemPrompt = 'You are a helpful assistant. Respond in English.';
}
// Создаем экземпляр ChatOllama
const chat = new ChatOllama({
baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434',
model: process.env.OLLAMA_MODEL || 'mistral',
system: systemPrompt
});
console.log('Отправка запроса к Ollama...');
// Получаем ответ от модели
try {
const response = await chat.invoke(message);
return response.content;
} catch (error) {
console.error('Ошибка при вызове ChatOllama:', error);
// Альтернативный метод запроса через прямой API
try {
console.log('Пробуем альтернативный метод запроса...');
const response = await fetch(`${process.env.OLLAMA_BASE_URL || 'http://localhost:11434'}/api/generate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: process.env.OLLAMA_MODEL || 'mistral',
prompt: message,
system: systemPrompt,
stream: false
}),
});
const data = await response.json();
return data.response;
} catch (fallbackError) {
console.error('Ошибка при использовании альтернативного метода:', fallbackError);
return "Извините, я не смог обработать ваш запрос. Пожалуйста, попробуйте позже.";
}
}
}
// Функция для обработки гостевых сообщений после аутентификации
async function processGuestMessages(userId, guestId) {
try {
console.log(`Starting to process guest messages for user ${userId} with guestId ${guestId}`);
// Получаем все гостевые сообщения
const guestMessages = await db.query(
`SELECT m.id, m.content, m.conversation_id, m.metadata, m.created_at
FROM messages m
WHERE m.metadata->>'guest_id' = $1
ORDER BY m.created_at ASC`,
[guestId]
);
console.log(`Found ${guestMessages.rows.length} guest messages to process`);
// Обновляем user_id для всех бесед с гостевыми сообщениями
await db.query(
`UPDATE conversations c
SET user_id = $1
WHERE id IN (
SELECT DISTINCT conversation_id
FROM messages m
WHERE m.metadata->>'guest_id' = $2
)`,
[userId, guestId]
);
// Обрабатываем каждое гостевое сообщение
for (const msg of guestMessages.rows) {
console.log(`Processing guest message ${msg.id}: ${msg.content}`);
// Получаем язык из метаданных
const metadata = typeof msg.metadata === 'string' ? JSON.parse(msg.metadata) : msg.metadata;
const language = metadata?.language || 'ru';
// Получаем ответ от AI
console.log(`Getting AI response for message ${msg.id} in ${language}`);
const aiResponse = await aiAssistant.getResponse(msg.content, language);
// Сохраняем ответ AI в ту же беседу
await db.query(
`INSERT INTO messages
(conversation_id, sender_type, content, channel, created_at)
VALUES ($1, 'assistant', $2, 'chat', NOW())`,
[msg.conversation_id, aiResponse]
);
console.log(`Saved AI response for message ${msg.id}`);
}
console.log(`Successfully processed all guest messages for user ${userId}`);
return true;
} catch (error) {
console.error('Error processing guest messages:', error);
return false;
}
}
// Обработчик для гостевых сообщений
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) => {
try {
const { message, language } = req.body;
const userId = req.session.userId;
// Используем методы из aiAssistant вместо прямого обращения к vectorStore
const similarDocs = await aiAssistant.findSimilarDocuments(message);
const aiResponse = await aiAssistant.getResponse(message, language);
// Создаем новую беседу или получаем существующую
const conversationResult = await db.query(
`INSERT INTO conversations (user_id, created_at)
VALUES ($1, NOW())
RETURNING id`,
[userId]
);
const conversationId = conversationResult.rows[0].id;
// Сохраняем сообщение пользователя
await db.query(
`INSERT INTO messages
(conversation_id, sender_type, content, channel, metadata, created_at)
VALUES ($1, 'user', $2, 'chat', $3, NOW())`,
[
conversationId,
message,
JSON.stringify({ language: language || 'ru' })
]
);
// Сохраняем ответ AI
await db.query(
`INSERT INTO messages
(conversation_id, sender_type, content, channel, metadata, created_at)
VALUES ($1, 'assistant', $2, 'chat', $3, NOW())`,
[
conversationId,
aiResponse,
JSON.stringify({ language: language || 'ru' })
]
);
res.json({
success: true,
message: aiResponse
});
} catch (error) {
logger.error('Error processing message:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Добавьте этот маршрут для проверки доступных моделей
router.get('/models', async (req, res) => {
try {
const ollama = new Ollama();
const models = await ollama.list();
res.json({
success: true,
models: models.models.map((model) => model.name),
});
} 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
});
if (!req.session.authenticated || !req.session.userId) {
return res.status(401).json({ error: 'Unauthorized' });
}
const limit = parseInt(req.query.limit) || 50;
const offset = parseInt(req.query.offset) || 0;
// Получаем сообщения с пагинацией
const result = await db.query(
`SELECT
m.id,
m.content,
m.sender_type as role,
m.created_at,
c.user_id
FROM messages m
JOIN conversations c ON m.conversation_id = c.id
WHERE c.user_id = $1
ORDER BY m.created_at DESC
LIMIT $2 OFFSET $3`,
[req.session.userId, limit, offset]
);
return res.json({
success: true,
messages: result.rows.reverse()
});
} catch (error) {
logger.error('Error getting chat history:', error);
return res.status(500).json({ error: 'Internal server error' });
}
});
// Маршрут для получения всех диалогов (только для админов)
router.get('/admin/history', requireAdmin, async (req, res) => {
try {
const { limit = 50, offset = 0, userId } = req.query;
let query = `
SELECT ch.id, ch.user_id, u.username, ch.channel,
ch.sender_type, ch.content, ch.metadata, ch.created_at
FROM chat_history ch
LEFT JOIN users u ON ch.user_id = u.id
`;
const params = [];
let paramIndex = 1;
if (userId) {
query += ` WHERE ch.user_id = $${paramIndex}`;
params.push(userId);
paramIndex++;
}
query += ` ORDER BY ch.created_at DESC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`;
params.push(limit, offset);
const result = await db.query(query, params);
res.json(result.rows);
} catch (error) {
logger.error('Error fetching admin chat history:', error);
res.status(500).json({ error: 'Внутренняя ошибка сервера' });
}
});
// Обработчик для связывания гостевых сообщений с пользователем
router.post('/link-guest-messages', requireAuth, async (req, res) => {
try {
const { userId } = req.session;
const guestId = req.session.guestId;
console.log('Linking messages:', { userId, guestId });
if (!guestId) {
console.log('No guestId in session');
return res.json({ success: true, message: 'No guest messages to link' });
}
// Проверяем наличие гостевых сообщений
const guestMessages = await db.query(
'SELECT EXISTS(SELECT 1 FROM guest_messages WHERE guest_id = $1)',
[guestId]
);
console.log('Guest messages check:', guestMessages.rows[0]);
if (!guestMessages.rows[0].exists) {
console.log('No guest messages found for guestId:', guestId);
return res.json({ success: true, message: 'No guest messages to link' });
}
// Связываем сообщения
console.log('Calling link_guest_messages function');
await db.query('SELECT link_guest_messages($1, $2)', [userId, guestId]);
// Очищаем guestId из сессии после связывания
delete req.session.guestId;
console.log('Messages linked successfully');
res.json({ success: true });
} catch (error) {
console.error('Error linking guest messages:', error);
res.status(500).json({ success: false, error: 'Internal server error' });
}
});
// Обновляем маршрут верификации Telegram
router.post('/auth/telegram/verify', async (req, res) => {
// ... существующий код ...
if (result.success) {
// Если есть гостевые сообщения, обрабатываем их
if (req.session.guestId) {
await processGuestMessages(userId, req.session.guestId);
}
res.json({
success: true,
userId: userId,
telegramId: result.telegramId,
isAdmin: req.session.isAdmin || false,
authenticated: true
});
}
});
// Маршрут для удаления сообщений
router.delete('/message/:id', requireAuth, async (req, res) => {
try {
const messageId = req.params.id;
const userId = req.session.userId;
// Проверяем права на удаление
const messageCheck = await db.query(
`SELECT m.id
FROM messages m
JOIN conversations c ON m.conversation_id = c.id
WHERE m.id = $1 AND c.user_id = $2`,
[messageId, userId]
);
if (messageCheck.rows.length === 0) {
return res.status(403).json({ error: 'Forbidden' });
}
// Удаляем сообщение
await db.query(
'DELETE FROM messages WHERE id = $1',
[messageId]
);
res.json({ success: true });
} catch (error) {
logger.error('Error deleting message:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
module.exports = router;