Spaces:
Sleeping
Sleeping
| import express from 'express'; | |
| import { v4 as uuidv4 } from 'uuid'; | |
| import axios from 'axios'; | |
| import { database } from '../db/database'; | |
| import { AuthenticatedRequest, authenticateToken } from '../middleware/auth'; | |
| const router = express.Router(); | |
| // Create a new chat session (authenticated - for tenant dashboard) | |
| router.post('/sessions', authenticateToken, async (req: AuthenticatedRequest, res) => { | |
| try { | |
| const { tenantId } = req.user!; | |
| const { domain, userAgent } = req.body; | |
| const userIp = req.ip || req.connection.remoteAddress; | |
| // Verify tenant exists | |
| const tenant = await database.get('SELECT id FROM tenants WHERE id = ?', [tenantId]); | |
| if (!tenant) { | |
| return res.status(404).json({ error: 'Tenant not found' }); | |
| } | |
| // Generate session token | |
| const sessionToken = uuidv4(); | |
| // Create chat session | |
| const result = await database.run( | |
| 'INSERT INTO chat_sessions (tenant_id, domain, user_ip, user_agent, session_token) VALUES (?, ?, ?, ?, ?)', | |
| [tenantId, domain, userIp, userAgent, sessionToken] | |
| ); | |
| // Log analytics event | |
| await database.run( | |
| 'INSERT INTO analytics_events (tenant_id, event_type, event_data) VALUES (?, ?, ?)', | |
| [tenantId, 'chat_session_started', JSON.stringify({ sessionId: result.lastID, domain })] | |
| ); | |
| res.status(201).json({ | |
| sessionId: result.lastID, | |
| sessionToken, | |
| tenantId, | |
| message: 'Chat session created successfully' | |
| }); | |
| } catch (error) { | |
| console.error('Create session error:', error); | |
| res.status(500).json({ error: 'Internal server error' }); | |
| } | |
| }); | |
| // Create a new chat session (public - for widget) | |
| router.post('/public/sessions', async (req, res) => { | |
| try { | |
| const { tenantId, domain, userAgent } = req.body; | |
| const userIp = req.ip || req.connection.remoteAddress; | |
| if (!tenantId) { | |
| return res.status(400).json({ error: 'Tenant ID is required' }); | |
| } | |
| // Verify tenant exists | |
| const tenant = await database.get('SELECT id FROM tenants WHERE id = ?', [tenantId]); | |
| if (!tenant) { | |
| return res.status(404).json({ error: 'Tenant not found' }); | |
| } | |
| // Generate session token | |
| const sessionToken = uuidv4(); | |
| // Create chat session | |
| const result = await database.run( | |
| 'INSERT INTO chat_sessions (tenant_id, domain, user_ip, user_agent, session_token) VALUES (?, ?, ?, ?, ?)', | |
| [tenantId, domain, userIp, userAgent, sessionToken] | |
| ); | |
| // Log analytics event | |
| await database.run( | |
| 'INSERT INTO analytics_events (tenant_id, event_type, event_data) VALUES (?, ?, ?)', | |
| [tenantId, 'chat_session_started', JSON.stringify({ sessionId: result.lastID, domain })] | |
| ); | |
| res.status(201).json({ | |
| sessionId: result.lastID, | |
| sessionToken, | |
| message: 'Chat session created successfully' | |
| }); | |
| } catch (error) { | |
| console.error('Create session error:', error); | |
| res.status(500).json({ error: 'Internal server error' }); | |
| } | |
| }); | |
| // Send a message and get AI response | |
| router.post('/messages', async (req, res) => { | |
| try { | |
| const { sessionToken, message, tenantId } = req.body; | |
| if (!sessionToken || !message || !tenantId) { | |
| return res.status(400).json({ error: 'Session token, message, and tenant ID are required' }); | |
| } | |
| // Get chat session | |
| const session = await database.get( | |
| 'SELECT * FROM chat_sessions WHERE session_token = ? AND tenant_id = ?', | |
| [sessionToken, tenantId] | |
| ); | |
| if (!session) { | |
| return res.status(404).json({ error: 'Chat session not found' }); | |
| } | |
| // Save user message | |
| await database.run( | |
| 'INSERT INTO chat_messages (session_id, sender, message) VALUES (?, ?, ?)', | |
| [session.id, 'user', message] | |
| ); | |
| // Get chat history for context | |
| const chatHistory = await database.query( | |
| 'SELECT sender, message, timestamp FROM chat_messages WHERE session_id = ? ORDER BY timestamp ASC', | |
| [session.id] | |
| ); | |
| // Get tenant's knowledge base | |
| const knowledgeBase = await database.query( | |
| 'SELECT name, type, source FROM knowledge_base WHERE tenant_id = ? AND status = "active"', | |
| [tenantId] | |
| ); | |
| try { | |
| // Call MCP server for AI response | |
| const mcpResponse = await axios.post(`${process.env.MCP_SERVER_URL}/chat`, { | |
| message, | |
| history: chatHistory, | |
| knowledge_base: knowledgeBase, | |
| tenant_id: tenantId | |
| }, { | |
| headers: { | |
| 'Authorization': `Bearer ${process.env.MCP_AUTH_TOKEN}`, | |
| 'Content-Type': 'application/json' | |
| }, | |
| timeout: 30000 // 30 second timeout | |
| }); | |
| const aiResponse = mcpResponse.data.response || 'I apologize, but I encountered an issue processing your request.'; | |
| // Save AI response | |
| await database.run( | |
| 'INSERT INTO chat_messages (session_id, sender, message, metadata) VALUES (?, ?, ?, ?)', | |
| [session.id, 'ai', aiResponse, JSON.stringify({ | |
| model: mcpResponse.data.model || 'gemini-1.5-flash', | |
| confidence: mcpResponse.data.confidence || 0.8 | |
| })] | |
| ); | |
| // Log analytics event | |
| await database.run( | |
| 'INSERT INTO analytics_events (tenant_id, event_type, event_data) VALUES (?, ?, ?)', | |
| [tenantId, 'message_sent', JSON.stringify({ | |
| sessionId: session.id, | |
| messageLength: message.length, | |
| responseLength: aiResponse.length | |
| })] | |
| ); | |
| res.json({ | |
| response: aiResponse, | |
| messageId: session.id, | |
| timestamp: new Date().toISOString() | |
| }); | |
| } catch (mcpError) { | |
| console.error('MCP Server error:', mcpError); | |
| // Fallback response if MCP server is down | |
| const fallbackResponse = "I'm sorry, but I'm experiencing technical difficulties at the moment. Please try again in a few minutes or contact support if the issue persists."; | |
| await database.run( | |
| 'INSERT INTO chat_messages (session_id, sender, message, metadata) VALUES (?, ?, ?, ?)', | |
| [session.id, 'ai', fallbackResponse, JSON.stringify({ error: 'mcp_server_unavailable' })] | |
| ); | |
| res.json({ | |
| response: fallbackResponse, | |
| messageId: session.id, | |
| timestamp: new Date().toISOString(), | |
| error: 'Service temporarily unavailable' | |
| }); | |
| } | |
| } catch (error) { | |
| console.error('Send message error:', error); | |
| res.status(500).json({ error: 'Internal server error' }); | |
| } | |
| }); | |
| // Get chat history | |
| router.get('/sessions/:sessionToken/history', async (req, res) => { | |
| try { | |
| const { sessionToken } = req.params; | |
| const { tenantId } = req.query; | |
| if (!tenantId) { | |
| return res.status(400).json({ error: 'Tenant ID is required' }); | |
| } | |
| // Get session | |
| const session = await database.get( | |
| 'SELECT * FROM chat_sessions WHERE session_token = ? AND tenant_id = ?', | |
| [sessionToken, tenantId] | |
| ); | |
| if (!session) { | |
| return res.status(404).json({ error: 'Chat session not found' }); | |
| } | |
| // Get messages | |
| const messages = await database.query( | |
| 'SELECT sender, message, metadata, timestamp FROM chat_messages WHERE session_id = ? ORDER BY timestamp ASC', | |
| [session.id] | |
| ); | |
| res.json({ | |
| sessionId: session.id, | |
| messages, | |
| session: { | |
| started_at: session.started_at, | |
| resolved: session.resolved, | |
| rating: session.rating | |
| } | |
| }); | |
| } catch (error) { | |
| console.error('Get history error:', error); | |
| res.status(500).json({ error: 'Internal server error' }); | |
| } | |
| }); | |
| // Rate conversation | |
| router.post('/sessions/:sessionToken/rate', async (req, res) => { | |
| try { | |
| const { sessionToken } = req.params; | |
| const { rating, feedback, tenantId } = req.body; | |
| if (!tenantId || rating === undefined) { | |
| return res.status(400).json({ error: 'Tenant ID and rating are required' }); | |
| } | |
| if (rating < 1 || rating > 5) { | |
| return res.status(400).json({ error: 'Rating must be between 1 and 5' }); | |
| } | |
| // Update session | |
| await database.run( | |
| 'UPDATE chat_sessions SET rating = ?, feedback = ?, resolved = TRUE WHERE session_token = ? AND tenant_id = ?', | |
| [rating, feedback, sessionToken, tenantId] | |
| ); | |
| // Log analytics event | |
| await database.run( | |
| 'INSERT INTO analytics_events (tenant_id, event_type, event_data) VALUES (?, ?, ?)', | |
| [tenantId, 'conversation_rated', JSON.stringify({ | |
| sessionToken, | |
| rating, | |
| hasFeedback: !!feedback | |
| })] | |
| ); | |
| res.json({ message: 'Rating submitted successfully' }); | |
| } catch (error) { | |
| console.error('Rate conversation error:', error); | |
| res.status(500).json({ error: 'Internal server error' }); | |
| } | |
| }); | |
| // End chat session | |
| router.post('/sessions/:sessionToken/end', async (req, res) => { | |
| try { | |
| const { sessionToken } = req.params; | |
| const { tenantId } = req.body; | |
| if (!tenantId) { | |
| return res.status(400).json({ error: 'Tenant ID is required' }); | |
| } | |
| // Update session | |
| await database.run( | |
| 'UPDATE chat_sessions SET ended_at = CURRENT_TIMESTAMP WHERE session_token = ? AND tenant_id = ?', | |
| [sessionToken, tenantId] | |
| ); | |
| // Log analytics event | |
| await database.run( | |
| 'INSERT INTO analytics_events (tenant_id, event_type, event_data) VALUES (?, ?, ?)', | |
| [tenantId, 'chat_session_ended', JSON.stringify({ sessionToken })] | |
| ); | |
| res.json({ message: 'Chat session ended successfully' }); | |
| } catch (error) { | |
| console.error('End session error:', error); | |
| res.status(500).json({ error: 'Internal server error' }); | |
| } | |
| }); | |
| export default router; |