399 lines
13 KiB
JavaScript
399 lines
13 KiB
JavaScript
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;
|