Download docs/scripts/jit-box.js from Snapkitty/rowm-polymorphic-notebook: direct link, hf CLI and curl.
- Browser
- Download file 14.3 kB
-
https://huggingface.co/Snapkitty/rowm-polymorphic-notebook/resolve/d2b2d25c8af3e17bd06e8af8ea577164bc3b5cfe/docs/scripts/jit-box.js
- Command line
-
hf download hf://Snapkitty/rowm-polymorphic-notebook@d2b2d25c8af3e17bd06e8af8ea577164bc3b5cfe/docs/scripts/jit-box.js
-
curl -L -o jit-box.js https://huggingface.co/Snapkitty/rowm-polymorphic-notebook/resolve/d2b2d25c8af3e17bd06e8af8ea577164bc3b5cfe/docs/scripts/jit-box.js
14.3 kB
| /** | |
| * JIT Box — Floating Chat Interface | |
| * Embedded WebLLM assistant for notebook interaction | |
| */ | |
| class JITBox { | |
| constructor() { | |
| this.elements = this.initElements(); | |
| this.engine = null; | |
| this.contextBuilder = null; | |
| this.unicodeEngine = null; | |
| this.receiptChain = null; | |
| this.state = { | |
| isOpen: false, | |
| isMinimized: false, | |
| hasModel: false, | |
| }; | |
| this.setupEventListeners(); | |
| this.injectDefaultStyling(); | |
| } | |
| /** | |
| * Initialize DOM elements | |
| */ | |
| initElements() { | |
| return { | |
| launcher: document.getElementById('jit-launcher'), | |
| button: document.getElementById('jit-toggle'), | |
| panel: document.getElementById('jit-panel'), | |
| messagesContainer: document.getElementById('jit-messages'), | |
| input: document.getElementById('jit-input'), | |
| sendBtn: document.getElementById('jit-send'), | |
| stopBtn: document.getElementById('jit-stop'), | |
| clearBtn: document.getElementById('jit-clear'), | |
| minimizeBtn: document.getElementById('jit-minimize'), | |
| closeBtn: document.getElementById('jit-close'), | |
| statusBadge: document.getElementById('jit-status'), | |
| header: document.querySelector('.jit-header'), | |
| }; | |
| } | |
| /** | |
| * Setup event listeners | |
| */ | |
| setupEventListeners() { | |
| // Launcher | |
| this.elements.button.addEventListener('click', () => this.togglePanel()); | |
| // Panel controls | |
| this.elements.minimizeBtn.addEventListener('click', () => this.toggleMinimize()); | |
| this.elements.closeBtn.addEventListener('click', () => this.closePanel()); | |
| this.elements.clearBtn.addEventListener('click', () => this.clearMessages()); | |
| // Chat input | |
| this.elements.input.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault(); | |
| this.sendMessage(); | |
| } | |
| }); | |
| this.elements.sendBtn.addEventListener('click', () => this.sendMessage()); | |
| this.elements.stopBtn.addEventListener('click', () => this.stopGeneration()); | |
| } | |
| /** | |
| * Toggle panel visibility | |
| */ | |
| async togglePanel() { | |
| if (this.state.isOpen) { | |
| this.closePanel(); | |
| } else { | |
| this.openPanel(); | |
| } | |
| } | |
| /** | |
| * Open panel and initialize engine | |
| */ | |
| async openPanel() { | |
| this.state.isOpen = true; | |
| this.elements.panel.classList.remove('hidden'); | |
| this.elements.button.classList.add('active'); | |
| // Initialize engine on first open | |
| if (!this.engine) { | |
| await this.initializeEngine(); | |
| } | |
| this.elements.input.focus(); | |
| } | |
| /** | |
| * Close panel | |
| */ | |
| closePanel() { | |
| this.state.isOpen = false; | |
| this.elements.panel.classList.add('hidden'); | |
| this.elements.button.classList.remove('active'); | |
| } | |
| /** | |
| * Toggle minimize | |
| */ | |
| toggleMinimize() { | |
| this.state.isMinimized = !this.state.isMinimized; | |
| if (this.state.isMinimized) { | |
| this.elements.panel.classList.add('minimized'); | |
| } else { | |
| this.elements.panel.classList.remove('minimized'); | |
| } | |
| } | |
| /** | |
| * Initialize WebLLM engine | |
| */ | |
| async initializeEngine() { | |
| try { | |
| // Check WebLLM availability | |
| if (!WebLLMEngine.hasWebLLM()) { | |
| this.updateStatus('ERROR'); | |
| this.addMessage({ | |
| role: 'system', | |
| content: 'WebLLM not available. Load @mlc-ai/web-llm script first.', | |
| }); | |
| return; | |
| } | |
| this.updateStatus('LOADING'); | |
| this.elements.button.classList.add('loading'); | |
| // Initialize engines | |
| this.engine = new WebLLMEngine({ | |
| temperature: 0.2, | |
| topP: 0.9, | |
| maxTokens: 256, | |
| }); | |
| this.contextBuilder = new NotebookContextBuilder(); | |
| this.unicodeEngine = new UnicodeIREngine(); | |
| this.receiptChain = new WORMReceiptChain(); | |
| // Listen to engine events | |
| this.engine.on('statusChanged', (status) => this.updateStatus(status)); | |
| this.engine.on('token', (token) => this.appendToken(token)); | |
| this.engine.on('generationComplete', (response) => this.onGenerationComplete(response)); | |
| this.engine.on('error', (error) => this.onGenerationError(error)); | |
| // Initialize engine | |
| await this.engine.initialize(); | |
| this.state.hasModel = true; | |
| this.elements.button.classList.remove('loading'); | |
| this.updateStatus('READY'); | |
| this.elements.input.disabled = false; | |
| this.elements.sendBtn.disabled = false; | |
| this.addMessage({ | |
| role: 'system', | |
| content: `✓ Ready. Using ${this.engine.model}. WebGPU: ${WebLLMEngine.hasWebGPU() ? 'Yes' : 'CPU mode'}`, | |
| }); | |
| } catch (error) { | |
| this.updateStatus('ERROR'); | |
| this.elements.button.classList.remove('loading'); | |
| this.addMessage({ | |
| role: 'system', | |
| content: `✗ Engine initialization failed: ${error.message}`, | |
| }); | |
| } | |
| } | |
| /** | |
| * Send message | |
| */ | |
| async sendMessage() { | |
| const userMessage = this.elements.input.value.trim(); | |
| if (!userMessage || !this.engine || this.engine.isGenerating) { | |
| return; | |
| } | |
| // Add user message to UI | |
| this.addMessage({ | |
| role: 'user', | |
| content: userMessage, | |
| }); | |
| // Clear input | |
| this.elements.input.value = ''; | |
| this.elements.input.style.height = 'auto'; | |
| // Build context | |
| let systemPrompt = ''; | |
| try { | |
| const packet = this.contextBuilder.buildContextPacket(userMessage); | |
| systemPrompt = this.contextBuilder.formatSystemPrompt(packet); | |
| } catch (error) { | |
| console.error('Context building failed:', error); | |
| systemPrompt = 'Answer questions about the notebook. Preserve Unicode exactly.'; | |
| } | |
| // Generate response | |
| this.updateStatus('GENERATING'); | |
| this.elements.sendBtn.disabled = true; | |
| this.elements.stopBtn.disabled = false; | |
| const assistantContainer = this.addMessage({ | |
| role: 'assistant', | |
| content: '', | |
| streaming: true, | |
| }); | |
| try { | |
| await this.engine.generateResponse(userMessage, systemPrompt); | |
| } catch (error) { | |
| if (error.message !== 'Generation aborted') { | |
| this.onGenerationError(error.message); | |
| } | |
| } finally { | |
| this.updateStatus('READY'); | |
| this.elements.sendBtn.disabled = false; | |
| this.elements.stopBtn.disabled = true; | |
| assistantContainer.dataset.streaming = 'false'; | |
| } | |
| } | |
| /** | |
| * Append token to current message | |
| */ | |
| appendToken(token) { | |
| const messages = this.elements.messagesContainer.querySelectorAll('.jit-message'); | |
| const lastMessage = messages[messages.length - 1]; | |
| if (lastMessage && lastMessage.dataset.role === 'assistant') { | |
| const bubble = lastMessage.querySelector('.jit-message-bubble'); | |
| const sanitized = this.sanitizeToken(token); | |
| bubble.textContent += sanitized; | |
| // Auto-scroll to bottom | |
| if (!this.isUserScrolledUp()) { | |
| this.elements.messagesContainer.scrollTop = this.elements.messagesContainer.scrollHeight; | |
| } | |
| } | |
| } | |
| /** | |
| * Sanitize token for display (prevent XSS) | |
| */ | |
| sanitizeToken(token) { | |
| // Remove control characters but preserve Unicode | |
| return token.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, ''); | |
| } | |
| /** | |
| * Add message to UI | |
| */ | |
| addMessage({ role, content, streaming = false }) { | |
| const messageDiv = document.createElement('div'); | |
| messageDiv.className = `jit-message ${role}`; | |
| messageDiv.dataset.role = role; | |
| messageDiv.dataset.streaming = streaming ? 'true' : 'false'; | |
| const bubble = document.createElement('div'); | |
| bubble.className = 'jit-message-bubble'; | |
| // Apply role-specific styling | |
| if (role === 'system') { | |
| bubble.style.fontSize = '11px'; | |
| bubble.style.color = 'var(--color-text-secondary)'; | |
| bubble.style.fontStyle = 'italic'; | |
| } | |
| bubble.textContent = content; | |
| messageDiv.appendChild(bubble); | |
| this.elements.messagesContainer.appendChild(messageDiv); | |
| // Auto-scroll to bottom | |
| this.elements.messagesContainer.scrollTop = this.elements.messagesContainer.scrollHeight; | |
| return messageDiv; | |
| } | |
| /** | |
| * Check if user has scrolled up | |
| */ | |
| isUserScrolledUp() { | |
| const container = this.elements.messagesContainer; | |
| return container.scrollTop < container.scrollHeight - container.clientHeight - 100; | |
| } | |
| /** | |
| * Clear all messages | |
| */ | |
| clearMessages() { | |
| this.elements.messagesContainer.innerHTML = ''; | |
| if (this.engine) { | |
| this.engine.clearHistory(); | |
| } | |
| this.receiptChain.clear(); | |
| } | |
| /** | |
| * Stop generation | |
| */ | |
| stopGeneration() { | |
| if (this.engine && this.engine.isGenerating) { | |
| this.engine.stopGeneration(); | |
| } | |
| } | |
| /** | |
| * Update status badge | |
| */ | |
| updateStatus(status) { | |
| const statusMap = { | |
| 'OFFLINE': 'offline', | |
| 'LOADING': 'loading', | |
| 'READY': 'ready', | |
| 'GENERATING': 'generating', | |
| 'ERROR': 'error', | |
| }; | |
| const badgeClass = statusMap[status] || 'offline'; | |
| // Remove all status classes | |
| for (const cls of this.elements.statusBadge.classList) { | |
| if (cls.startsWith('jit-status-badge-')) { | |
| this.elements.statusBadge.classList.remove(cls); | |
| } | |
| } | |
| this.elements.statusBadge.classList.add(`jit-status-badge`); | |
| this.elements.statusBadge.classList.add(badgeClass); | |
| this.elements.statusBadge.textContent = status; | |
| } | |
| /** | |
| * Generation complete handler | |
| */ | |
| async onGenerationComplete(response) { | |
| try { | |
| // Create receipt for response | |
| const receipt = await this.receiptChain.createReceipt({ | |
| agentId: 'jit-assistant', | |
| capabilityId: 'generate-response', | |
| instructionHash: 'utf-8-stream', | |
| action: 'nlg', | |
| inputHash: '...', | |
| outputHash: '...', | |
| keyVersion: 1, | |
| signature: '...', | |
| status: 'sealed', | |
| }); | |
| console.log('Receipt created:', receipt.receiptId); | |
| } catch (error) { | |
| console.error('Receipt creation failed:', error); | |
| } | |
| } | |
| /** | |
| * Generation error handler | |
| */ | |
| onGenerationError(error) { | |
| this.addMessage({ | |
| role: 'system', | |
| content: `✗ Error: ${error}`, | |
| }); | |
| } | |
| /** | |
| * Inject default styling if not already present | |
| */ | |
| injectDefaultStyling() { | |
| // Check if styles already present | |
| if (document.querySelector('style[data-jit-styles]')) { | |
| return; | |
| } | |
| const style = document.createElement('style'); | |
| style.setAttribute('data-jit-styles', 'true'); | |
| style.textContent = ` | |
| .jit-status-badge { | |
| display: inline-block; | |
| font-size: 10px; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| padding: 3px 8px; | |
| border-radius: 3px; | |
| letter-spacing: 0.5px; | |
| } | |
| .jit-status-badge.offline { | |
| background: rgba(107, 114, 128, 0.3); | |
| color: #a0a0a0; | |
| } | |
| .jit-status-badge.loading { | |
| background: rgba(59, 130, 246, 0.3); | |
| color: #60a5fa; | |
| animation: jit-pulse 1.5s ease-in-out infinite; | |
| } | |
| .jit-status-badge.ready { | |
| background: rgba(16, 185, 129, 0.3); | |
| color: #10b981; | |
| } | |
| .jit-status-badge.generating { | |
| background: rgba(0, 217, 255, 0.3); | |
| color: #00d9ff; | |
| animation: jit-pulse 1s ease-in-out infinite; | |
| } | |
| .jit-status-badge.error { | |
| background: rgba(239, 68, 68, 0.3); | |
| color: #ef4444; | |
| } | |
| @keyframes jit-pulse { | |
| 0%, 100% { opacity: 0.7; } | |
| 50% { opacity: 1; } | |
| } | |
| `; | |
| document.head.appendChild(style); | |
| } | |
| /** | |
| * Get statistics | |
| */ | |
| getStats() { | |
| return { | |
| isOpen: this.state.isOpen, | |
| hasModel: this.state.hasModel, | |
| engineStatus: this.engine?.status || 'OFFLINE', | |
| messageCount: this.elements.messagesContainer.querySelectorAll('.jit-message').length, | |
| receiptCount: this.receiptChain?.receipts.length || 0, | |
| }; | |
| } | |
| } | |
| // Initialize JIT Box on DOM ready | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', () => { | |
| window.jitBox = new JITBox(); | |
| }); | |
| } else { | |
| window.jitBox = new JITBox(); | |
| } | |
| // Export for use in other modules | |
| if (typeof module !== 'undefined' && module.exports) { | |
| module.exports = JITBox; | |
| } | |