import { pipeline, TextStreamer } from "https://cdn.jsdelivr.net/npm/@huggingface/transformers"; class AppleChatBot { constructor() { this.generator = null; this.isGenerating = false; this.messages = [ { role: "system", content: "You are a helpful assistant. Be concise and friendly in your responses." } ]; this.init(); } async init() { this.setupEventListeners(); await this.loadModel(); } setupEventListeners() { const messageInput = document.getElementById('messageInput'); const sendButton = document.getElementById('sendButton'); // Handle input changes messageInput.addEventListener('input', () => { this.updateCharCount(); this.autoResizeTextarea(); sendButton.disabled = !messageInput.value.trim() || this.isGenerating; }); // Handle Enter key messageInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this.sendMessage(); } }); // Handle send button sendButton.addEventListener('click', () => this.sendMessage()); } updateCharCount() { const messageInput = document.getElementById('messageInput'); const charCount = document.getElementById('charCount'); charCount.textContent = `${messageInput.value.length} / 2000`; } autoResizeTextarea() { const textarea = document.getElementById('messageInput'); textarea.style.height = 'auto'; textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px'; } async loadModel() { try { // Show loading overlay const loadingOverlay = document.getElementById('loadingOverlay'); const progressFill = document.getElementById('progressFill'); const progressText = document.getElementById('progressText'); const modelStatus = document.getElementById('modelStatus'); const statusDot = modelStatus.querySelector('.status-dot'); const statusText = modelStatus.querySelector('.status-text'); // Update status statusText.textContent = 'Loading model...'; statusDot.className = 'status-dot loading'; // Create 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); progressFill.style.width = `${percentage}%`; progressText.textContent = `${percentage}%`; if (progress.status === 'downloading') { statusText.textContent = `Downloading... ${percentage}%`; } else if (progress.status === 'ready') { statusText.textContent = 'Processing model...'; } } } ); // Hide loading overlay loadingOverlay.style.opacity = '0'; setTimeout(() => { loadingOverlay.style.display = 'none'; }, 300); // Update status to ready statusText.textContent = 'Ready'; statusDot.className = 'status-dot ready'; document.getElementById('sendButton').disabled = false; } catch (error) { console.error('Error loading model:', error); const loadingOverlay = document.getElementById('loadingOverlay'); const loadingContent = loadingOverlay.querySelector('.loading-content'); loadingContent.innerHTML = `
There was an error loading the AI model. Please refresh the page and try again.
${content}
`; } messageDiv.appendChild(messageContent); chatMessages.appendChild(messageDiv); // Smooth scroll to bottom this.scrollToBottom(); // Add entrance animation requestAnimationFrame(() => { messageDiv.style.opacity = '1'; messageDiv.style.transform = 'translateY(0)'; }); return messageDiv; } formatMessage(content) { // Basic markdown-like formatting return content .replace(/\*\*(.*?)\*\*/g, '$1') .replace(/\*(.*?)\*/g, '$1') .replace(/`(.*?)`/g, '$1')
.replace(/\n/g, '