import { pipeline, TextStreamer } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.0'; // Global variables let generator = null; let conversationHistory = []; let isGenerating = false; // DOM elements const messagesContainer = document.getElementById('messagesContainer'); const userInput = document.getElementById('userInput'); const sendButton = document.getElementById('sendButton'); const clearButton = document.getElementById('clearButton'); const statusText = document.getElementById('statusText'); const progressContainer = document.getElementById('progressContainer'); const progressBar = document.getElementById('progressBar'); // Initialize the application async function initialize() { try { updateStatus('Loading model... This may take 1-2 minutes on first load.', true); // Create a text generation pipeline with progress tracking generator = await pipeline( "text-generation", "onnx-community/Llama-3.2-1B-Instruct-q4f16", { dtype: "q4f16", device: "webgpu", progress_callback: (progress) => { if (progress.status === 'downloading') { const percent = Math.round((progress.loaded / progress.total) * 100); updateProgress(percent); updateStatus(`Downloading model: ${progress.file} (${percent}%)`, true); } else if (progress.status === 'loading') { updateStatus(`Loading model into memory...`, true); } } } ); updateStatus('✓ Model loaded successfully! Ready to chat.', false); enableInput(); // Initialize conversation with system message conversationHistory = [ { role: "system", content: "You are a helpful, friendly, and knowledgeable AI assistant. Provide clear, concise, and accurate responses." } ]; } catch (error) { console.error('Initialization error:', error); updateStatus(`❌ Error loading model: ${error.message}`, false); showError('Failed to load the model. Please refresh the page and try again.'); } } // Update status bar function updateStatus(message, showProgress) { statusText.textContent = message; progressContainer.style.display = showProgress ? 'block' : 'none'; } // Update progress bar function updateProgress(percent) { progressBar.style.width = `${percent}%`; } // Enable input controls function enableInput() { userInput.disabled = false; sendButton.disabled = false; userInput.focus(); } // Disable input controls function disableInput() { userInput.disabled = true; sendButton.disabled = true; } // Add message to chat function addMessage(role, content, isStreaming = false) { const messageDiv = document.createElement('div'); messageDiv.className = `message ${role}`; const contentDiv = document.createElement('div'); contentDiv.className = 'message-content'; const roleLabel = document.createElement('strong'); roleLabel.textContent = role === 'user' ? 'You:' : 'Assistant:'; const messageText = document.createElement('p'); messageText.textContent = content; contentDiv.appendChild(roleLabel); contentDiv.appendChild(messageText); messageDiv.appendChild(contentDiv); messagesContainer.appendChild(messageDiv); messagesContainer.scrollTop = messagesContainer.scrollHeight; return messageText; } // Add typing indicator function addTypingIndicator() { const messageDiv = document.createElement('div'); messageDiv.className = 'message assistant'; messageDiv.id = 'typingIndicator'; const contentDiv = document.createElement('div'); contentDiv.className = 'message-content'; const indicator = document.createElement('div'); indicator.className = 'typing-indicator'; indicator.innerHTML = ''; contentDiv.appendChild(indicator); messageDiv.appendChild(contentDiv); messagesContainer.appendChild(messageDiv); messagesContainer.scrollTop = messagesContainer.scrollHeight; } // Remove typing indicator function removeTypingIndicator() { const indicator = document.getElementById('typingIndicator'); if (indicator) { indicator.remove(); } } // Generate response async function generateResponse(userMessage) { if (!generator || isGenerating) return; isGenerating = true; disableInput(); // Add user message to history and UI conversationHistory.push({ role: "user", content: userMessage }); addMessage('user', userMessage); // Show typing indicator addTypingIndicator(); try { let assistantResponse = ''; const messageElement = document.createElement('p'); // Create streamer with callback const streamer = new TextStreamer(generator.tokenizer, { skip_prompt: true, skip_special_tokens: true, callback_function: (text) => { assistantResponse += text; removeTypingIndicator(); // Update or create message element if (!messageElement.parentElement) { const messageDiv = document.createElement('div'); messageDiv.className = 'message assistant'; const contentDiv = document.createElement('div'); contentDiv.className = 'message-content'; const roleLabel = document.createElement('strong'); roleLabel.textContent = 'Assistant:'; contentDiv.appendChild(roleLabel); contentDiv.appendChild(messageElement); messageDiv.appendChild(contentDiv); messagesContainer.appendChild(messageDiv); } messageElement.textContent = assistantResponse; messagesContainer.scrollTop = messagesContainer.scrollHeight; } }); // Generate response const output = await generator(conversationHistory, { max_new_tokens: 512, do_sample: false, streamer: streamer, }); // Add assistant response to history const finalResponse = output[0].generated_text.at(-1).content; conversationHistory.push({ role: "assistant", content: finalResponse }); updateStatus('✓ Model loaded successfully! Ready to chat.', false); } catch (error) { console.error('Generation error:', error); removeTypingIndicator(); addMessage('assistant', `Sorry, I encountered an error: ${error.message}`); updateStatus('❌ Error generating response', false); } finally { isGenerating = false; enableInput(); } } // Handle send button click async function handleSend() { const message = userInput.value.trim(); if (!message || isGenerating) return; userInput.value = ''; userInput.style.height = 'auto'; await generateResponse(message); } // Handle clear button click function handleClear() { if (confirm('Are you sure you want to clear the conversation?')) { // Keep only system message conversationHistory = conversationHistory.slice(0, 1); // Clear UI messagesContainer.innerHTML = ''; addMessage('assistant', "Hello! I'm your AI assistant powered by Gemma. How can I help you today?"); userInput.value = ''; userInput.focus(); } } // Show error message function showError(message) { const errorDiv = document.createElement('div'); errorDiv.className = 'message assistant'; errorDiv.innerHTML = `
`; messagesContainer.appendChild(errorDiv); } // Event listeners sendButton.addEventListener('click', handleSend); clearButton.addEventListener('click', handleClear); userInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); } }); // Auto-resize textarea userInput.addEventListener('input', function() { this.style.height = 'auto'; this.style.height = Math.min(this.scrollHeight, 150) + 'px'; }); // Initialize on load initialize();