Spaces:
Running
Running
| // Main application logic for the chatbot | |
| class ChatbotApp { | |
| constructor() { | |
| this.generator = null; | |
| this.messages = [ | |
| { role: "system", content: "You are a helpful assistant." } | |
| ]; | |
| this.isGenerating = false; | |
| this.init(); | |
| } | |
| async init() { | |
| // Initialize UI elements | |
| this.loadingScreen = document.getElementById('loadingScreen'); | |
| this.chatContainer = document.getElementById('chatContainer'); | |
| this.messagesArea = document.getElementById('messagesArea'); | |
| this.messageInput = document.getElementById('messageInput'); | |
| this.sendButton = document.getElementById('sendButton'); | |
| this.progressBar = document.getElementById('progressBar'); | |
| this.progressText = document.getElementById('progressText'); | |
| // Set up event listeners | |
| this.sendButton.addEventListener('click', () => this.sendMessage()); | |
| this.messageInput.addEventListener('keydown', (e) => this.handleKeyPress(e)); | |
| this.messageInput.addEventListener('input', () => this.adjustTextareaHeight()); | |
| // Load the model | |
| await this.loadModel(); | |
| } | |
| async loadModel() { | |
| try { | |
| // Import transformers.js | |
| const { pipeline, TextStreamer, env } = await import('https://cdn.jsdelivr.net/npm/@huggingface/transformers'); | |
| // Configure environment | |
| env.allowLocalModels = false; | |
| env.allowRemoteModels = true; | |
| // Create a text generation pipeline | |
| this.generator = await pipeline( | |
| "text-generation", | |
| "onnx-community/gemma-3-270m-it-ONNX", | |
| { | |
| dtype: "fp32", | |
| progress_callback: (info) => { | |
| if (info.status === 'downloading') { | |
| const progress = Math.round((info.loaded / info.total) * 100); | |
| this.updateProgress(progress); | |
| } | |
| } | |
| } | |
| ); | |
| // Model loaded successfully | |
| this.loadingScreen.classList.add('hidden'); | |
| this.chatContainer.classList.remove('hidden'); | |
| this.messageInput.focus(); | |
| } catch (error) { | |
| console.error('Error loading model:', error); | |
| this.showError('Failed to load model. Please refresh the page and try again.'); | |
| } | |
| } | |
| updateProgress(progress) { | |
| this.progressBar.style.width = `${progress}%`; | |
| this.progressText.textContent = `${progress}%`; | |
| } | |
| showError(message) { | |
| this.loadingScreen.innerHTML = ` | |
| <div class="text-center"> | |
| <div class="inline-flex items-center justify-center w-16 h-16 mb-4 bg-red-100 rounded-full"> | |
| <svg class="w-8 h-8 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" + "d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path> | |
| </svg> | |
| </div> | |
| <h2 class="text-xl font-semibold text-gray-900 mb-2">Error</h2> | |
| <p class="text-gray-500">${message}</p> | |
| <button onclick="location.reload()" class="mt-4 px-4 py-2 bg-purple-500 text-white rounded-lg hover:bg-purple-600 transition-colors"> | |
| Retry | |
| </button> | |
| </div> | |
| `; | |
| } | |
| handleKeyPress(e) { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault(); | |
| this.sendMessage(); | |
| } | |
| } | |
| adjustTextareaHeight() { | |
| this.messageInput.style.height = 'auto'; | |
| this.messageInput.style.height = Math.min(this.messageInput.scrollHeight, 120) + 'px'; | |
| } | |
| async sendMessage() { | |
| const message = this.messageInput.value.trim(); | |
| if (!message || this.isGenerating) return; | |
| // Add user message to UI | |
| this.addMessageToUI('user', message); | |
| this.messages.push({ role: "user", content: message }); | |
| // Clear input | |
| this.messageInput.value = ''; | |
| this.adjustTextareaHeight(); | |
| // Disable send button | |
| this.isGenerating = true; | |
| this.sendButton.disabled = true; | |
| this.sendButton.innerHTML = ` | |
| <svg class="w-5 h-5 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" + "d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"> | |
| </path> | |
| </svg> | |
| `; | |
| // Add assistant message placeholder | |
| const assistantMessageId = this.addMessageToUI('assistant', '', true); | |
| try { | |
| // Generate response with streaming | |
| let fullResponse = ''; | |
| const streamer = new this.generator.tokenizer.TextStreamer(this.generator.tokenizer, { | |
| skip_prompt: true, | |
| skip_special_tokens: true, | |
| callback_function: (text) => { | |
| fullResponse += text; | |
| this.updateMessage(assistantMessageId, fullResponse); | |
| } | |
| }); | |
| const output = await this.generator(this.messages, { | |
| max_new_tokens: 512, | |
| do_sample: false, | |
| streamer: streamer, | |
| }); | |
| const response = output[0].generated_text.at(-1).content; | |
| this.messages.push({ role: "assistant", content: response }); | |
| } catch (error) { | |
| console.error('Error generating response:', error); | |
| this.updateMessage(assistantMessageId, 'Sorry, I encountered an error. Please try again.'); | |
| } finally { | |
| // Re-enable send button | |
| this.isGenerating = false; | |
| this.sendButton.disabled = false; | |
| this.sendButton.innerHTML = ` | |
| <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path> | |
| </svg> | |
| `; | |
| this.messageInput.focus(); | |
| } | |
| } | |
| addMessageToUI(role, content, isStreaming = false) { | |
| const messageId = Date.now(); | |
| const messageDiv = document.createElement('div'); | |
| messageDiv.className = 'flex items-start space-x-3'; | |
| messageDiv.id = `message-${messageId}`; | |
| if (role === 'user') { | |
| messageDiv.innerHTML = ` | |
| <div class="w-8 h-8 bg-gray-300 rounded-full flex items-center justify-center flex-shrink-0"> | |
| <svg class="w-5 h-5 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" + "d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path> | |
| </svg> | |
| </div> | |
| <div class="bg-gray-100 rounded-lg shadow-sm p-4 max-w-md"> | |
| <p class="text-gray-800 whitespace-pre-wrap">${content}</p> | |
| </div> | |
| `; | |
| } else { | |
| messageDiv.innerHTML = ` | |
| <div | |
| class="w-8 h-8 bg-gradient-to-br from-purple-500 to-pink-500 rounded-full flex items-center justify-center flex-shrink-0"> | |
| <svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" + "d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"> | |
| </path> | |
| </svg> | |
| </div> | |
| <div class="bg-white rounded-lg shadow-sm p-4 max-w-md"> | |
| <p class="text-gray-800 whitespace-pre-wrap">${isStreaming ? '<span class="animate-pulse">β</span>' : content}</p> | |
| </div> | |
| `; | |
| } | |
| this.messagesArea.appendChild(messageDiv); | |
| this.messagesArea.scrollTop = this.messagesArea.scrollHeight; | |
| return messageId; | |
| } | |
| updateMessage(messageId, content) { | |
| const messageElement = document.getElementById(`message-${messageId}`); | |
| if (messageElement) { | |
| const contentElement = messageElement.querySelector('.bg-white p, .bg-gray-100 p'); | |
| contentElement.innerHTML = content + '<span class="animate-pulse">β</span>'; | |
| this.messagesArea.scrollTop = this.messagesArea.scrollHeight; | |
| } | |
| } | |
| } | |
| // Initialize the app when DOM is loaded | |
| document.addEventListener('DOMContentLoaded', () => { | |
| new ChatbotApp(); | |
| }); |