import { pipeline, env } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.0'; // ============================================ // CONFIGURATION // ============================================ const CONFIG = { model: 'Xenova/Llama-3.2-3B-Instruct', // Quantized version for faster loading maxTokens: 512, temperature: 0.7, topP: 0.9, doSample: true, repetitionPenalty: 1.1 }; // ============================================ // DOM ELEMENTS // ============================================ const elements = { chatContainer: document.getElementById('chat-container'), messagesList: document.getElementById('messages-list'), userInput: document.getElementById('user-input'), sendBtn: document.getElementById('send-btn'), loadingIndicator: document.getElementById('loading-indicator'), loadingMessage: document.getElementById('loading-message'), progressBar: document.getElementById('progress-bar'), progressFill: document.getElementById('progress-fill'), welcomeScreen: document.getElementById('welcome-screen'), statusDot: document.querySelector('.status-dot'), statusText: document.querySelector('.status-text'), exampleBtns: document.querySelectorAll('.example-btn') }; // ============================================ // APPLICATION STATE // ============================================ let appState = { modelLoaded: false, isGenerating: false, messages: [] }; // ============================================ // WORKER HANDLERS // ============================================ let currentResponse = ''; let isWorkerBusy = false; // Handle worker messages worker.onmessage = (e) => { const { type, payload } = e.data; switch (type) { case 'loading': updateModelStatus('loading'); break; case 'progress': updateProgress(payload.progress, payload.message); break; case 'loaded': appState.modelLoaded = true; updateModelStatus('ready'); hideLoading(); hideWelcomeScreen(); break; case 'result': handleGenerationComplete(payload.result); break; case 'error': showError(payload.error); break; case 'cancelled': stopGeneration(); break; } }; // ============================================ // MODEL INITIALIZATION // ============================================ async function initializeModel() { try { showLoading('Initializing model...'); worker.postMessage({ type: 'load', payload: { model: CONFIG.model } }); } catch (error) { showError(`Failed to initialize model: ${error.message}`); } } // ============================================ // GENERATION FUNCTIONS // ============================================ async function sendMessage(message) { if (!appState.modelLoaded || isWorkerBusy) return; // Add user message to chat addMessage('user', message); appState.messages.push({ role: 'user', content: message }); // Clear input elements.userInput.value = ''; elements.userInput.style.height = 'auto'; updateSendButton(); // Start generation isWorkerBusy = true; currentResponse = ''; showLoading('Generating response...'); try { // Create prompt for the model const prompt = createPrompt(); worker.postMessage({ type: 'generate', payload: { prompt: prompt, maxTokens: CONFIG.maxTokens, temperature: CONFIG.temperature, topP: CONFIG.topP, doSample: CONFIG.doSample, repetitionPenalty: CONFIG.repetitionPenalty } }); } catch (error) { showError(`Generation failed: ${error.message}`); isWorkerBusy = false; } } function createPrompt() { // Convert messages to chat format if (appState.messages.length === 0) { return 'You are a helpful AI assistant. Please answer the user\'s question helpfully and accurately.'; } return appState.messages .map(msg => `${msg.role}: ${msg.content}`) .join('\n\n') + '\n\nassistant:'; } // ============================================ // MESSAGE HANDLING // ============================================ function addMessage(role, content) { const messageDiv = document.createElement('div'); messageDiv.className = `message ${role}`; const avatar = role === 'user' ? '👤' : '🤖'; messageDiv.innerHTML = `
`; elements.messagesList.appendChild(messageDiv); scrollToBottom(); return messageDiv; } function handleGenerationComplete(generatedText) { // Add AI response to chat const messageDiv = addMessage('ai', ''); // Create content element for streaming const contentElement = messageDiv.querySelector('.message-content'); contentElement.classList.add('typing-cursor'); // Stream the response streamText(contentElement, generatedText) .then(() => { contentElement.classList.remove('typing-cursor'); appState.messages.push({ role: 'assistant', content: generatedText }); isWorkerBusy = false; hideLoading(); }) .catch(error => { showError(`Streaming failed: ${error.message}`); isWorkerBusy = false; hideLoading(); }); } async function streamText(element, text) { const words = text.split(' '); let currentText = ''; for (let i = 0; i < words.length; i++) { currentText += words[i] + ' '; element.innerHTML = formatContent(currentText); scrollToBottom(); // Add a small delay for more natural typing effect await new Promise(resolve => setTimeout(resolve, 10 + Math.random() * 20)); } } function formatContent(text) { // Simple text formatting // Escape HTML let formatted = text .replace(/&/g, '&') .replace(//g, '>'); // Format code blocks formatted = formatted.replace(/```(\w*)\n([\s\S]*?)```/g, (match, lang, code) => { return `${escapeHtml(code.trim())}`;
});
// Format inline code
formatted = formatted.replace(/`([^`]+)`/g, '$1');
// Format bold text
formatted = formatted.replace(/\*\*([^*]+)\*\*/g, '$1');
// Format line breaks
formatted = formatted.replace(/\n/g, '