WebashalarForML's picture
Upload 178 files
330b6e4 verified
/**
* 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 = `<p>${content}</p>`;
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 `<pre><code class="language-${detectedLang}">${this.escapeHtml(code.trim())}</code></pre>`;
});
// Convert inline code
content = content.replace(/`([^`]+)`/g, '<code>$1</code>');
// Convert line breaks to paragraphs
const paragraphs = content.split('\n\n').filter(p => p.trim());
if (paragraphs.length > 1) {
content = paragraphs.map(p => `<p>${p.replace(/\n/g, '<br>')}</p>`).join('');
} else {
content = `<p>${content.replace(/\n/g, '<br>')}</p>`;
}
return content;
}
applySyntaxHighlighting(element) {
// Apply Prism.js syntax highlighting
if (window.Prism) {
Prism.highlightAllUnder(element);
}
}
startStreamingResponse(messageId) {
const messageDiv = document.createElement('div');
messageDiv.className = 'message assistant-message';
messageDiv.id = `streaming-${messageId}`;
const contentDiv = document.createElement('div');
contentDiv.className = 'message-content streaming-response';
contentDiv.innerHTML = '<p></p>';
messageDiv.appendChild(contentDiv);
this.elements.chatMessages.appendChild(messageDiv);
this.scrollToBottom();
this.streamingElement = contentDiv.querySelector('p');
}
appendToStreamingResponse(chunk) {
if (this.streamingElement) {
this.streamingElement.textContent += chunk;
this.scrollToBottom();
}
}
endStreamingResponse() {
if (this.streamingElement) {
const content = this.streamingElement.textContent;
const processedContent = this.processMessageContent(content, this.currentLanguage);
this.streamingElement.parentElement.innerHTML = processedContent;
this.applySyntaxHighlighting(this.streamingElement.parentElement);
this.streamingElement.parentElement.classList.remove('streaming-response');
this.streamingElement = null;
}
}
showTypingIndicator() {
if (!this.isTyping) {
this.isTyping = true;
this.elements.typingIndicator.style.display = 'block';
this.scrollToBottom();
}
}
hideTypingIndicator() {
if (this.isTyping) {
this.isTyping = false;
this.elements.typingIndicator.style.display = 'none';
}
}
updateConnectionStatus(status, text) {
this.elements.statusIndicator.className = `status-indicator ${status}`;
this.elements.statusText.textContent = text;
}
showError(message) {
this.elements.errorText.textContent = message;
this.elements.errorMessage.style.display = 'flex';
}
hideError() {
this.elements.errorMessage.style.display = 'none';
}
updateCharacterCount() {
const length = this.elements.messageInput.value.length;
const maxLength = 2000;
this.elements.characterCount.textContent = `${length}/${maxLength}`;
if (length > maxLength * 0.9) {
this.elements.characterCount.className = 'character-count error';
} else if (length > maxLength * 0.8) {
this.elements.characterCount.className = 'character-count warning';
} else {
this.elements.characterCount.className = 'character-count';
}
}
updateSendButton() {
const hasText = this.elements.messageInput.value.trim().length > 0;
this.elements.sendButton.disabled = !hasText || !this.isConnected;
}
autoResize() {
const textarea = this.elements.messageInput;
textarea.style.height = 'auto';
textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px';
}
scrollToBottom() {
requestAnimationFrame(() => {
this.elements.chatMessages.scrollTop = this.elements.chatMessages.scrollHeight;
});
}
processMessageQueue() {
if (this.messageQueue.length > 0 && this.sessionId) {
this.messageQueue.forEach(message => {
this.socket.emit('message', message);
});
this.messageQueue = [];
}
}
generateSessionId() {
// Generate a temporary session ID for demo purposes
// In production, this would be handled by proper authentication
return 'session_' + Math.random().toString(36).substr(2, 9) + '_' + Date.now();
}
generateUserId() {
// Generate a temporary user ID for demo purposes
// In production, this would come from authentication
let userId = localStorage.getItem('temp_user_id');
if (!userId) {
userId = 'user_' + Math.random().toString(36).substr(2, 9) + '_' + Date.now();
localStorage.setItem('temp_user_id', userId);
}
return userId;
}
getLanguageDisplayName(language) {
const languageNames = {
python: 'Python',
javascript: 'JavaScript',
java: 'Java',
cpp: 'C++',
csharp: 'C#',
go: 'Go',
rust: 'Rust',
typescript: 'TypeScript'
};
return languageNames[language] || language;
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
showNotification(message, type = 'success') {
this.elements.notificationText.textContent = message;
this.elements.notificationToast.className = `notification-toast ${type}`;
this.elements.notificationToast.style.display = 'block';
// Auto-hide after 3 seconds
setTimeout(() => {
this.elements.notificationToast.style.display = 'none';
}, 3000);
}
}
// Utility functions for enhanced UX
class ChatUtils {
static formatTimestamp(timestamp) {
const date = new Date(timestamp);
const now = new Date();
const diffMs = now - date;
const diffMins = Math.floor(diffMs / 60000);
if (diffMins < 1) return 'Just now';
if (diffMins < 60) return `${diffMins}m ago`;
if (diffMins < 1440) return `${Math.floor(diffMins / 60)}h ago`;
return date.toLocaleDateString();
}
static detectCodeLanguage(code) {
// Simple language detection based on common patterns
if (code.includes('def ') || code.includes('import ') || code.includes('print(')) return 'python';
if (code.includes('function ') || code.includes('const ') || code.includes('console.log')) return 'javascript';
if (code.includes('public class ') || code.includes('System.out.println')) return 'java';
if (code.includes('#include') || code.includes('std::')) return 'cpp';
if (code.includes('using System') || code.includes('Console.WriteLine')) return 'csharp';
if (code.includes('func ') || code.includes('package main')) return 'go';
if (code.includes('fn ') || code.includes('println!')) return 'rust';
if (code.includes('interface ') || code.includes(': string')) return 'typescript';
return 'text';
}
static copyToClipboard(text) {
if (navigator.clipboard) {
navigator.clipboard.writeText(text).then(() => {
console.log('Text copied to clipboard');
if (window.chatClient) {
window.chatClient.showNotification('Code copied to clipboard!');
}
}).catch(err => {
console.error('Failed to copy text: ', err);
if (window.chatClient) {
window.chatClient.showNotification('Failed to copy code', 'error');
}
});
} else {
// Fallback for older browsers
const textArea = document.createElement('textarea');
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
const success = document.execCommand('copy');
document.body.removeChild(textArea);
if (window.chatClient) {
if (success) {
window.chatClient.showNotification('Code copied to clipboard!');
} else {
window.chatClient.showNotification('Failed to copy code', 'error');
}
}
}
}
}
// Initialize chat client when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
window.chatClient = new ChatClient();
// Add copy functionality to code blocks
document.addEventListener('click', (e) => {
if (e.target.tagName === 'CODE' && e.target.parentElement.tagName === 'PRE') {
ChatUtils.copyToClipboard(e.target.textContent);
// Show temporary feedback
const originalText = e.target.textContent;
e.target.textContent = 'Copied!';
setTimeout(() => {
e.target.textContent = originalText;
}, 1000);
}
});
// Handle page visibility changes
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible' && window.chatClient && !window.chatClient.isConnected) {
// Try to reconnect when page becomes visible
setTimeout(() => {
if (!window.chatClient.isConnected) {
window.chatClient.connectWebSocket();
}
}, 1000);
}
});
});
// Export for potential external use
window.ChatClient = ChatClient;
window.ChatUtils = ChatUtils;