diff --git a/backend/db.js b/backend/db.js index 9e10da7..cdc7d9e 100644 --- a/backend/db.js +++ b/backend/db.js @@ -25,10 +25,25 @@ const query = (text, params) => { return pool.query(text, params); }; +// Функция для сохранения гостевого сообщения в базе данных +async function saveGuestMessageToDatabase(message, language, guestId) { + try { + await query(` + INSERT INTO guest_messages (guest_id, content, language, created_at) + VALUES ($1, $2, $3, NOW()) + `, [guestId, message, language]); + console.log('Гостевое сообщение успешно сохранено:', message); + } catch (error) { + console.error('Ошибка при сохранении гостевого сообщения:', error); + throw error; // Пробрасываем ошибку дальше + } +} + // Экспортируем функции для работы с базой данных module.exports = { query, pool, + saveGuestMessageToDatabase, }; // Функция для создания временного хранилища данных в памяти diff --git a/backend/routes/auth.js b/backend/routes/auth.js index 2b6bdb5..50c1d92 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -12,6 +12,7 @@ const { verifySignature, checkAccess, findOrCreateUser } = require('../utils/aut const authService = require('../services/auth-service'); const { SiweMessage } = require('siwe'); const { sendEmail } = require('../services/emailBot'); +const { verificationCodes } = require('../services/telegramBot'); // Создайте лимитер для попыток аутентификации const authLimiter = rateLimit({ @@ -123,12 +124,20 @@ router.post('/verify', async (req, res) => { const { userId, isAdmin } = await findOrCreateUser(address); console.log('User found/created:', { userId, isAdmin }); + // Сохраняем guestId перед обновлением сессии + const currentGuestId = req.session.guestId; + // Устанавливаем пользователя в сессии req.session.userId = userId; req.session.address = address; req.session.isAdmin = isAdmin; req.session.authenticated = true; + // Сохраняем guestId в новой сессии + if (currentGuestId) { + req.session.guestId = currentGuestId; + } + // Сохраняем сессию ПЕРЕД отправкой ответа await new Promise((resolve, reject) => { req.session.save(err => { @@ -142,12 +151,20 @@ router.post('/verify', async (req, res) => { }); }); - // Добавляем задержку для гарантии сохранения сессии (временное решение) - await new Promise(resolve => setTimeout(resolve, 100)); - - console.log('Authentication successful for user:', { userId, address, isAdmin }); + console.log('Authentication successful for user:', { + userId, + address, + isAdmin, + guestId: currentGuestId + }); console.log('Session after save:', req.session); + // Обрабатываем гостевые сообщения, если они есть + if (currentGuestId) { + console.log(`Processing guest messages for guestId: ${currentGuestId}`); + await authService.processGuestMessages(userId, currentGuestId); + } + return res.json({ authenticated: true, userId, @@ -156,8 +173,8 @@ router.post('/verify', async (req, res) => { authType: 'wallet' }); } catch (error) { - console.error('Error verifying signature:', error); - return res.status(500).json({ error: 'Internal server error' }); + console.error('Error during wallet verification:', error); + res.status(500).json({ error: 'Internal server error' }); } }); @@ -604,83 +621,120 @@ router.get('/telegram/code', async (req, res) => { } }); +// Функция для проверки кода Telegram +async function verifyTelegramCode(code) { + try { + // Используем глобальное хранилище кодов + const verificationCodes = global.verificationCodes; + + if (!verificationCodes) { + return { success: false, error: 'Система верификации не инициализирована' }; + } + + // Ищем chatId по коду + for (const [chatId, data] of verificationCodes.entries()) { + if (data.code === code) { + // Проверяем срок действия + if (Date.now() > data.expires) { + verificationCodes.delete(chatId); + return { success: false, error: 'Код истек' }; + } + + // Код верный и не истек + const telegramId = chatId; + verificationCodes.delete(chatId); + return { + success: true, + telegramId: telegramId + }; + } + } + return { success: false, error: 'Неверный код' }; + } catch (error) { + console.error('Error in verifyTelegramCode:', error); + throw error; + } +} + +// Функция для проверки баланса токенов +async function checkTokenBalance(address) { + try { + const authService = require('../services/auth-service'); + const isAdmin = await authService.checkTokensAndUpdateRole(address); + return isAdmin; + } catch (error) { + console.error('Error checking token balance:', error); + return false; + } +} + // Маршрут для верификации Telegram router.post('/telegram/verify', async (req, res) => { + const { code } = req.body; + try { - const { telegramId, code } = req.body; - const verificationData = req.session.telegramVerificationData; + const telegramBot = require('../services/telegramBot'); + const result = await telegramBot.verifyCode(code); - // Проверяем, что код существует и не истек - if (!verificationData || - verificationData.code !== code || - Date.now() > verificationData.expires) { - return res.status(400).json({ - success: false, - error: 'Неверный или истекший код подтверждения' + if (result.success) { + // Проверяем, что у нас есть telegramId + if (!result.telegramId) { + return res.status(400).json({ error: 'Invalid Telegram ID' }); + } + + // Создаем или находим пользователя + const userResult = await pool.query( + `INSERT INTO users (created_at) + VALUES (NOW()) + RETURNING id`, + ); + + const userId = userResult.rows[0].id; + + // Добавляем Telegram идентификатор + await pool.query( + `INSERT INTO user_identities + (user_id, identity_type, identity_value, verified, created_at) + VALUES ($1, 'telegram', $2, true, NOW()) + ON CONFLICT (identity_type, identity_value) + DO UPDATE SET verified = true + RETURNING user_id`, + [userId, result.telegramId] + ); + + // Обновляем сессию + req.session.userId = userId; + req.session.authenticated = true; + req.session.authType = 'telegram'; + req.session.telegramId = result.telegramId; + + // Если есть подключенный кошелек, проверяем баланс токенов + if (req.session.address) { + const isAdmin = await checkTokenBalance(req.session.address); + req.session.isAdmin = isAdmin; + } + + // Сохраняем сессию + await new Promise((resolve, reject) => { + req.session.save(err => { + if (err) reject(err); + else resolve(); + }); + }); + + return res.json({ + success: true, + userId: userId, + telegramId: result.telegramId, + isAdmin: req.session.isAdmin || false, + authenticated: true }); } - // Ищем или создаем пользователя с этим Telegram ID - const result = await db.query( - 'SELECT * FROM find_or_create_user_by_identity($1, $2)', - ['telegram', telegramId] - ); - - const userId = result.rows[0].user_id; - const isNew = result.rows[0].is_new; - - // Проверяем, есть ли у пользователя связанный кошелек - const walletResult = await db.query(` - SELECT identity_value - FROM user_identities ui - WHERE ui.user_id = $1 AND ui.identity_type = 'wallet' - `, [userId]); - - const hasWallet = walletResult.rows.length > 0; - let walletAddress = null; - let isAdmin = false; - - // Если есть кошелек, проверяем наличие токенов - if (hasWallet) { - walletAddress = walletResult.rows[0].identity_value; - const userResult = await db.query('SELECT is_admin FROM users WHERE id = $1', [userId]); - isAdmin = userResult.rows[0].is_admin; - } - - // Устанавливаем сессию - req.session.authenticated = true; - req.session.userId = userId; - req.session.authType = 'telegram'; - req.session.telegramId = telegramId; - req.session.isAdmin = isAdmin; - if (walletAddress) { - req.session.address = walletAddress; - } - - // Сохраняем сессию - await new Promise((resolve, reject) => { - req.session.save(err => { - if (err) reject(err); - else resolve(); - }); - }); - - // Очищаем данные верификации - delete req.session.telegramVerificationData; - - res.json({ - success: true, - authenticated: true, - userId, - telegramId, - isAdmin, - hasWallet, - walletAddress, - isNew - }); + res.status(400).json({ error: result.error || 'Invalid verification code' }); } catch (error) { - logger.error(`Error in telegram verification: ${error.message}`); - res.status(500).json({ success: false, error: 'Внутренняя ошибка сервера' }); + console.error('Error in telegram verification:', error); + res.status(500).json({ error: 'Internal server error' }); } }); @@ -1019,7 +1073,7 @@ router.get('/email/auth-status/:token', async (req, res) => { } }); -// Маршрут для прямой проверки кода, введенного пользователем +// Маршрут для проверки кода, введенного пользователем router.post('/email/verify-code', async (req, res) => { try { const { email, code } = req.body; @@ -1027,19 +1081,19 @@ router.post('/email/verify-code', async (req, res) => { if (!email || !code) { return res.status(400).json({ success: false, error: 'Email и код обязательны' }); } - + const EmailBotService = require('../services/emailBot'); const emailBot = new EmailBotService(process.env.EMAIL_USER, process.env.EMAIL_PASSWORD); // Проверяем код из хранилища - const verificationData = emailBot.verificationCodes.get(email.toLowerCase()); + const verificationData = EmailBotService.verificationCodes.get(email.toLowerCase()); if (!verificationData) { return res.status(400).json({ success: false, error: 'Код подтверждения не найден' }); } if (Date.now() > verificationData.expires) { - emailBot.verificationCodes.delete(email.toLowerCase()); + EmailBotService.verificationCodes.delete(email.toLowerCase()); return res.status(400).json({ success: false, error: 'Срок действия кода истек' }); } @@ -1083,21 +1137,8 @@ router.post('/email/verify-code', async (req, res) => { req.session.email = email.toLowerCase(); req.session.authType = 'email'; - // Если был временный ID, удаляем его - if (req.session.tempUserId) { - delete req.session.tempUserId; - } - - // Сохраняем сессию - await new Promise((resolve, reject) => { - req.session.save(err => { - if (err) reject(err); - else resolve(); - }); - }); - // Удаляем код из хранилища - emailBot.verificationCodes.delete(email.toLowerCase()); + EmailBotService.verificationCodes.delete(email.toLowerCase()); return res.json({ success: true, @@ -1107,9 +1148,26 @@ router.post('/email/verify-code', async (req, res) => { }); } catch (error) { - logger.error(`Error verifying email code: ${error.message}`); + console.error('Error verifying email code:', error); return res.status(500).json({ success: false, error: 'Ошибка сервера' }); } }); +// Маршрут для очистки сессии +router.post('/clear-session', async (req, res) => { + try { + // Очищаем все данные сессии + req.session.destroy((err) => { + if (err) { + console.error('Error destroying session:', err); + return res.status(500).json({ error: 'Internal server error' }); + } + res.json({ success: true }); + }); + } catch (error) { + console.error('Error clearing session:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + module.exports = router; \ No newline at end of file diff --git a/backend/routes/chat.js b/backend/routes/chat.js index 66b0ef5..280417f 100644 --- a/backend/routes/chat.js +++ b/backend/routes/chat.js @@ -6,6 +6,7 @@ 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') { @@ -66,149 +67,147 @@ async function getAIResponse(message, language = 'ru') { } } -// Обработчик сообщений чата -router.post('/message', requireAuth, async (req, res) => { - console.log('Сессия в /api/chat/message:', req.session); - console.log('Аутентифицирован:', req.session.authenticated); - +// Функция для обработки гостевых сообщений после аутентификации +async function processGuestMessages(userId, guestId) { try { - const { message, language = 'ru' } = req.body; - const userId = typeof req.session.userId === 'object' - ? req.session.userId.userId - : req.session.userId; - - console.log(`Получено сообщение: ${message}, язык: ${language}, userId: ${userId}`); + console.log(`Starting to process guest messages for user ${userId} with guestId ${guestId}`); - // Проверяем, что userId существует - if (!userId) { - return res.status(400).json({ error: 'User ID is required' }); - } + // Получаем все гостевые сообщения + 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] + ); - // Определяем язык сообщения, если не указан явно - let detectedLanguage = language; - if (!language || language === 'auto') { - // Простая эвристика для определения языка - const cyrillicPattern = /[а-яА-ЯёЁ]/; - detectedLanguage = cyrillicPattern.test(message) ? 'ru' : 'en'; - } + console.log(`Found ${guestMessages.rows.length} guest messages to process`); - // Формируем системный промпт в зависимости от языка - let systemPrompt = ''; - if (detectedLanguage === 'ru') { - systemPrompt = 'Вы - полезный ассистент. Отвечайте на русском языке.'; - } else { - systemPrompt = 'You are a helpful assistant. Respond in English.'; - } + // Обновляем 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] + ); - // Отправляем запрос к Ollama с указанием языка - console.log(`Отправка запроса к Ollama (модель: ${process.env.OLLAMA_MODEL || 'mistral'}, язык: ${detectedLanguage}): ${message}`); - - // Проверяем доступность Ollama - console.log('Проверка доступности Ollama...'); - try { - const response = await fetch(`${process.env.OLLAMA_BASE_URL || 'http://localhost:11434'}/api/tags`); - const data = await response.json(); - console.log('Ollama доступен. Доступные модели:'); - data.models.forEach(model => { - console.log(`- ${model.name}`); - }); - } catch (error) { - console.error('Ошибка при проверке доступности Ollama:', error); - return res.status(500).json({ error: 'Сервис Ollama недоступен' }); - } - - // Создаем экземпляр 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...'); - - // Получаем ответ от модели - let aiResponse; - try { - const response = await chat.invoke(message); - aiResponse = response.content; - console.log('Ответ AI:', aiResponse); - } catch (error) { - console.error('Ошибка при вызове ChatOllama:', error); + // Обрабатываем каждое гостевое сообщение + for (const msg of guestMessages.rows) { + console.log(`Processing guest message ${msg.id}: ${msg.content}`); - // Альтернативный метод запроса через прямой 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(); - aiResponse = data.response; - console.log('Ответ AI (альтернативный метод):', aiResponse); - } catch (fallbackError) { - console.error('Ошибка при использовании альтернативного метода:', fallbackError); - throw error; // Выбрасываем исходную ошибку - } + // Получаем язык из метаданных + 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 getAIResponse(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}`); } - // Получаем или создаем диалог - let conversationId; - const conversationResult = await db.query(` - SELECT id FROM conversations - WHERE user_id = $1 - ORDER BY updated_at DESC - LIMIT 1 - `, [userId]); + console.log(`Successfully processed all guest messages for user ${userId}`); + return true; + } catch (error) { + console.error('Error processing guest messages:', error); + return false; + } +} - if (conversationResult.rows.length === 0) { - // Создаем новый диалог - const newConversationResult = await db.query(` - INSERT INTO conversations (user_id, created_at, updated_at) - VALUES ($1, NOW(), NOW()) - RETURNING id - `, [userId]); - conversationId = newConversationResult.rows[0].id; - console.log('Created new conversation:', conversationId); - } else { - conversationId = conversationResult.rows[0].id; - console.log('Using existing conversation:', conversationId); - } +// Обработчик для гостевых сообщений +router.post('/guest-message', async (req, res) => { + const { message, language } = req.body; + + // Генерируем временный ID сессии, если его нет + if (!req.session.guestId) { + req.session.guestId = crypto.randomBytes(16).toString('hex'); + } + + try { + // Создаем запись в conversations для гостя + const conversationResult = await db.query( + `INSERT INTO conversations (created_at) + VALUES (NOW()) + RETURNING id` + ); + + const conversationId = conversationResult.rows[0].id; + + // Создаем метаданные + const metadata = { + guest_id: req.session.guestId, + language: language || 'en' + }; + + // Сохраняем только сообщение пользователя + await db.query( + `INSERT INTO messages + (conversation_id, sender_type, content, channel, metadata, created_at) + VALUES ($1, 'guest', $2, 'chat', $3, NOW())`, + [ + conversationId, + message, + JSON.stringify(metadata) + ] + ); + + res.json({ success: true }); + } catch (error) { + console.error('Error processing message:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +// Маршрут для обычных сообщений (для аутентифицированных пользователей) +router.post('/message', requireAuth, async (req, res) => { + const { message, language } = req.body; + const userId = req.session.userId; + + try { + // Создаем новую беседу или получаем существующую + const conversationResult = await db.query( + `INSERT INTO conversations (user_id, created_at) + VALUES ($1, NOW()) + RETURNING id`, + [userId] + ); + + const conversationId = conversationResult.rows[0].id; // Сохраняем сообщение пользователя - const userMessageResult = await db.query(` - INSERT INTO messages (conversation_id, sender_type, sender_id, content, channel, created_at) - VALUES ($1, 'user', $2, $3, 'web', NOW()) - RETURNING id - `, [conversationId, userId, message]); - console.log('Saved user message:', userMessageResult.rows[0].id); + await db.query( + `INSERT INTO messages + (conversation_id, sender_type, content, channel, created_at) + VALUES ($1, 'user', $2, 'chat', NOW())`, + [conversationId, message] + ); - // Сохраняем ответ ИИ - const aiMessageResult = await db.query(` - INSERT INTO messages (conversation_id, sender_type, content, channel, created_at) - VALUES ($1, 'ai', $2, 'web', NOW()) - RETURNING id - `, [conversationId, aiResponse]); - console.log('Saved AI message:', aiMessageResult.rows[0].id); + // Получаем ответ от AI + const aiResponse = await getAIResponse(message, language); - // Обновляем время последнего сообщения в диалоге - await db.query(` - UPDATE conversations - SET updated_at = NOW() - WHERE id = $1 - `, [conversationId]); + // Сохраняем ответ AI + await db.query( + `INSERT INTO messages + (conversation_id, sender_type, content, channel, created_at) + VALUES ($1, 'assistant', $2, 'chat', NOW())`, + [conversationId, aiResponse] + ); res.json({ - reply: aiResponse, - language: detectedLanguage + success: true, + message: aiResponse }); } catch (error) { console.error('Error processing message:', error); @@ -233,82 +232,49 @@ router.get('/models', async (req, res) => { }); // Получение истории сообщений -router.get('/history', requireAuth, async (req, res) => { +router.get('/history', async (req, res) => { + const limit = parseInt(req.query.limit) || 2; // По умолчанию только последнее сообщение и ответ + const offset = parseInt(req.query.offset) || 0; + try { - // Получаем ID пользователя из сессии или из объекта пользователя - const userId = req.session?.userId || req.user?.userId; - - console.log('Запрос истории чата для пользователя:', userId); - console.log('User object from request:', req.user); - - // Проверяем, что userId существует - if (!userId) { - console.error('Пользователь не аутентифицирован'); + if (!req.session.authenticated || !req.session.userId) { return res.status(401).json({ error: 'Unauthorized' }); } - - // Получаем историю сообщений из базы данных - console.log('Querying chat history for user:', userId); - - // Проверяем, существует ли таблица messages - try { - const tableCheck = await db.query(` - SELECT EXISTS ( - SELECT FROM information_schema.tables - WHERE table_name = 'messages' - ); - `); - - console.log('Table messages exists:', tableCheck.rows[0].exists); - - if (tableCheck.rows[0].exists) { - // Используем таблицу messages - const result = await db.query(` - SELECT m.*, 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 ASC - `, [userId]); - - console.log(`Найдено ${result.rows.length} сообщений для пользователя ${userId}`); - - return res.json({ messages: result.rows }); - } else { - // Проверяем, существует ли таблица chat_history - const chatHistoryCheck = await db.query(` - SELECT EXISTS ( - SELECT FROM information_schema.tables - WHERE table_name = 'chat_history' - ); - `); - - console.log('Table chat_history exists:', chatHistoryCheck.rows[0].exists); - - if (chatHistoryCheck.rows[0].exists) { - // Используем таблицу chat_history - const result = await db.query(` - SELECT * FROM chat_history - WHERE user_id = $1 - ORDER BY created_at ASC - `, [userId]); - - console.log(`Найдено ${result.rows.length} сообщений для пользователя ${userId}`); - - return res.json({ messages: result.rows }); - } else { - // Ни одна из таблиц не существует - console.log('No message tables found in database'); - return res.json({ messages: [] }); - } - } - } catch (error) { - console.error('Error checking tables:', error); - return res.json({ messages: [] }); - } + + // Получаем общее количество сообщений + 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 + OR (m.metadata->>'guest_id' = $2 AND m.metadata->>'processed' = 'true')`, + [req.session.userId, req.session.guestId] + ); + + const total = parseInt(countResult.rows[0].total); + + // Получаем сообщения с пагинацией + const result = await db.query( + `SELECT m.id, m.content, m.sender_type as role, m.created_at, + c.user_id, m.metadata + FROM messages m + JOIN conversations c ON m.conversation_id = c.id + WHERE c.user_id = $1 + OR (m.metadata->>'guest_id' = $2 AND m.metadata->>'processed' = 'true') + ORDER BY m.created_at DESC + LIMIT $3 OFFSET $4`, + [req.session.userId, req.session.guestId, limit, offset] + ); + + return res.json({ + success: true, + messages: result.rows.reverse(), + total + }); + } catch (error) { - console.error('Error fetching chat history:', error); - res.status(500).json({ error: 'Internal server error' }); + console.error('Error getting chat history:', error); + return res.status(500).json({ error: 'Internal server error' }); } }); @@ -345,39 +311,6 @@ router.get('/admin/history', requireAdmin, async (req, res) => { } }); -// Обработчик для гостевых сообщений -router.post('/guest-message', async (req, res) => { - try { - const { message, language } = req.body; - console.log(`Получено гостевое сообщение: ${message} язык: ${language}`); - - // Генерируем временный ID сессии, если его нет - if (!req.session.guestId) { - req.session.guestId = crypto.randomBytes(16).toString('hex'); - } - - // Сохраняем сообщение в базе данных с временным ID - await db.query(` - INSERT INTO guest_messages (guest_id, content, language, created_at) - VALUES ($1, $2, $3, NOW()) - `, [req.session.guestId, message, language]); - - // Отправляем запрос к AI - const aiResponse = await getAIResponse(message, language); - - // Сохраняем ответ AI в базе данных - await db.query(` - INSERT INTO guest_messages (guest_id, content, language, created_at, is_ai) - VALUES ($1, $2, $3, NOW(), true) - `, [req.session.guestId, aiResponse, language]); - - return res.json({ message: aiResponse, reply: aiResponse }); - } catch (error) { - console.error('Error processing guest message:', error); - return res.status(500).json({ error: 'Internal server error' }); - } -}); - // Обработчик для связывания гостевых сообщений с пользователем router.post('/link-guest-messages', requireAuth, async (req, res) => { try { @@ -413,4 +346,64 @@ router.post('/link-guest-messages', requireAuth, async (req, res) => { } }); +// Обновляем маршрут верификации кошелька +router.post('/verify', async (req, res) => { + const { address, signature, message } = req.body; + + try { + // ... существующий код верификации ... + + // После успешной верификации и создания пользователя + if (req.session.guestId) { + console.log('Found guest messages, processing...'); + await processGuestMessages(userId, req.session.guestId); + } + + // Сохраняем данные в сессии + req.session.userId = userId; + req.session.address = address; + req.session.isAdmin = isAdmin; + req.session.authenticated = true; + + console.log('Authentication successful for user:', { + userId, + address, + isAdmin, + guestId: req.session.guestId + }); + + res.json({ + authenticated: true, + userId: userId, + address: address, + isAdmin: isAdmin, + authType: 'wallet' + }); + + } catch (error) { + console.error('Error during wallet verification:', error); + res.status(500).json({ 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 + }); + } +}); + module.exports = router; diff --git a/backend/services/auth-service.js b/backend/services/auth-service.js index d628ee0..4f79689 100644 --- a/backend/services/auth-service.js +++ b/backend/services/auth-service.js @@ -1,6 +1,7 @@ const db = require('../db'); const logger = require('../utils/logger'); const { ethers } = require('ethers'); +const { processMessage } = require('./ai-assistant'); // Используем AI Assistant // В начале файла auth-service.js const getProvider = (network) => { @@ -246,6 +247,108 @@ class AuthService { return false; } } + + /** + * Обрабатывает гостевые сообщения после аутентификации + */ + async processGuestMessages(userId, guestId) { + try { + logger.info(`Processing guest messages for user ${userId} with guestId ${guestId}`); + + // Сначала обновляем 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] + ); + + // Получаем все гостевые сообщения без ответов + 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 + AND NOT EXISTS ( + SELECT 1 FROM messages + WHERE conversation_id = m.conversation_id + AND sender_type = 'assistant' + ) + ORDER BY m.created_at ASC`, + [guestId] + ); + + logger.info(`Found ${guestMessages.rows.length} unprocessed guest messages`); + + // Обрабатываем каждое гостевое сообщение + for (const msg of guestMessages.rows) { + logger.info(`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 Assistant для обработки сообщения + const aiResponse = await processMessage(userId, 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] + ); + + logger.info(`Saved AI response for message ${msg.id}`); + } + + // Обновляем метаданные сообщений, чтобы показать, что они обработаны + await db.query( + `UPDATE messages m + SET metadata = jsonb_set( + CASE + WHEN m.metadata IS NULL THEN '{}'::jsonb + ELSE m.metadata::jsonb + END, + '{processed}', + 'true' + ) + WHERE m.metadata->>'guest_id' = $1`, + [guestId] + ); + + logger.info(`Successfully processed all guest messages for user ${userId}`); + return true; + } catch (error) { + logger.error('Error processing guest messages:', error); + return false; + } + } + + async disconnect() { + try { + // Очищаем состояние аутентификации + this.isAuthenticated = false; + this.userId = null; + this.address = null; + this.isAdmin = false; + this.authType = null; + + // Очищаем сессию + localStorage.removeItem('auth'); + + // Очищаем guestId + localStorage.removeItem('guestId'); + + return true; + } catch (error) { + logger.error('Error during disconnect:', error); + return false; + } + } } module.exports = new AuthService(); \ No newline at end of file diff --git a/backend/services/telegramBot.js b/backend/services/telegramBot.js index bc02ae9..e39fe88 100644 --- a/backend/services/telegramBot.js +++ b/backend/services/telegramBot.js @@ -1,273 +1,218 @@ const TelegramBot = require('node-telegram-bot-api'); const logger = require('../utils/logger'); +const { pool } = require('../db'); +const crypto = require('crypto'); // Создаем бота const token = process.env.TELEGRAM_BOT_TOKEN; let bot = null; -// Добавим хранилище для кодов подтверждения -const verificationCodes = new Map(); // Формат: { telegramId: { code: '123456', token: 'auth_token', expires: timestamp } } +/** + * Функция для отправки кода подтверждения + */ +async function sendVerificationCode(chatId) { + try { + // Генерируем код и токен + const code = Math.floor(100000 + Math.random() * 900000).toString(); + const authToken = crypto.randomBytes(32).toString('hex'); + + // Создаем пользователя и сохраняем код в базу данных + const result = await pool.query( + `WITH new_user AS ( + INSERT INTO users (created_at) + VALUES (NOW()) + RETURNING id + ) + INSERT INTO telegram_auth_tokens + (user_id, token, verification_code, telegram_id, expires_at) + VALUES ( + (SELECT id FROM new_user), + $1, $2, $3, + NOW() + INTERVAL '5 minutes' + ) + RETURNING user_id`, + [authToken, code, chatId.toString()] + ); + + // Отправляем код с инлайн-кнопкой + const sentMessage = await bot.sendMessage(chatId, + 'Привет! Я бот для аутентификации в DApp for Business.\n\n' + + '🔐 Ваш код подтверждения:\n\n' + + `${code}\n\n` + + 'Введите этот код на сайте для завершения авторизации.\n' + + 'Код действителен в течение 5 минут.', + { + parse_mode: 'HTML', + reply_markup: { + inline_keyboard: [ + [{ text: '🔄 Получить новый код', callback_data: 'new_code' }] + ] + } + } + ); + + // Удаляем сообщение через 30 секунд + setTimeout(async () => { + try { + await bot.deleteMessage(chatId, sentMessage.message_id); + await bot.sendMessage(chatId, + 'Для получения нового кода используйте команду /start или меню команд', + { + reply_markup: { + keyboard: [ + [{ text: '/start' }] + ], + resize_keyboard: true, + persistent: true + } + } + ); + } catch (error) { + console.error('Error deleting message:', error); + } + }, 30000); + + return { code, token: authToken, userId: result.rows[0].user_id }; + } catch (error) { + console.error('Error sending verification code:', error); + throw error; + } +} + +/** + * Функция для проверки кода + */ +async function verifyCode(code) { + try { + const result = await pool.query( + `SELECT token, telegram_id, user_id + FROM telegram_auth_tokens + WHERE verification_code = $1 + AND expires_at > NOW() + AND NOT used`, + [code] + ); + + if (result.rows.length === 0) { + return { success: false, error: 'Неверный или истекший код' }; + } + + const { token, telegram_id, user_id } = result.rows[0]; + + // Помечаем токен как использованный + await pool.query( + 'UPDATE telegram_auth_tokens SET used = true WHERE token = $1', + [token] + ); + + // Добавляем Telegram ID в таблицу идентификаторов + await pool.query( + `INSERT INTO user_identities + (user_id, identity_type, identity_value, verified, created_at) + VALUES ($1, 'telegram', $2, true, NOW()) + ON CONFLICT (identity_type, identity_value) + DO UPDATE SET verified = true`, + [user_id, telegram_id] + ); + + return { + success: true, + telegramId: telegram_id, + userId: user_id + }; + } catch (error) { + console.error('Error verifying code:', error); + throw error; + } +} /** * Инициализация Telegram бота - * @returns {Object|null} - Объект с методами для работы с ботом или null, если инициализация не удалась */ function initTelegramBot() { if (!token) { - console.warn('TELEGRAM_BOT_TOKEN not set, Telegram integration disabled'); + console.warn('TELEGRAM_BOT_TOKEN not set'); return null; } try { - // Создаем бота с опцией polling - bot = new TelegramBot(token, { polling: true }); + // Создаем бота с опцией обработки ошибок + bot = new TelegramBot(token, { + polling: { + autoStart: true, + params: { + timeout: 10 + } + }, + request: { + timeout: 30000, // увеличиваем таймаут до 30 секунд + proxy: process.env.HTTPS_PROXY // используем прокси если есть + } + }); + console.log('Telegram bot initialized'); - // Регистрируем обработчики событий - registerHandlers(); + // Очищаем все предыдущие обработчики + bot.removeAllListeners(); + + // Устанавливаем команды бота с обработкой ошибок + bot.setMyCommands([ + { command: '/start', description: 'Получить код подтверждения' }, + { command: '/help', description: 'Показать справку' } + ]).catch(error => { + console.warn('Error setting bot commands:', error); + // Продолжаем работу даже если не удалось установить команды + }); + + // Обработчик команды /start + bot.onText(/\/start/, async (msg) => { + const chatId = msg.chat.id; + try { + await sendVerificationCode(chatId); + } catch (error) { + console.error('Error handling /start:', error); + await bot.sendMessage(chatId, 'Произошла ошибка. Пожалуйста, попробуйте позже.') + .catch(err => console.error('Error sending error message:', err)); + } + }); + + // Обработчик ошибок polling + bot.on('polling_error', (error) => { + console.error('Telegram bot polling error:', error); + // Перезапускаем polling при ошибке + setTimeout(() => { + try { + bot.startPolling(); + } catch (e) { + console.error('Error restarting polling:', e); + } + }, 10000); // пробуем перезапустить через 10 секунд + }); + + // Обработчик остановки polling + bot.on('stop', () => { + console.log('Bot polling stopped'); + // Пробуем перезапустить + setTimeout(() => { + try { + bot.startPolling(); + } catch (e) { + console.error('Error restarting polling after stop:', e); + } + }, 5000); + }); + + return bot; - return { - bot, - sendMessage: (chatId, text) => bot.sendMessage(chatId, text) - }; } catch (error) { console.error('Error initializing Telegram bot:', error); return null; } } -/** - * Регистрация обработчиков событий для бота - */ -function registerHandlers() { - // Обработчик /start - bot.onText(/\/start(.*)/, async (msg, match) => { - const chatId = msg.chat.id; - const param = match[1] ? match[1].trim() : ''; - - console.log(`Получена команда /start с параметром: "${param}" от пользователя ${chatId}`); - - if (param.startsWith('auth_')) { - // Это токен авторизации через deep link - const authToken = param.replace('auth_', ''); - console.log(`Обработка токена авторизации: ${authToken}`); - - try { - // Проверяем, существует ли токен - const { pool } = require('../db'); - const tokenResult = await pool.query( - 'SELECT user_id, expires_at FROM telegram_auth_tokens WHERE token = $1', - [authToken] - ); - - if (tokenResult.rows.length === 0 || new Date(tokenResult.rows[0].expires_at) < new Date()) { - bot.sendMessage(chatId, '❌ Недействительный или истекший токен авторизации.'); - return; - } - - // Генерируем код подтверждения - const verificationCode = Math.floor(100000 + Math.random() * 900000).toString(); // 6-значный код - - // Сохраняем в хранилище - verificationCodes.set(chatId.toString(), { - code: verificationCode, - token: authToken, - expires: Date.now() + 5 * 60 * 1000 // Срок действия 5 минут - }); - - // Отправляем код пользователю - bot.sendMessage(chatId, - '🔐 Для завершения связывания аккаунта, пожалуйста, введите этот код:\n\n' + - `${verificationCode}\n\n` + - 'Код действителен в течение 5 минут.', - { parse_mode: 'HTML' } - ); - } catch (error) { - console.error('Error processing auth token:', error); - bot.sendMessage(chatId, '❌ Произошла ошибка при обработке запроса авторизации.'); - } - } else { - // Получаем последний активный токен для этого чата, если есть - const { pool } = require('../db'); - try { - const lastTokenResult = await pool.query(` - SELECT token FROM telegram_auth_tokens - WHERE expires_at > NOW() AND used = FALSE - ORDER BY created_at DESC LIMIT 1 - `); - - if (lastTokenResult.rows.length > 0) { - const authToken = lastTokenResult.rows[0].token; - - // Генерируем код подтверждения - const verificationCode = Math.floor(100000 + Math.random() * 900000).toString(); // 6-значный код - - // Сохраняем в хранилище - verificationCodes.set(chatId.toString(), { - code: verificationCode, - token: authToken, - expires: Date.now() + 5 * 60 * 1000 // Срок действия 5 минут - }); - - // Отправляем код пользователю - bot.sendMessage(chatId, - '🔐 Для завершения связывания аккаунта, пожалуйста, введите этот код:\n\n' + - `${verificationCode}\n\n` + - 'Код действителен в течение 5 минут.', - { parse_mode: 'HTML' } - ); - return; - } - } catch (error) { - console.error('Error checking last token:', error); - } - - // Если нет активного токена, отправляем стандартное сообщение - bot.sendMessage(chatId, - 'Привет! Я бот для аутентификации в DApp for Business.\n\n' + - 'Для связи с вашим аккаунтом используйте кнопку на сайте.' - ); - } - }); - - // Обработчик для проверки кода подтверждения - bot.on('message', async (msg) => { - const chatId = msg.chat.id; - const text = msg.text; - - // Игнорируем команды - if (text && text.startsWith('/')) return; - - // Проверяем, есть ли ожидающая верификация для этого чата - const verificationData = verificationCodes.get(chatId.toString()); - - if (verificationData && text === verificationData.code) { - // Код верный, проверяем срок действия - if (Date.now() > verificationData.expires) { - bot.sendMessage(chatId, '❌ Срок действия кода истек. Пожалуйста, начните процесс заново.'); - verificationCodes.delete(chatId.toString()); - return; - } - - // Код верный и актуальный, завершаем аутентификацию - try { - const result = await linkTelegramAccount(chatId.toString(), verificationData.token); - - if (result.success) { - bot.sendMessage(chatId, - '✅ Аутентификация успешна!\n\n' + - 'Ваш Telegram аккаунт связан с DApp for Business.\n' + - 'Теперь вы можете использовать бота для общения с системой.' - ); - } else { - bot.sendMessage(chatId, - '❌ Ошибка аутентификации: ' + (result.error || 'неизвестная ошибка') - ); - } - - // Удаляем данные верификации - verificationCodes.delete(chatId.toString()); - } catch (error) { - console.error('Error completing authentication:', error); - bot.sendMessage(chatId, '❌ Произошла ошибка при завершении аутентификации.'); - } - } else if (verificationData) { - // Есть ожидающая верификация, но код неверный - bot.sendMessage(chatId, '❌ Неверный код. Пожалуйста, попробуйте еще раз.'); - } else { - // Нет ожидающей верификации - bot.sendMessage(chatId, 'Я могу помочь с аутентификацией. Используйте кнопку на сайте для начала процесса.'); - } - }); - - // Добавить обработку прямых команд аутентификации - bot.onText(/\/auth (.+)/, async (msg, match) => { - const chatId = msg.chat.id; - const authToken = match[1].trim(); - - console.log(`Получена прямая команда авторизации с токеном: ${authToken}`); - - try { - // Связываем Telegram ID с аккаунтом по токену - const result = await linkTelegramAccount(chatId.toString(), authToken); - console.log(`Результат связывания: ${JSON.stringify(result)}`); - - if (result.success) { - bot.sendMessage(chatId, - '✅ Аутентификация успешна!\n\n' + - 'Ваш Telegram аккаунт связан с DApp for Business.\n' + - 'Теперь вы можете использовать бота для общения с системой.' - ); - } else { - bot.sendMessage(chatId, - '❌ Ошибка аутентификации: ' + (result.error || 'неизвестная ошибка') - ); - } - } catch (error) { - console.error('Error linking telegram account:', error); - bot.sendMessage(chatId, '❌ Произошла ошибка при связывании аккаунта.'); - } - }); - - // Обработка ошибок - bot.on('polling_error', (error) => { - logger.error(`[polling_error] ${JSON.stringify(error)}`); - }); - - console.log('Telegram bot handlers registered'); -} - -/** - * Связывание Telegram ID с аккаунтом пользователя - * @param {string} telegramId - ID пользователя в Telegram - * @param {string} authToken - Токен авторизации - * @returns {Promise} - Результат операции - */ -async function linkTelegramAccount(telegramId, authToken) { - try { - console.log(`Попытка связать Telegram ID ${telegramId} с токеном ${authToken}`); - - // Здесь должен быть код для связывания через API или напрямую с БД - const { pool } = require('../db'); - - // Проверяем токен авторизации - const tokenResult = await pool.query( - 'SELECT user_id, expires_at FROM telegram_auth_tokens WHERE token = $1', - [authToken] - ); - - console.log(`Результат запроса токена: ${JSON.stringify(tokenResult.rows)}`); - - if (tokenResult.rows.length === 0 || new Date(tokenResult.rows[0].expires_at) < new Date()) { - console.log('Токен не найден или истек'); - return { success: false, error: 'Недействительный или истекший токен' }; - } - - const userId = tokenResult.rows[0].user_id; - console.log(`Найден пользователь с ID: ${userId}`); - - // Добавляем идентификатор Telegram для пользователя - await pool.query( - 'INSERT INTO user_identities (user_id, identity_type, identity_value, verified, created_at) ' + - 'VALUES ($1, $2, $3, true, NOW()) ' + - 'ON CONFLICT (identity_type, identity_value) ' + - 'DO UPDATE SET user_id = $1, verified = true', - [userId, 'telegram', telegramId] - ); - - // Отмечаем токен как использованный - await pool.query( - 'UPDATE telegram_auth_tokens SET used = true WHERE token = $1', - [authToken] - ); - - return { success: true }; - } catch (error) { - console.error('Error in linkTelegramAccount:', error); - return { success: false, error: 'Внутренняя ошибка сервера' }; - } -} - +// Экспортируем функции module.exports = { - initTelegramBot + initTelegramBot, + verifyCode, + sendVerificationCode }; \ No newline at end of file diff --git a/frontend/src/api/axios.js b/frontend/src/api/axios.js index 127807d..942e8a0 100644 --- a/frontend/src/api/axios.js +++ b/frontend/src/api/axios.js @@ -1,4 +1,5 @@ import axios from 'axios'; +import { useAuthStore } from '../stores/auth'; // Создаем экземпляр axios с базовым URL const instance = axios.create({ @@ -13,11 +14,22 @@ const instance = axios.create({ instance.interceptors.request.use( (config) => { console.log('Axios interceptor running'); - const address = localStorage.getItem('walletAddress'); - if (address) { - console.log('Adding Authorization header in interceptor:', `Bearer ${address}`); - config.headers.Authorization = `Bearer ${address}`; + const authStore = useAuthStore(); + + // Логируем параметры запроса + console.log('Request parameters:', config); + + // Если уже есть заголовок Authorization, не перезаписываем его + if (config.headers.Authorization) { + return config; } + + // Если пользователь аутентифицирован и есть адрес кошелька + if (authStore.isAuthenticated && authStore.address) { + console.log('Adding Authorization header:', `Bearer ${authStore.address}`); + config.headers.Authorization = `Bearer ${authStore.address}`; + } + return config; }, (error) => { @@ -25,4 +37,36 @@ instance.interceptors.request.use( } ); +// Добавляем перехватчик для обработки ответов +instance.interceptors.response.use( + (response) => { + console.log('Response from server:', response.data); + return response; + }, + (error) => { + // Проверяем, что это действительно ошибка авторизации + if (error.response?.status === 401 && + !error.config.url.includes('/auth/') && + !error.config.url.includes('/verify') && + !error.config.url.includes('/chat/history')) { // Не очищаем при ошибке загрузки истории + console.log('Auth error, clearing state'); + const auth = useAuthStore(); + auth.disconnect(); + } + return Promise.reject(error); + } +); + +// Пример функции для отправки гостевого сообщения на сервер +const sendGuestMessageToServer = async (messageText) => { + try { + await axios.post('/api/chat/guest-message', { + message: messageText, + language: userLanguage.value + }); + } catch (error) { + console.error('Ошибка при отправке гостевого сообщения на сервер:', error); + } +}; + export default instance; \ No newline at end of file diff --git a/frontend/src/components/TelegramConnect.vue b/frontend/src/components/TelegramConnect.vue index 5150f89..85dbf74 100644 --- a/frontend/src/components/TelegramConnect.vue +++ b/frontend/src/components/TelegramConnect.vue @@ -1,108 +1,54 @@ diff --git a/frontend/src/components/chat/MessageInput.vue b/frontend/src/components/chat/MessageInput.vue index 26dfb4c..34516da 100644 --- a/frontend/src/components/chat/MessageInput.vue +++ b/frontend/src/components/chat/MessageInput.vue @@ -44,15 +44,40 @@ const handleEnter = (event) => { // Отправка сообщения const sendMessage = async () => { - if (!message.value.trim() || sending.value) return; + const messageText = message.value.trim(); + if (!messageText) return; + + const userMessage = { + id: Date.now(), + content: messageText, + role: auth.isAuthenticated ? 'user' : 'guest', + timestamp: new Date().toISOString() + }; + + messages.value.push(userMessage); try { - sending.value = true; + // Логируем параметры запроса + console.log('Sending message to Ollama:', { + message: messageText, + language: userLanguage.value + }); - const response = await axios.post( - `/api/messages/conversations/${props.conversationId}/messages`, - { content: message.value } - ); + const response = await axios.post('/api/chat/message', { + message: messageText, + language: userLanguage.value + }); + + // Логируем ответ от Ollama + console.log('Response from Ollama:', response.data); + + // Обработка ответа + messages.value.push({ + id: Date.now() + 1, + content: response.data.message, + role: 'assistant', + timestamp: new Date().toISOString() + }); // Очищаем поле ввода message.value = ''; @@ -65,7 +90,7 @@ const sendMessage = async () => { // Уведомляем родительский компонент о новых сообщениях emit('message-sent', [response.data.userMessage, response.data.aiMessage]); } catch (error) { - console.error('Error sending message:', error); + console.error('Ошибка при отправке сообщения:', error); } finally { sending.value = false; } @@ -81,6 +106,61 @@ defineExpose({ resetInput, focus: () => textareaRef.value?.focus(), }); + +const sendGuestMessage = async (messageText) => { + if (!messageText.trim()) return; + + const userMessage = { + id: Date.now(), + content: messageText, + role: 'user', + timestamp: new Date().toISOString(), + isGuest: true + }; + + // Добавляем сообщение пользователя в локальную историю + messages.value.push(userMessage); + + // Сохраняем сообщение в массиве гостевых сообщений + guestMessages.value.push(userMessage); + + // Сохраняем гостевые сообщения в localStorage + localStorage.setItem('guestMessages', JSON.stringify(guestMessages.value)); + + // Очищаем поле ввода + newMessage.value = ''; + + // Прокрутка вниз + await nextTick(); + if (messagesContainer.value) { + messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight; + } + + // Устанавливаем состояние загрузки + isLoading.value = true; + + // Вместо отправки запроса к Ollama, отправляем сообщение с кнопками для аутентификации + const authMessage = { + id: Date.now() + 1, + content: 'Чтобы продолжить, пожалуйста, аутентифицируйтесь.', + role: 'assistant', + timestamp: new Date().toISOString(), + isGuest: true, + showAuthOptions: true // Указываем, что нужно показать кнопки аутентификации + }; + + messages.value.push(authMessage); + guestMessages.value.push(authMessage); + localStorage.setItem('guestMessages', JSON.stringify(guestMessages.value)); + + // Прокрутка вниз + await nextTick(); + if (messagesContainer.value) { + messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight; + } + + isLoading.value = false; +}; diff --git a/frontend/src/stores/auth.js b/frontend/src/stores/auth.js index 6ad609a..da8b275 100644 --- a/frontend/src/stores/auth.js +++ b/frontend/src/stores/auth.js @@ -1,18 +1,37 @@ import { defineStore } from 'pinia'; import axios from '../api/axios'; +const loadAuthState = () => { + const savedAuth = localStorage.getItem('auth'); + if (savedAuth) { + try { + return JSON.parse(savedAuth); + } catch (e) { + console.error('Error parsing saved auth state:', e); + } + } + return null; +}; + export const useAuthStore = defineStore('auth', { - state: () => ({ - user: null, - isAuthenticated: false, - isAdmin: false, - authType: null, - identities: {}, - loading: false, - error: null, - messages: [], - address: null - }), + state: () => { + const savedState = loadAuthState(); + return { + user: null, + isAuthenticated: savedState?.isAuthenticated || false, + isAdmin: savedState?.isAdmin || false, + authType: savedState?.authType || null, + identities: {}, + loading: false, + error: null, + messages: [], + address: null, + wallet: null, + telegramId: savedState?.telegramId || null, + email: null, + userId: savedState?.userId || null + }; + }, actions: { async connectWallet(address, signature, message) { @@ -421,6 +440,99 @@ export const useAuthStore = defineStore('auth', { console.error('Error checking Telegram auth status:', error); return { success: false, error: 'Ошибка проверки статуса' }; } + }, + + async disconnect(router) { + // Проверяем, действительно ли нужно выходить + if (!this.isAuthenticated) { + console.log('Already logged out, skipping disconnect'); + return; + } + + try { + // Сначала пробуем очистить сессию на сервере + await axios.post('/api/auth/clear-session'); + await axios.post('/api/auth/logout'); + + // Очищаем состояние только после успешного выхода + this.clearState(); + + if (router) router.push('/'); + } catch (error) { + console.error('Error during logout:', error); + } + }, + + // Выносим очистку состояния в отдельный метод + clearState() { + this.isAuthenticated = false; + this.wallet = null; + this.messages = []; + this.user = null; + this.address = null; + this.isAdmin = false; + this.authType = null; + this.identities = {}; + this.telegramId = null; + this.userId = null; + + // Очищаем локальное хранилище + localStorage.removeItem('wallet'); + localStorage.removeItem('isAuthenticated'); + localStorage.removeItem('walletAddress'); + localStorage.removeItem('auth'); + }, + + async setWalletAuth(authData) { + this.isAuthenticated = authData.authenticated; + this.address = authData.address; + this.isAdmin = authData.isAdmin; + }, + + async setTelegramAuth(authData) { + this.telegramId = authData.telegramId; + // Проверяем баланс токенов если есть подключенный кошелек + if (this.address) { + await this.checkTokenBalance(); + } + }, + + async checkTokenBalance() { + try { + const response = await axios.get(`/api/auth/check-tokens?address=${this.address}`); + this.isAdmin = response.data.isAdmin; + } catch (error) { + console.error('Error checking token balance:', error); + } + }, + + setAuth(authData) { + console.log('Setting auth state:', authData); + + // Обновляем все поля состояния + this.isAuthenticated = authData.authenticated || authData.isAuthenticated; + this.userId = authData.userId; + this.isAdmin = authData.isAdmin; + this.authType = authData.authType; + this.address = authData.address; + + // Сохраняем состояние в localStorage + const stateToSave = { + isAuthenticated: this.isAuthenticated, + userId: this.userId, + isAdmin: this.isAdmin, + authType: this.authType, + address: this.address + }; + + localStorage.setItem('auth', JSON.stringify(stateToSave)); + + console.log('Auth state updated:', { + isAuthenticated: this.isAuthenticated, + userId: this.userId, + authType: this.authType, + address: this.address + }); } } }); diff --git a/frontend/src/utils/wallet.js b/frontend/src/utils/wallet.js index a54d946..b9f3869 100644 --- a/frontend/src/utils/wallet.js +++ b/frontend/src/utils/wallet.js @@ -1,43 +1,38 @@ +import { ethers } from 'ethers'; import axios from '../api/axios'; import { useAuthStore } from '../stores/auth'; -// Функция для подключения кошелька -async function connectWallet() { +// Переименовываем функцию для соответствия импорту +export async function connectWithWallet() { try { // Проверяем, доступен ли MetaMask if (!window.ethereum) { throw new Error('MetaMask не установлен'); } - // Запрашиваем доступ к аккаунтам - const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); - const address = accounts[0]; - - // Получаем nonce от сервера - const nonceResponse = await axios.get(`/api/auth/nonce?address=${address}`, { - withCredentials: true - }); + // Запрашиваем доступ к кошельку + const provider = new ethers.BrowserProvider(window.ethereum); + const signer = await provider.getSigner(); + const address = await signer.getAddress(); + + // Получаем nonce для подписи + const nonceResponse = await axios.get(`/api/auth/nonce?address=${address}`); const nonce = nonceResponse.data.nonce; - - // Создаем сообщение для подписи + + // Формируем сообщение для подписи const message = `Подпишите это сообщение для аутентификации в DApp for Business. Nonce: ${nonce}`; - - // Запрашиваем подпись - const signature = await window.ethereum.request({ - method: 'personal_sign', - params: [message, address] - }); - - // Отправляем подпись на сервер для верификации + + // Подписываем сообщение + const signature = await signer.signMessage(message); + + // Верифицируем подпись на сервере const response = await axios.post('/api/auth/verify', { address, signature, - message - }, { - withCredentials: true + message: nonce }); - console.log('Успешно подключен:', response.data); + console.log('Wallet verification response:', response.data); // Обновляем состояние в хранилище auth const authStore = useAuthStore(); @@ -47,7 +42,7 @@ async function connectWallet() { address: response.data.address }; authStore.isAdmin = response.data.isAdmin; - authStore.authType = response.data.authType; + authStore.authType = 'wallet'; // Сохраняем адрес кошелька в локальном хранилище localStorage.setItem('walletAddress', address); @@ -55,13 +50,14 @@ async function connectWallet() { return { success: true, authenticated: response.data.authenticated, + userId: response.data.userId, address: response.data.address, isAdmin: response.data.isAdmin, - authType: response.data.authType + authType: 'wallet' }; } catch (error) { - console.error('Ошибка при подключении кошелька:', error); - return { success: false, error: error.message || 'Ошибка подключения кошелька' }; + console.error('Error connecting wallet:', error); + return { success: false, error: error.message }; } } @@ -92,5 +88,3 @@ async function disconnectWallet() { throw error; } } - -export { connectWallet, disconnectWallet }; diff --git a/frontend/src/views/HomeView.vue b/frontend/src/views/HomeView.vue index f6daf94..e516d2d 100644 --- a/frontend/src/views/HomeView.vue +++ b/frontend/src/views/HomeView.vue @@ -7,60 +7,81 @@
- +
+ +
+ +
+
{{ message.content }}
+ + +
+ + + +
- -
-
- -
- -
- -
- - - - - - - -
{{ emailErrorMessage }}
+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+ +
+ {{ emailError }}
@@ -72,11 +93,11 @@
-
@@ -90,6 +111,7 @@ import { useAuthStore } from '../stores/auth'; import WalletConnection from '../components/WalletConnection.vue'; import TelegramConnect from '../components/TelegramConnect.vue'; import axios from '../api/axios'; +import { connectWithWallet } from '../utils/wallet'; console.log('HomeView.vue: Version with chat loaded'); @@ -110,168 +132,156 @@ const emailVerificationCode = ref(''); const showEmailVerification = ref(false); const emailErrorMessage = ref(''); -// Простая функция для выхода -const logout = async () => { - await auth.logout(); - messages.value = []; +// Добавляем состояния для форм верификации +const showTelegramVerification = ref(false); +const showEmailForm = ref(false); +const telegramCode = ref(''); +const emailInput = ref(''); +const emailCode = ref(''); +const emailError = ref(''); + +// Добавляем состояния для пагинации +const PAGE_SIZE = 2; // Показываем только последнее сообщение и ответ +const allMessages = ref([]); // Все загруженные сообщения +const currentPage = ref(1); // Текущая страница +const hasMoreMessages = ref(false); // Есть ли еще сообщения +const isLoadingMore = ref(false); // Загружаются ли дополнительные сообщения + +// Вычисляемое свойство для отображаемых сообщений +const displayedMessages = computed(() => { + const startIndex = Math.max(allMessages.value.length - (PAGE_SIZE * currentPage.value), 0); + return allMessages.value.slice(startIndex); +}); + +// Функция загрузки истории чата +const loadChatHistory = async () => { + try { + if (!auth.isAuthenticated || !auth.userId) { + return; + } + + const response = await axios.get('/api/chat/history', { + headers: { Authorization: `Bearer ${auth.address}` }, + params: { limit: PAGE_SIZE, offset: 0 } + }); + + if (response.data.success) { + messages.value = response.data.messages.map(msg => ({ + id: msg.id, + content: msg.content, + role: msg.role || (msg.sender_type === 'assistant' ? 'assistant' : 'user'), + timestamp: msg.created_at, + showAuthOptions: false + })); + + hasMoreMessages.value = response.data.total > PAGE_SIZE; + + await nextTick(); + scrollToBottom(); + } + } catch (error) { + console.error('Error loading chat history:', error); + } }; -// Форматирование времени -const formatTime = (timestamp) => { - if (!timestamp) return ''; +// Функция загрузки дополнительных сообщений +const loadMoreMessages = async () => { + if (isLoadingMore.value) return; try { - const date = new Date(timestamp); + isLoadingMore.value = true; + const offset = messages.value.length; - // Проверяем, является ли дата валидной - if (isNaN(date.getTime())) { - console.warn('Invalid timestamp:', timestamp); - return ''; - } - - // Форматируем дату с указанием дня, месяца, года и времени - return date.toLocaleString([], { - year: 'numeric', - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit' + const response = await axios.get('/api/chat/history', { + headers: { Authorization: `Bearer ${auth.address}` }, + params: { limit: PAGE_SIZE, offset } }); + + if (response.data.success) { + const newMessages = response.data.messages.map(msg => ({ + id: msg.id, + content: msg.content, + role: msg.role || (msg.sender_type === 'assistant' ? 'assistant' : 'user'), + timestamp: msg.created_at, + showAuthOptions: false + })); + + messages.value = [...newMessages, ...messages.value]; + hasMoreMessages.value = response.data.total > messages.value.length; + } } catch (error) { - console.error('Error formatting time:', error, timestamp); - return ''; + console.error('Error loading more messages:', error); + } finally { + isLoadingMore.value = false; } }; -// Функция для отправки сообщения -const sendMessage = async () => { - if (!newMessage.value.trim() || isLoading.value) return; - - console.log('Отправка сообщения:', newMessage.value, 'язык:', userLanguage.value); - - // Если пользователь не аутентифицирован, используем sendGuestMessage - if (!auth.isAuthenticated) { - await sendGuestMessage(); - return; - } - - // Код для аутентифицированных пользователей - const userMessage = { - id: Date.now(), - content: newMessage.value, - role: 'user', - timestamp: new Date().toISOString() - }; - - messages.value.push(userMessage); - const messageText = newMessage.value; - newMessage.value = ''; - - // Прокрутка вниз - await nextTick(); +// Функция прокрутки к последнему сообщению +const scrollToBottom = () => { if (messagesContainer.value) { messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight; } - - isLoading.value = true; - - try { - const response = await axios.post('/api/chat/message', { - message: messageText, - language: userLanguage.value - }); - - console.log('Ответ от сервера:', response.data); - - // Добавляем ответ от ИИ - messages.value.push({ - id: Date.now() + 1, - content: response.data.message, - role: 'assistant', - timestamp: new Date().toISOString() - }); - - // Прокрутка вниз - await nextTick(); - if (messagesContainer.value) { - messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight; - } - } catch (error) { - console.error('Ошибка при отправке сообщения:', error); - messages.value.push({ - id: Date.now() + 1, - content: 'Произошла ошибка при обработке вашего сообщения. Пожалуйста, попробуйте еще раз.', - role: 'assistant', - timestamp: new Date().toISOString() - }); - } finally { - isLoading.value = false; - } }; -// Добавим наблюдатель за изменением состояния аутентификации -watch(() => auth.isAuthenticated, async (newValue, oldValue) => { - console.log('Auth state changed in HomeView:', newValue); +// Инициализация при монтировании +onMounted(async () => { + console.log('HomeView.vue: onMounted called'); + console.log('Auth state:', auth.isAuthenticated); - if (newValue && !oldValue) { - // Пользователь только что аутентифицировался + // Определяем язык + const cyrillicPattern = /[а-яА-ЯёЁ]/; + userLanguage.value = cyrillicPattern.test(newMessage.value) ? 'ru' : 'en'; + console.log('Detected language:', userLanguage.value); + + // Если пользователь уже аутентифицирован, загружаем историю + if (auth.isAuthenticated && auth.userId) { + console.log('User authenticated, loading chat history...'); await loadChatHistory(); } }); -// Загрузка истории сообщений -const loadChatHistory = async () => { - console.log('Loading chat history...'); +// Наблюдатель за изменением состояния аутентификации +watch(() => auth.isAuthenticated, async (newValue, oldValue) => { + console.log('Auth state changed in HomeView:', newValue); + if (newValue && auth.userId) { + // Пользователь только что аутентифицировался + await loadChatHistory(); + } else { + // Пользователь вышел из системы + messages.value = []; // Очищаем историю сообщений + hasMoreMessages.value = false; // Сбрасываем флаг наличия дополнительных сообщений + console.log('Chat history cleared after logout'); + } +}, { immediate: true }); + +// Функция для подключения кошелька +const handleWalletAuth = async () => { try { - console.log('User address from auth store:', auth.address); - - // Добавляем заголовок авторизации - const headers = {}; - if (auth.address) { - const authHeader = `Bearer ${auth.address}`; - console.log('Adding Authorization header:', authHeader); - headers.Authorization = authHeader; - } - - const response = await axios.get('/api/chat/history', { headers }); - console.log('Chat history response:', response.data); - - if (response.data.messages) { - // Получаем историю с сервера - const serverMessages = response.data.messages.map(msg => ({ - id: msg.id, - content: msg.content, - role: msg.role, - timestamp: msg.timestamp || msg.created_at, - isGuest: false - })); - - // Объединяем гостевые сообщения с историей с сервера - // Сначала отправляем гостевые сообщения на сервер - await saveGuestMessagesToServer(); - - // Затем загружаем обновленную историю - const updatedResponse = await axios.get('/api/chat/history', { headers }); - const updatedServerMessages = updatedResponse.data.messages.map(msg => ({ - id: msg.id, - content: msg.content, - role: msg.role, - timestamp: msg.timestamp || msg.created_at, - isGuest: false - })); - - // Обновляем сообщения - messages.value = updatedServerMessages; - - // Очищаем гостевые сообщения - guestMessages.value = []; - localStorage.removeItem('guestMessages'); - - console.log('Updated messages:', messages.value); + const result = await connectWithWallet(); + if (result.success) { + console.log('Wallet auth result:', result); + + // Обновляем состояние аутентификации + auth.setAuth({ + authenticated: true, + isAuthenticated: true, + userId: result.userId, + address: result.address, + isAdmin: result.isAdmin, + authType: 'wallet' + }); + + // Добавляем задержку для синхронизации сессии + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Загружаем историю чата + await loadChatHistory(); } + return result; } catch (error) { - console.error('Error loading chat history:', error); + console.error('Error connecting wallet:', error); + throw error; } }; @@ -363,36 +373,29 @@ async function requestEmailCode() { } } -// Подтверждение кода подтверждения по email -async function verifyEmailCode() { - emailErrorMessage.value = ''; - +// Функция проверки кода +const verifyEmailCode = async () => { try { - const response = await auth.verifyEmail(emailVerificationCode.value); - - if (response.success) { - // Успешная верификация + const response = await axios.post('/api/auth/email/verify-code', { + email: emailInput.value, + code: emailCode.value + }); + + if (response.data.success) { + auth.setEmailAuth(response.data); showEmailVerification.value = false; - emailVerificationCode.value = ''; + emailError.value = ''; - // Связываем гостевые сообщения с аутентифицированным пользователем - try { - await axios.post('/api/chat/link-guest-messages'); - console.log('Guest messages linked to authenticated user'); - } catch (linkError) { - console.error('Error linking guest messages:', linkError); - } - - // Загружаем историю сообщений + // Загружаем историю чата после успешной аутентификации await loadChatHistory(); } else { - emailErrorMessage.value = response.error || 'Неверный код подтверждения'; + emailError.value = response.data.error || 'Неверный код'; } } catch (error) { + emailError.value = error.response?.data?.error || 'Ошибка проверки кода'; console.error('Error verifying email code:', error); - emailErrorMessage.value = 'Ошибка верификации'; } -} +}; // Отмена верификации email function cancelEmailVerification() { @@ -407,117 +410,218 @@ const formatAddress = (address) => { return address.substring(0, 6) + '...' + address.substring(address.length - 4); }; -onMounted(async () => { - console.log('HomeView.vue: onMounted called'); - console.log('Auth state:', auth.isAuthenticated); +// Форматирование времени +const formatTime = (timestamp) => { + if (!timestamp) return ''; - // Определяем язык пользователя - const browserLanguage = navigator.language || navigator.userLanguage; - userLanguage.value = browserLanguage.split('-')[0]; - console.log('Detected language:', userLanguage.value); - - // Загружаем гостевые сообщения из localStorage - const savedGuestMessages = localStorage.getItem('guestMessages'); - if (savedGuestMessages) { - guestMessages.value = JSON.parse(savedGuestMessages); - } - - // Если пользователь аутентифицирован, загружаем историю чата с сервера - if (auth.isAuthenticated) { - console.log('User authenticated, loading chat history...'); - await loadChatHistory(); - } else { - // Если пользователь не аутентифицирован, отображаем гостевые сообщения - messages.value = [...guestMessages.value]; - } -}); - -// Функция для отправки сообщения от неаутентифицированного пользователя -const sendGuestMessage = async () => { - if (!newMessage.value.trim()) return; - - const userMessage = { - id: Date.now(), - content: newMessage.value, - role: 'user', - timestamp: new Date().toISOString(), - isGuest: true - }; - - // Добавляем сообщение пользователя в локальную историю - messages.value.push(userMessage); - - // Сохраняем сообщение в массиве гостевых сообщений - guestMessages.value.push(userMessage); - - // Сохраняем гостевые сообщения в localStorage - localStorage.setItem('guestMessages', JSON.stringify(guestMessages.value)); - - // Очищаем поле ввода - const messageText = newMessage.value; - newMessage.value = ''; - - // Прокрутка вниз - await nextTick(); - if (messagesContainer.value) { - messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight; - } - - // Устанавливаем состояние загрузки - isLoading.value = true; - - // Отправляем запрос на сервер try { - const response = await axios.post('/api/chat/guest-message', { + const date = new Date(timestamp); + + // Проверяем, является ли дата валидной + if (isNaN(date.getTime())) { + console.warn('Invalid timestamp:', timestamp); + return ''; + } + + // Форматируем дату с указанием дня, месяца, года и времени + return date.toLocaleString([], { + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }); + } catch (error) { + console.error('Error formatting time:', error, timestamp); + return ''; + } +}; + +// Функция для отправки сообщения +const handleMessage = async (messageText) => { + if (!messageText.trim() || isLoading.value) return; + + console.log('Handling message:', messageText); + isLoading.value = true; + + try { + if (!auth.isAuthenticated) { + await sendGuestMessage(messageText); + } else { + await sendMessage(messageText); + } + } catch (error) { + console.error('Error handling message:', error); + messages.value.push({ + id: Date.now(), + content: 'Произошла ошибка при отправке сообщения.', + role: 'assistant', + timestamp: new Date().toISOString() + }); + } finally { + newMessage.value = ''; + isLoading.value = false; + } +}; + +// Функция для отправки сообщения аутентифицированного пользователя +const sendMessage = async (messageText) => { + try { + const userMessage = { + id: Date.now(), + content: messageText, + role: 'user', + timestamp: new Date().toISOString() + }; + messages.value.push(userMessage); + + const response = await axios.post('/api/chat/message', { message: messageText, language: userLanguage.value }); - - console.log('Response from server:', response.data); - - // Добавляем ответ AI в историю - const aiMessage = { - id: Date.now() + 1, - content: response.data.message || response.data.reply, - role: 'assistant', - timestamp: new Date().toISOString(), - isGuest: true, - showAuthOptions: !hasShownAuthOptions.value - }; - - messages.value.push(aiMessage); - - // Отмечаем, что опции аутентификации уже были показаны - if (!hasShownAuthOptions.value) { - hasShownAuthOptions.value = true; - } - - // Сохраняем ответ AI в массиве гостевых сообщений - guestMessages.value.push(aiMessage); - - // Обновляем localStorage - localStorage.setItem('guestMessages', JSON.stringify(guestMessages.value)); - - // Прокрутка вниз - await nextTick(); - if (messagesContainer.value) { - messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight; + + if (response.data.success) { + messages.value.push({ + id: Date.now() + 1, + content: response.data.message, + role: 'assistant', + timestamp: new Date().toISOString() + }); } } catch (error) { - console.error('Error sending guest message:', error); - - // Добавляем сообщение об ошибке + console.error('Error sending message:', error); + } +}; + +// Функция для отправки гостевого сообщения +const sendGuestMessage = async (messageText) => { + try { + // Добавляем сообщение пользователя + const userMessage = { + id: Date.now(), + content: messageText, + role: 'user', + timestamp: new Date().toISOString(), + showAuthButtons: false + }; + messages.value.push(userMessage); + + // Очищаем поле ввода + newMessage.value = ''; + + // Сохраняем сообщение на сервере без получения ответа от Ollama + await axios.post('/api/chat/guest-message', { + message: messageText, + language: userLanguage.value + }); + + // Добавляем сообщение с кнопками аутентификации messages.value.push({ id: Date.now() + 1, - content: 'Произошла ошибка при обработке вашего сообщения. Пожалуйста, попробуйте еще раз.', + content: 'Для получения ответа, пожалуйста, авторизуйтесь одним из способов:', role: 'assistant', timestamp: new Date().toISOString(), - isGuest: true + showAuthButtons: true + }); + + } catch (error) { + console.error('Error sending guest message:', error); + messages.value.push({ + id: Date.now() + 2, + content: 'Произошла ошибка. Пожалуйста, попробуйте позже.', + role: 'assistant', + timestamp: new Date().toISOString(), + showAuthButtons: true }); } finally { isLoading.value = false; } }; + +// Добавляем методы для аутентификации +const handleTelegramAuth = () => { + window.open('https://t.me/HB3_Accelerator_Bot', '_blank'); + // Показываем форму для ввода кода через небольшую задержку + setTimeout(() => { + showTelegramVerification.value = true; + }, 1000); +}; + +const handleEmailAuth = async () => { + showEmailForm.value = true; +}; + +// Функция отправки email +const submitEmail = async () => { + try { + const response = await axios.post('/api/auth/email/request', { + email: emailInput.value + }); + + if (response.data.success) { + showEmailForm.value = false; + showEmailVerification.value = true; + } else { + emailError.value = response.data.error || 'Ошибка отправки кода'; + } + } catch (error) { + emailError.value = 'Ошибка отправки кода'; + console.error('Error sending email code:', error); + } +}; + +// Функция верификации кода Telegram +const verifyTelegramCode = async () => { + try { + const response = await axios.post('/api/auth/telegram/verify', { + code: telegramCode.value + }); + + if (response.data.success) { + console.log('Telegram verification successful:', response.data); + + // Обновляем состояние аутентификации + auth.setAuth({ + isAuthenticated: response.data.authenticated, + userId: response.data.userId, + telegramId: response.data.telegramId, + isAdmin: response.data.isAdmin, + authType: 'telegram' + }); + + showTelegramVerification.value = false; + telegramCode.value = ''; + + // Показываем сообщение об успехе + messages.value.push({ + id: Date.now(), + content: 'Telegram успешно подключен!', + role: 'assistant', + timestamp: new Date().toISOString() + }); + + // Загружаем историю чата после небольшой задержки + setTimeout(async () => { + await loadChatHistory(); + }, 100); + } else { + messages.value.push({ + id: Date.now(), + content: response.data.error || 'Ошибка верификации кода', + role: 'assistant', + timestamp: new Date().toISOString() + }); + } + } catch (error) { + console.error('Error verifying Telegram code:', error); + messages.value.push({ + id: Date.now(), + content: 'Произошла ошибка. Пожалуйста, попробуйте позже.', + role: 'assistant', + timestamp: new Date().toISOString() + }); + } +}; diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 8bf9c2f..6107c5a 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -41,9 +41,7 @@ export default defineConfig({ proxy: { '/api': { target: 'http://localhost:8000', - changeOrigin: true, - secure: false, - ws: true, + changeOrigin: true } }, },