// Main application logic for Apple Style Chatbot class AppleChatbot { constructor() { this.generator = null; this.isGenerating = false; this.messages = []; this.settings = { maxTokens: 512, temperature: 0.7, streaming: true }; this.init(); } async init() { // Initialize UI elements this.loadingScreen = document.getElementById('loadingScreen'); this.chatContainer = document.getElementById('chatContainer'); this.inputArea = document.getElementById('inputArea'); this.messagesList = document.getElementById('messagesList'); this.messageInput = document.getElementById('messageInput'); this.sendBtn = document.getElementById('sendBtn'); this.clearBtn = document.getElementById('clearBtn'); this.settingsBtn = document.getElementById('settingsBtn'); this.progressFill = document.getElementById('progressFill'); this.loadingStatus = document.getElementById('loadingStatus'); this.charCount = document.getElementById('charCount'); // Setup event listeners this.setupEventListeners(); // Load model using Web Worker await this.loadModel(); } setupEventListeners() { // Send button this.sendBtn.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', (e) => { const length = e.target.value.length; this.charCount.textContent = `${length} / 1000`; this.sendBtn.disabled = length === 0 || this.isGenerating; // Auto-resize textarea e.target.style.height = 'auto'; e.target.style.height = Math.min(e.target.scrollHeight, 120) + 'px'; }); // Clear button this.clearBtn.addEventListener('click', () => this.clearChat()); // Settings this.settingsBtn.addEventListener('click', () => this.openSettings()); document.getElementById('closeSettings').addEventListener('click', () => this.closeSettings()); document.getElementById('saveSettings').addEventListener('click', () => this.saveSettings()); // Settings inputs document.getElementById('maxTokens').addEventListener('input', (e) => { document.getElementById('maxTokensValue').textContent = e.target.value; }); document.getElementById('temperature').addEventListener('input', (e) => { document.getElementById('temperatureValue').textContent = e.target.value; }); // Close modal on backdrop click document.getElementById('settingsModal').addEventListener('click', (e) => { if (e.target.id === 'settingsModal') { this.closeSettings(); } }); } async loadModel() { try { // Create Web Worker for model loading const worker = new Worker('worker.js'); worker.onmessage = async (e) => { const { type, data } = e.data; switch (type) { case 'progress': this.updateProgress(data.progress, data.status); break; case 'model_loaded': // Model is loaded, initialize the generator await this.initializeGenerator(); break; } }; // Start loading the model worker.postMessage({ type: 'load_model' }); } catch (error) { console.error('Error loading model:', error); this.showError('Failed to load AI model. Please refresh the page and try again.'); } } updateProgress(progress, status) { this.progressFill.style.width = `${progress}%`; this.loadingStatus.textContent = status; } async initializeGenerator() { try { // Initialize the text generation pipeline this.generator = await pipeline( 'text-generation', 'onnx-community/gemma-3-270m-it-ONNX', { dtype: 'fp32' } ); // Hide loading screen and show chat this.loadingScreen.style.display = 'none'; this.chatContainer.style.display = 'block'; this.inputArea.style.display = 'block'; // Focus on input this.messageInput.focus(); } catch (error) { console.error('Error initializing generator:', error); this.showError('Failed to initialize AI model. Please refresh the page and try again.'); } } async sendMessage() { const text = this.messageInput.value.trim(); if (!text || this.isGenerating || !this.generator) return; // Add user message this.addMessage('user', text); this.messages.push({ role: 'user', content: text }); // Clear input this.messageInput.value = ''; this.charCount.textContent = '0 / 1000'; this.sendBtn.disabled = true; this.messageInput.style.height = 'auto'; // Show typing indicator const typingMessage = this.showTypingIndicator(); try { this.isGenerating = true; // Generate response const response = await this.generateResponse(); // Remove typing indicator typingMessage.remove(); // Add assistant response this.addMessage('assistant', response); this.messages.push({ role: 'assistant', content: response }); } catch (error) { console.error('Error generating response:', error); typingMessage.remove(); this.addMessage('assistant', 'Sorry, I encountered an error while generating a response. Please try again.'); } finally { this.isGenerating = false; this.sendBtn.disabled = false; this.messageInput.focus(); } } async generateResponse() { if (!this.generator) throw new Error('Generator not initialized'); const generationParams = { max_new_tokens: this.settings.maxTokens, do_sample: this.settings.temperature > 0, temperature: this.settings.temperature, }; // Add streamer if streaming is enabled if (this.settings.streaming) { generationParams.streamer = new TextStreamer(this.generator.tokenizer, { skip_prompt: true, skip_special_tokens: true, }); } // Generate response const output = await this.generator(this.messages, generationParams); // Extract the assistant's response const lastMessage = output[0].generated_text.at(-1); return lastMessage ? lastMessage.content : 'I apologize, but I couldn\'t generate a response.'; } addMessage(role, content) { const messageDiv = document.createElement('div'); messageDiv.className = `message ${role}`; const avatar = document.createElement('div'); avatar.className = 'message-avatar'; avatar.innerHTML = role === 'user' ? '' : ''; const messageContent = document.createElement('div'); messageContent.className = 'message-content'; const messageText = document.createElement('div'); messageText.className = 'message-text'; messageText.textContent = content; const messageTime = document.createElement('div'); messageTime.className = 'message-time'; messageTime.textContent = this.getCurrentTime(); messageContent.appendChild(messageText); messageContent.appendChild(messageTime); messageDiv.appendChild(avatar); messageDiv.appendChild(messageContent); this.messagesList.appendChild(messageDiv); // Scroll to bottom this.chatContainer.scrollTop = this.chatContainer.scrollHeight; } showTypingIndicator() { const messageDiv = document.createElement('div'); messageDiv.className = 'message assistant'; const avatar = document.createElement('div'); avatar.className = 'message-avatar'; avatar.innerHTML = ''; const messageContent = document.createElement('div'); messageContent.className = 'message-content'; const typingIndicator = document.createElement('div'); typingIndicator.className = 'message-text typing-indicator'; typingIndicator.innerHTML = `
`; const messageTime = document.createElement('div'); messageTime.className = 'message-time'; messageTime.textContent = 'Typing...'; messageContent.appendChild(typingIndicator); messageContent.appendChild(messageTime); messageDiv.appendChild(avatar); messageDiv.appendChild(messageContent); this.messagesList.appendChild(messageDiv); this.chatContainer.scrollTop = this.chatContainer.scrollHeight; return messageDiv; } getCurrentTime() { const now = new Date(); const hours = now.getHours().toString().padStart(2, '0'); const minutes = now.getMinutes().toString().padStart(2, '0'); return `${hours}:${minutes}`; } clearChat() { if (confirm('Are you sure you want to clear all messages?')) { // Keep only the welcome message this.messagesList.innerHTML = ` `; this.messages = []; this.messageInput.focus(); } } openSettings() { document.getElementById('settingsModal').classList.add('active'); document.getElementById('maxTokens').value = this.settings.maxTokens; document.getElementById('maxTokensValue').textContent = this.settings.maxTokens; document.getElementById('temperature').value = this.settings.temperature; document.getElementById('temperatureValue').textContent = this.settings.temperature; document.getElementById('streaming').checked = this.settings.streaming; } closeSettings() { document.getElementById('settingsModal').classList.remove('active'); } saveSettings() { this.settings.maxTokens = parseInt(document.getElementById('maxTokens').value); this.settings.temperature = parseFloat(document.getElementById('temperature').value); this.settings.streaming = document.getElementById('streaming').checked; this.closeSettings(); } showError(message) { this.loadingStatus.textContent = message; this.loadingStatus.style.color = 'var(--error)'; } } // Import transformers.js classes const { pipeline, TextStreamer } = window.transformers; // Initialize the chatbot when the page loads document.addEventListener('DOMContentLoaded', () => { new AppleChatbot(); });