// Main application logic for Apple Style Chatbot import { pipeline, TextStreamer } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.0'; 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 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 { this.updateProgress(10, 'Downloading model...'); // Initialize the text generation pipeline with progress callback this.generator = await pipeline( 'text-generation', 'onnx-community/gemma-3-270m-it-ONNX', { dtype: 'fp32', progress_callback: (progress) => { const percentage = Math.round(progress.progress * 100); const status = progress.status || 'Loading...'; this.updateProgress(percentage, status); } } ); this.updateProgress(100, 'Model ready!'); // Hide loading screen and show chat setTimeout(() => { this.loadingScreen.style.display = 'none'; this.chatContainer.style.display = 'block'; this.inputArea.style.display = 'block'; this.messageInput.focus(); }, 500); } 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 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, }; // Create prompt from messages const prompt = this.createPrompt(); // Generate response const output = await this.generator(prompt, generationParams); // Extract the assistant's response const generatedText = output[0].generated_text; const response = generatedText.replace(prompt, '').trim(); return response || 'I apologize, but I couldn\'t generate a response.'; } createPrompt() { // Create a simple prompt from the conversation history let prompt = ''; for (const msg of this.messages) { if (msg.role === 'user') { prompt += `User: ${msg.content}\n`; } else { prompt += `Assistant: ${msg.content}\n`; } } prompt += 'Assistant: '; return prompt; } 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)'; } } // Initialize the chatbot when the page loads document.addEventListener('DOMContentLoaded', () => { new AppleChatbot(); });