// index.js content here class AIChatApp { constructor() { this.messagesContainer = document.getElementById('messages-container'); this.messageInput = document.getElementById('message-input'); this.sendButton = document.getElementById('send-button'); this.loadingOverlay = document.getElementById('loading-overlay'); this.statusBar = document.getElementById('status-bar'); this.statusText = document.getElementById('status-text'); this.modelStatus = document.getElementById('model-status'); this.deviceSelect = document.getElementById('device-select'); this.deviceToggle = document.getElementById('device-toggle'); this.generator = null; this.currentDevice = 'cpu'; this.isGenerating = false; this.messages = []; this.initializeEventListeners(); this.initializeAutoResize(); this.loadModel(); } initializeEventListeners() { // Send message this.sendButton.addEventListener('click', () => this.sendMessage()); this.messageInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this.sendMessage(); } }); // Device selection this.deviceSelect.addEventListener('change', (e) => { this.currentDevice = e.target.value; this.updateDeviceUI(); if (this.generator) { this.loadModel(); } }); this.deviceToggle.addEventListener('click', () => { this.currentDevice = this.currentDevice === 'cpu' ? 'webgpu' : 'cpu'; this.deviceSelect.value = this.currentDevice; this.updateDeviceUI(); if (this.generator) { this.loadModel(); } }); // Clear messages document.addEventListener('keydown', (e) => { if (e.ctrlKey && e.key === 'l') { e.preventDefault(); this.clearChat(); } }); } initializeAutoResize() { this.messageInput.addEventListener('input', () => { this.messageInput.style.height = 'auto'; this.messageInput.style.height = Math.min(this.messageInput.scrollHeight, 120) + 'px'; }); } async loadModel() { try { this.setStatus('Loading AI model...', 'Loading...'); this.showLoading(true); const device = this.currentDevice === 'webgpu' ? { device: 'webgpu' } : {}; // Check WebGPU support if selected if (this.currentDevice === 'webgpu') { if (!navigator.gpu) { throw new Error('WebGPU is not supported in this browser. Falling back to CPU.'); } } this.generator = await pipeline( "text-generation", "onnx-community/MobileLLM-R1-360M-ONNX", { dtype: "fp32", ...device } ); this.setStatus('Model loaded successfully!', `Model: MobileLLM-R1-360M (${this.currentDevice.toUpperCase()})`); this.modelStatus.textContent = `Model: MobileLLM-R1-360M (${this.currentDevice.toUpperCase()})`; this.updateDeviceUI(); this.showLoading(false); } catch (error) { console.error('Failed to load model:', error); this.setStatus('Failed to load model. Retrying with CPU...', 'Error'); if (this.currentDevice === 'webgpu') { // Fallback to CPU this.currentDevice = 'cpu'; this.deviceSelect.value = 'cpu'; this.updateDeviceUI(); this.loadModel(); } else { this.showLoading(false); this.setStatus('Model loading failed. Please refresh the page.', 'Error'); this.messagesContainer.innerHTML += `
`; } } } async sendMessage() { if (this.isGenerating || !this.generator) { return; } const input = this.messageInput.value.trim(); if (!input) return; // Add user message const userMessage = { role: "user", content: input }; this.messages.push(userMessage); this.addMessage('user', input, true); this.messageInput.value = ''; this.messageInput.style.height = 'auto'; // Generate response await this.generateResponse(userMessage); } async generateResponse(userMessage) { this.isGenerating = true; this.showLoading(true); this.setStatus('AI is generating response...', 'Generating...'); try { // Prepare messages for the model (just the latest user message for simplicity) const messages = [userMessage]; const streamer = new TextStreamer(this.generator.tokenizer, { skip_prompt: true, skip_special_tokens: true, }); const output = await this.generator(messages, { max_new_tokens: 200, do_sample: false, temperature: 0.7, streamer: streamer }); // Get the generated response const responseContent = output[0].generated_text.at(-1).content; // Add assistant response this.addMessage('assistant', responseContent, false); this.messages.push({ role: "assistant", content: responseContent }); this.setStatus('Response generated successfully!', 'Ready'); this.isGenerating = false; this.showLoading(false); } catch (error) { console.error('Generation error:', error); this.addMessage('assistant', `I'm sorry, I encountered an error while generating a response: ${error.message}. Please try again.`, false); this.setStatus('Generation failed. Please try again.', 'Error'); this.isGenerating = false; this.showLoading(false); } } addMessage(type, content, isUser = false) { const messageId = `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); const icon = type === 'user' ? 'fa-user' : 'fa-robot'; const author = type === 'user' ? 'You' : 'AI Assistant'; const messageClass = type === 'user' ? 'user-message' : 'assistant-message'; const messageHTML = ` `; this.messagesContainer.insertAdjacentHTML('beforeend', messageHTML); this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight; // Add math rendering if needed this.renderMath(); } formatMessageContent(content, type) { // Basic markdown-like formatting let formatted = content .replace(/\*\*(.*?)\*\*/g, '$1') .replace(/\*(.*?)\*/g, '$1') .replace(/`(.*?)`/g, '$1')
.replace(/\n/g, '