class ChatbotApp { constructor() { this.generator = null; this.messages = [ { role: "system", content: "You are a helpful assistant. Be concise and friendly." } ]; this.isGenerating = false; this.init(); } async init() { this.setupElements(); this.setupEventListeners(); await this.loadModel(); } setupElements() { this.chatMessages = document.getElementById('chatMessages'); this.messageInput = document.getElementById('messageInput'); this.sendButton = document.getElementById('sendButton'); this.status = document.getElementById('status'); this.loadingIndicator = document.getElementById('loadingIndicator'); this.progressFill = document.getElementById('progressFill'); this.progressText = document.getElementById('progressText'); this.charCount = document.getElementById('charCount'); } setupEventListeners() { // Send button click this.sendButton.addEventListener('click', () => this.sendMessage()); // Enter key to send (Shift+Enter for new line) this.messageInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this.sendMessage(); } }); // Character count this.messageInput.addEventListener('input', () => { const length = this.messageInput.value.length; this.charCount.textContent = `${length} / 1000`; // Auto-resize textarea this.messageInput.style.height = 'auto'; this.messageInput.style.height = Math.min(this.messageInput.scrollHeight, 120) + 'px'; }); // Initialize worker this.setupWorker(); } setupWorker() { // Create worker from separate file this.worker = new Worker('worker.js'); this.worker.addEventListener('message', (e) => { switch(e.data.type) { case 'progress': this.updateProgress(e.data.progress); break; case 'modelLoaded': this.onModelLoaded(e.data.generator); break; case 'error': this.onError(e.data.error); break; } }); } async loadModel() { this.status.textContent = 'Downloading model...'; this.loadingIndicator.classList.remove('hidden'); this.worker.postMessage({ type: 'loadModel' }); } updateProgress(progress) { this.progressFill.style.width = `${progress}%`; this.progressText.textContent = `${Math.round(progress)}%`; } onModelLoaded(generator) { this.generator = generator; this.loadingIndicator.classList.add('hidden'); this.status.textContent = 'Ready'; this.messageInput.disabled = false; this.sendButton.disabled = false; this.messageInput.focus(); } onError(error) { this.loadingIndicator.classList.add('hidden'); this.status.textContent = 'Error loading model'; this.addMessage('system', `Error: ${error}. Please refresh the page to try again.`); } async sendMessage() { const message = this.messageInput.value.trim(); if (!message || this.isGenerating || !this.generator) return; // Add user message this.addMessage('user', message); this.messages.push({ role: "user", content: message }); // Clear input this.messageInput.value = ''; this.messageInput.style.height = 'auto'; this.charCount.textContent = '0 / 1000'; // Disable input this.isGenerating = true; this.messageInput.disabled = true; this.sendButton.disabled = true; this.status.textContent = 'Thinking...'; // Add AI message with typing indicator const aiMessageId = this.addMessage('ai', '', true); try { // Generate response with streaming const streamer = new transformers.TextStreamer(this.generator.tokenizer, { skip_prompt: true, skip_special_tokens: true, callback_function: (text) => { this.updateMessage(aiMessageId, text); } }); const output = await this.generator(this.messages, { max_new_tokens: 512, do_sample: false, streamer: streamer, }); // Add the AI response to messages const aiResponse = output[0].generated_text.at(-1).content; this.messages.push({ role: "assistant", content: aiResponse }); } catch (error) { this.updateMessage(aiMessageId, 'Sorry, I encountered an error. Please try again.'); console.error('Generation error:', error); } finally { // Re-enable input this.isGenerating = false; this.messageInput.disabled = false; this.sendButton.disabled = false; this.status.textContent = 'Ready'; this.messageInput.focus(); } } addMessage(role, content, isTyping = false) { const messageId = 'msg-' + Date.now(); const messageDiv = document.createElement('div'); messageDiv.className = `message ${role}`; messageDiv.id = messageId; const avatar = document.createElement('div'); avatar.className = role === 'user' ? 'user-avatar' : 'ai-avatar'; avatar.innerHTML = role === 'user' ? ' ' : ' '; const messageContent = document.createElement('div'); messageContent.className = 'message-content'; const bubble = document.createElement('div'); bubble.className = 'message-bubble'; if (isTyping) { bubble.innerHTML = '
'; } else { bubble.textContent = content; } const time = document.createElement('div'); time.className = 'message-time'; time.textContent = new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); messageContent.appendChild(bubble); messageContent.appendChild(time); messageDiv.appendChild(avatar); messageDiv.appendChild(messageContent); this.chatMessages.appendChild(messageDiv); this.chatMessages.scrollTop = this.chatMessages.scrollHeight; return messageId; } updateMessage(messageId, content) { const messageElement = document.getElementById(messageId); if (messageElement) { const bubble = messageElement.querySelector('.message-bubble'); bubble.textContent = content; this.chatMessages.scrollTop = this.chatMessages.scrollHeight; } } } // Initialize the app when DOM is loaded document.addEventListener('DOMContentLoaded', () => { new ChatbotApp(); });