akhaliq's picture
akhaliq HF Staff
Update index.js
0ac4ad0 verified
Raw
History Blame Contribute Delete
11.4 kB
// index.js content here
class AIChatApp {
constructor() {
this.messagesContainer = document.getElementById('messages-container');
this.messageInput = document.getElementById('message-input');
this.sendButton = document.getElementById('send-button');
this.loadingOverlay = document.getElementById('loading-overlay');
this.statusBar = document.getElementById('status-bar');
this.statusText = document.getElementById('status-text');
this.modelStatus = document.getElementById('model-status');
this.deviceSelect = document.getElementById('device-select');
this.deviceToggle = document.getElementById('device-toggle');
this.generator = null;
this.currentDevice = 'cpu';
this.isGenerating = false;
this.messages = [];
this.initializeEventListeners();
this.initializeAutoResize();
this.loadModel();
}
initializeEventListeners() {
// Send message
this.sendButton.addEventListener('click', () => this.sendMessage());
this.messageInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
this.sendMessage();
}
});
// Device selection
this.deviceSelect.addEventListener('change', (e) => {
this.currentDevice = e.target.value;
this.updateDeviceUI();
if (this.generator) {
this.loadModel();
}
});
this.deviceToggle.addEventListener('click', () => {
this.currentDevice = this.currentDevice === 'cpu' ? 'webgpu' : 'cpu';
this.deviceSelect.value = this.currentDevice;
this.updateDeviceUI();
if (this.generator) {
this.loadModel();
}
});
// Clear messages
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'l') {
e.preventDefault();
this.clearChat();
}
});
}
initializeAutoResize() {
this.messageInput.addEventListener('input', () => {
this.messageInput.style.height = 'auto';
this.messageInput.style.height = Math.min(this.messageInput.scrollHeight, 120) + 'px';
});
}
async loadModel() {
try {
this.setStatus('Loading AI model...', 'Loading...');
this.showLoading(true);
const device = this.currentDevice === 'webgpu' ? { device: 'webgpu' } : {};
// Check WebGPU support if selected
if (this.currentDevice === 'webgpu') {
if (!navigator.gpu) {
throw new Error('WebGPU is not supported in this browser. Falling back to CPU.');
}
}
this.generator = await pipeline(
"text-generation",
"onnx-community/MobileLLM-R1-360M-ONNX",
{
dtype: "fp32",
...device
}
);
this.setStatus('Model loaded successfully!', `Model: MobileLLM-R1-360M (${this.currentDevice.toUpperCase()})`);
this.modelStatus.textContent = `Model: MobileLLM-R1-360M (${this.currentDevice.toUpperCase()})`;
this.updateDeviceUI();
this.showLoading(false);
} catch (error) {
console.error('Failed to load model:', error);
this.setStatus('Failed to load model. Retrying with CPU...', 'Error');
if (this.currentDevice === 'webgpu') {
// Fallback to CPU
this.currentDevice = 'cpu';
this.deviceSelect.value = 'cpu';
this.updateDeviceUI();
this.loadModel();
} else {
this.showLoading(false);
this.setStatus('Model loading failed. Please refresh the page.', 'Error');
this.messagesContainer.innerHTML += `
<div class="error-message">
<div class="message system-message">
<div class="message-content">
<i class="fas fa-exclamation-triangle"></i>
<strong>Error:</strong> Failed to load AI model. Please check your internet connection and refresh the page.
<br><small>Details: ${error.message}</small>
</div>
</div>
</div>
`;
}
}
}
async sendMessage() {
if (this.isGenerating || !this.generator) {
return;
}
const input = this.messageInput.value.trim();
if (!input) return;
// Add user message
const userMessage = {
role: "user",
content: input
};
this.messages.push(userMessage);
this.addMessage('user', input, true);
this.messageInput.value = '';
this.messageInput.style.height = 'auto';
// Generate response
await this.generateResponse(userMessage);
}
async generateResponse(userMessage) {
this.isGenerating = true;
this.showLoading(true);
this.setStatus('AI is generating response...', 'Generating...');
try {
// Prepare messages for the model (just the latest user message for simplicity)
const messages = [userMessage];
const streamer = new TextStreamer(this.generator.tokenizer, {
skip_prompt: true,
skip_special_tokens: true,
});
const output = await this.generator(messages, {
max_new_tokens: 200,
do_sample: false,
temperature: 0.7,
streamer: streamer
});
// Get the generated response
const responseContent = output[0].generated_text.at(-1).content;
// Add assistant response
this.addMessage('assistant', responseContent, false);
this.messages.push({ role: "assistant", content: responseContent });
this.setStatus('Response generated successfully!', 'Ready');
this.isGenerating = false;
this.showLoading(false);
} catch (error) {
console.error('Generation error:', error);
this.addMessage('assistant', `I'm sorry, I encountered an error while generating a response: ${error.message}. Please try again.`, false);
this.setStatus('Generation failed. Please try again.', 'Error');
this.isGenerating = false;
this.showLoading(false);
}
}
addMessage(type, content, isUser = false) {
const messageId = `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const icon = type === 'user' ? 'fa-user' : 'fa-robot';
const author = type === 'user' ? 'You' : 'AI Assistant';
const messageClass = type === 'user' ? 'user-message' : 'assistant-message';
const messageHTML = `
<div class="message ${messageClass}" role="log" id="${messageId}" aria-label="${author} message">
<div class="message-header">
<i class="fas ${icon}"></i>
<span class="message-author">${author}</span>
<span class="message-time">${time}</span>
</div>
<div class="message-content">
${this.formatMessageContent(content, type)}
</div>
</div>
`;
this.messagesContainer.insertAdjacentHTML('beforeend', messageHTML);
this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;
// Add math rendering if needed
this.renderMath();
}
formatMessageContent(content, type) {
// Basic markdown-like formatting
let formatted = content
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.*?)\*/g, '<em>$1</em>')
.replace(/`(.*?)`/g, '<code>$1</code>')
.replace(/\n/g, '<br>');
// Math equation detection and formatting
if (type === 'user' || content.includes('=') || content.match(/[a-zA-Z]\^[0-9]/)) {
formatted = formatted.replace(
/\$([^\$]+)\$/g,
'<span class="math-equation">$1</span>'
);
}
return formatted;
}
renderMath() {
// Simple math rendering using MathJax or basic CSS
const mathElements = this.messagesContainer.querySelectorAll('.math-equation');
mathElements.forEach(el => {
// Basic superscript/subscript formatting
el.innerHTML = el.innerHTML
.replace(/([a-zA-Z0-9])(\^[0-9]+)/g, '$1<sup>$2</sup>')
.replace(/([a-zA-Z0-9])(_[0-9]+)/g, '$1<sub>$2</sub>');
});
}
clearChat() {
if (confirm('Clear all messages?')) {
this.messages = [];
this.messagesContainer.innerHTML = `
<div class="welcome-message">
<div class="message assistant-message">
<div class="message-header">
<i class="fas fa-robot"></i>
<span class="message-author">AI Assistant</span>
<span class="message-time"></span>
</div>
<div class="message-content">
Chat cleared! How can I help you today? Try asking me to solve a math problem like "Solve x^2 - x - 6 = 0".
</div>
</div>
</div>
`;
this.setStatus('Chat cleared', 'Ready');
}
}
updateDeviceUI() {
const icon = this.currentDevice === 'webgpu' ? 'fa-microchip' : 'fa-cpu';
const text = this.currentDevice === 'webgpu' ? 'GPU' : 'CPU';
this.deviceToggle.innerHTML = `<i class="fas ${icon}"></i> ${text}`;
this.deviceToggle.title = `Switch to ${this.currentDevice === 'webgpu' ? 'CPU' : 'GPU (WebGPU)'}`;
}
setStatus(text, modelText = '') {
this.statusText.textContent = text;
if (modelText) {
this.modelStatus.textContent = modelText;
}
}
showLoading(show) {
this.loadingOverlay.style.display = show ? 'flex' : 'none';
if (!show) {
this.sendButton.disabled = false;
this.sendButton.innerHTML = '<i class="fas fa-paper-plane"></i>';
} else {
this.sendButton.disabled = true;
this.sendButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
}
}
}
// Initialize the app when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
new AIChatApp();
});
// Service Worker for offline capability (optional)
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js').catch(() => {
// Ignore service worker registration failure
});
});
}