/**
* Ahmad JIT UI
* Centered chat box for WebLLM inference
*/
class AhmadJITUI {
constructor() {
this.elements = {};
this.messageQueue = [];
this.setupElements();
this.setupEventListeners();
}
setupElements() {
// Create centered container
if (!document.getElementById('ahmad-jit-box')) {
const box = document.createElement('div');
box.id = 'ahmad-jit-box';
box.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 400px;
height: 600px;
background: #1a2654;
border: 1px solid #2a3654;
border-radius: 4px;
z-index: 9999;
display: flex;
flex-direction: column;
font-family: monospace;
font-size: 12px;
color: #e0e0e0;
box-shadow: 0 0 30px rgba(0, 217, 255, 0.2);
`;
box.innerHTML = `
Ω Ahmad JIT Assistant
OFFLINE
`;
document.body.appendChild(box);
}
this.elements.box = document.getElementById('ahmad-jit-box');
this.elements.messages = document.getElementById('ahmad-jit-messages');
this.elements.input = document.getElementById('ahmad-jit-input');
this.elements.loadBtn = document.getElementById('ahmad-jit-load');
this.elements.send = document.getElementById('ahmad-jit-send');
this.elements.stop = document.getElementById('ahmad-jit-stop');
this.elements.status = document.getElementById('ahmad-jit-status');
this.elements.composer = document.getElementById('ahmad-jit-composer');
this.elements.actionButtons = document.getElementById('ahmad-jit-action-buttons');
}
setupEventListeners() {
this.elements.loadBtn.addEventListener('click', () => this.loadModel());
this.elements.send.addEventListener('click', () => this.sendMessage());
this.elements.stop.addEventListener('click', () => this.stopGeneration());
this.elements.input.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
this.sendMessage();
}
});
}
async loadModel() {
try {
this.elements.loadBtn.disabled = true;
this.updateStatus('LOADING');
this.addMessage('system', 'Initializing WebLLM engine...');
this.addMessage('system', 'Downloading TinyLlama model (500MB, takes 1-5 min)...');
const engine = new AhmadJITEngine();
window.ahmadEngine = engine;
await engine.initialize();
this.updateStatus('READY');
this.elements.loadBtn.style.display = 'none';
this.elements.actionButtons.style.display = 'flex';
this.elements.input.disabled = false;
this.elements.send.disabled = false;
this.addMessage('system', '✓ Ahmad Bot ready. Ask about this notebook.');
} catch (error) {
this.updateStatus('ERROR');
this.addMessage('system', `Error: ${error.message}`);
this.addMessage('system', 'Check console (F12) for details.');
this.elements.loadBtn.disabled = false;
console.error('Model load failed:', error);
}
}
async sendMessage() {
const text = this.elements.input.value.trim();
if (!text) {
this.addMessage('system', 'Please enter a message.');
return;
}
if (!window.ahmadEngine) {
this.addMessage('system', 'Error: Engine not initialized. Load model first.');
return;
}
this.addMessage('user', text);
this.elements.input.value = '';
this.elements.input.disabled = true;
this.elements.send.disabled = true;
this.elements.stop.disabled = false;
this.elements.loadBtn.disabled = true;
this.updateStatus('GENERATING');
try {
let response = '';
await window.ahmadEngine.generate(text, (token) => {
response += token;
this.appendToken(token);
});
if (!response || response.length === 0) {
this.addMessage('system', 'Warning: No response generated.');
}
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
this.addMessage('system', `Generation error: ${errorMsg}`);
console.error('Message generation failed:', error);
}
this.updateStatus('READY');
this.elements.input.disabled = false;
this.elements.send.disabled = false;
this.elements.stop.disabled = true;
this.elements.loadBtn.disabled = false;
this.elements.input.focus();
}
stopGeneration() {
if (window.ahmadEngine) {
window.ahmadEngine.stop();
}
}
addMessage(role, text) {
const msgDiv = document.createElement('div');
const bgColor = role === 'user' ? '#00d9ff' : role === 'system' ? '#2a3654' : '#0a0e27';
const textColor = role === 'user' ? '#0a0e27' : role === 'system' ? '#a0a0a0' : '#e0e0e0';
const border = role === 'assistant' ? '1px solid #00d9ff' : 'none';
msgDiv.style.cssText = `
padding: 8px;
border-radius: 4px;
background: ${bgColor};
color: ${textColor};
word-wrap: break-word;
white-space: pre-wrap;
border: ${border};
font-style: ${role === 'system' ? 'italic' : 'normal'};
`;
msgDiv.dataset.role = role;
msgDiv.textContent = text;
this.elements.messages.appendChild(msgDiv);
this.elements.messages.scrollTop = this.elements.messages.scrollHeight;
}
appendToken(token) {
if (!token || typeof token !== 'string') {
return; // Silently skip invalid tokens
}
const messages = this.elements.messages;
if (!messages) {
console.error('Messages container not found');
return;
}
if (messages.lastChild && messages.lastChild.dataset && messages.lastChild.dataset.role === 'assistant') {
// Append to existing assistant message (textContent is XSS-safe)
messages.lastChild.textContent += token;
} else {
// Create new assistant message
this.addMessage('assistant', token);
}
// Auto-scroll to bottom
try {
messages.scrollTop = messages.scrollHeight;
} catch (e) {
console.warn('Scroll failed:', e);
}
}
updateStatus(status) {
this.elements.status.textContent = status;
this.elements.status.style.color =
status === 'READY' ? '#10b981' :
status === 'LOADING' ? '#fbbf24' :
status === 'GENERATING' ? '#fbbf24' :
status === 'ERROR' ? '#ef4444' :
'#a0a0a0';
}
}
// Auto-initialize on load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
window.ahmadJITUI = new AhmadJITUI();
console.log('Ahmad JIT UI initialized. Click LOAD MODEL to start.');
});
} else {
window.ahmadJITUI = new AhmadJITUI();
console.log('Ahmad JIT UI initialized. Click LOAD MODEL to start.');
}