/** * Chat Interface JavaScript * Handles WebSocket communication, UI interactions, and real-time chat functionality */ class ChatClient { constructor() { this.socket = null; this.sessionId = null; this.currentLanguage = 'python'; this.isConnected = false; this.isTyping = false; this.messageQueue = []; // DOM elements this.elements = { chatMessages: document.getElementById('chatMessages'), messageInput: document.getElementById('messageInput'), sendButton: document.getElementById('sendButton'), languageSelect: document.getElementById('languageSelect'), connectionStatus: document.getElementById('connectionStatus'), statusIndicator: document.getElementById('statusIndicator'), statusText: document.getElementById('statusText'), typingIndicator: document.getElementById('typingIndicator'), errorMessage: document.getElementById('errorMessage'), errorText: document.getElementById('errorText'), errorClose: document.getElementById('errorClose'), characterCount: document.getElementById('characterCount'), notificationToast: document.getElementById('notificationToast'), notificationText: document.getElementById('notificationText') }; this.init(); } init() { this.setupEventListeners(); this.connectWebSocket(); this.updateCharacterCount(); } setupEventListeners() { // Send button click this.elements.sendButton.addEventListener('click', () => this.sendMessage()); // Message input events this.elements.messageInput.addEventListener('input', () => { this.updateCharacterCount(); this.updateSendButton(); this.autoResize(); }); this.elements.messageInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this.sendMessage(); } }); // Language selection this.elements.languageSelect.addEventListener('change', (e) => { this.switchLanguage(e.target.value); }); // Error message close this.elements.errorClose.addEventListener('click', () => { this.hideError(); }); // Auto-hide error after 10 seconds let errorTimeout; const showError = this.showError.bind(this); this.showError = (message) => { showError(message); clearTimeout(errorTimeout); errorTimeout = setTimeout(() => this.hideError(), 10000); }; } connectWebSocket() { try { this.updateConnectionStatus('connecting', 'Connecting...'); // For demo purposes, create a temporary session ID and user ID // In a real app, these would come from authentication const tempSessionId = this.generateSessionId(); const tempUserId = this.generateUserId(); // Store for later use this.tempSessionId = tempSessionId; this.tempUserId = tempUserId; // Initialize Socket.IO connection with auth this.socket = io({ transports: ['websocket', 'polling'], timeout: 5000, reconnection: true, reconnectionAttempts: 5, reconnectionDelay: 1000, auth: { session_id: tempSessionId, user_id: tempUserId } }); // Connection events this.socket.on('connect', () => { console.log('WebSocket connected'); this.isConnected = true; this.updateConnectionStatus('connected', 'Connected'); // Session will be created automatically by the server this.processMessageQueue(); }); this.socket.on('disconnect', (reason) => { console.log('WebSocket disconnected:', reason); this.isConnected = false; this.updateConnectionStatus('disconnected', 'Disconnected'); if (reason === 'io server disconnect') { // Server initiated disconnect, try to reconnect this.socket.connect(); } }); this.socket.on('connect_error', (error) => { console.error('WebSocket connection error:', error); this.updateConnectionStatus('disconnected', 'Connection failed'); this.showError('Failed to connect to chat server. Please refresh the page.'); }); this.socket.on('reconnect', (attemptNumber) => { console.log('WebSocket reconnected after', attemptNumber, 'attempts'); this.updateConnectionStatus('connected', 'Reconnected'); this.hideError(); }); this.socket.on('reconnect_error', (error) => { console.error('WebSocket reconnection error:', error); this.showError('Reconnection failed. Please refresh the page.'); }); // Chat events this.socket.on('connection_status', (data) => { console.log('Connection status:', data); if (data.status === 'connected') { this.sessionId = data.session_id; this.currentLanguage = data.language; this.elements.languageSelect.value = data.language; } }); this.socket.on('response_start', (data) => { console.log('Response start:', data); this.hideTypingIndicator(); this.startStreamingResponse(data.session_id); }); this.socket.on('response_chunk', (data) => { console.log('Response chunk:', data); this.appendToStreamingResponse(data.content); }); this.socket.on('response_complete', (data) => { console.log('Response complete:', data); this.endStreamingResponse(); }); this.socket.on('language_switched', (data) => { console.log('Language switched:', data); this.currentLanguage = data.new_language; this.elements.languageSelect.value = data.new_language; this.addSystemMessage(data.message); }); this.socket.on('user_typing', (data) => { // For future multi-user support console.log('User typing:', data); }); this.socket.on('user_typing_stop', (data) => { // For future multi-user support console.log('User typing stop:', data); }); this.socket.on('error', (data) => { console.error('WebSocket error:', data); this.hideTypingIndicator(); this.showError(data.message || 'An error occurred while processing your message.'); }); } catch (error) { console.error('Failed to initialize WebSocket:', error); this.updateConnectionStatus('disconnected', 'Connection failed'); this.showError('Failed to initialize chat connection.'); } } createSession() { if (!this.isConnected) return; this.socket.emit('create_session', { language: this.currentLanguage, metadata: { user_agent: navigator.userAgent, timestamp: new Date().toISOString() } }); } sendMessage() { const message = this.elements.messageInput.value.trim(); if (!message || !this.isConnected) return; if (message.length > 2000) { this.showError('Message is too long. Please keep it under 2000 characters.'); return; } // Add user message to UI this.addMessage('user', message, this.currentLanguage); // Clear input this.elements.messageInput.value = ''; this.updateCharacterCount(); this.updateSendButton(); this.autoResize(); // Show typing indicator this.showTypingIndicator(); // Send message via WebSocket if (this.sessionId) { this.socket.emit('message', { content: message, language: this.currentLanguage, timestamp: new Date().toISOString() }); } else { // Queue message if session not ready this.messageQueue.push({ content: message, language: this.currentLanguage, timestamp: new Date().toISOString() }); } } switchLanguage(language) { if (language === this.currentLanguage) return; if (this.isConnected && this.sessionId) { this.socket.emit('language_switch', { language: language }); } else { // Update locally if not connected this.currentLanguage = language; this.addSystemMessage(`Language set to ${this.getLanguageDisplayName(language)}.`); } } addMessage(role, content, language, timestamp = null) { const messageDiv = document.createElement('div'); messageDiv.className = `message ${role}-message`; const contentDiv = document.createElement('div'); contentDiv.className = 'message-content'; // Process content for code highlighting const processedContent = this.processMessageContent(content, language); contentDiv.innerHTML = processedContent; messageDiv.appendChild(contentDiv); // Add timestamp if (timestamp || role === 'user') { const timestampDiv = document.createElement('div'); timestampDiv.className = 'message-timestamp'; timestampDiv.textContent = timestamp ? new Date(timestamp).toLocaleTimeString() : new Date().toLocaleTimeString(); messageDiv.appendChild(timestampDiv); } this.elements.chatMessages.appendChild(messageDiv); this.scrollToBottom(); // Apply syntax highlighting this.applySyntaxHighlighting(contentDiv); } addSystemMessage(content) { const messageDiv = document.createElement('div'); messageDiv.className = 'message assistant-message'; const contentDiv = document.createElement('div'); contentDiv.className = 'message-content'; contentDiv.style.background = 'linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%)'; contentDiv.style.color = 'white'; contentDiv.style.border = 'none'; contentDiv.innerHTML = `
${content}
`; messageDiv.appendChild(contentDiv); this.elements.chatMessages.appendChild(messageDiv); this.scrollToBottom(); } processMessageContent(content, language) { // Convert markdown-style code blocks to HTML content = content.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => { const detectedLang = lang || language || 'text'; return `${this.escapeHtml(code.trim())}`;
});
// Convert inline code
content = content.replace(/`([^`]+)`/g, '$1');
// Convert line breaks to paragraphs
const paragraphs = content.split('\n\n').filter(p => p.trim());
if (paragraphs.length > 1) {
content = paragraphs.map(p => `${p.replace(/\n/g, '
')}
${content.replace(/\n/g, '
')}