export function createRuntimeMeter(element) { const value = element.querySelector('.runtime-value'); const chart = element.querySelector('.runtime-sparkline'); const bars = element.querySelector('.runtime-bars'); const announcement = element.querySelector('[role="status"]'); const engine = element.querySelector('.runtime-engine'); // Measure the intrinsic contents so CSS can animate between numeric widths. // This also follows font changes without reserving empty space while idle. const content = element.querySelector('.runtime-content'); const sizeObserver = new ResizeObserver(([entry]) => { element.style.width = `calc(${Math.ceil(entry.contentRect.width)}px + 2 * var(--runtime-padding))`; }); sizeObserver.observe(content); let loaded = false, phase, rates = [], staleTimer; function show(nextPhase, label, description) { clearTimeout(staleTimer); if (nextPhase !== phase) announcement.textContent = description ?? `${label} in this browser.`; phase = nextPhase; engine.textContent = phase === 'tool' ? 'JavaScript' : 'WebGPU'; element.dataset.state = phase; value.textContent = label; chart.setAttribute('hidden', ''); element.title = description ?? `${label} in this browser.`; if (phase !== 'generating') { rates = []; bars.replaceChildren(); } } function idle() { show('idle', loaded ? 'Ready' : 'Idle', loaded ? 'Model ready. Inference and tools run in this browser.' : 'Model loads when you send a message. Inference and tools run in this browser.'); } function sample(rate) { if (!Number.isFinite(rate) || rate < 0) { show('generating', 'Generating…'); return; } show('generating', `${rate < 10 ? rate.toFixed(1) : Math.round(rate)} tok/s`, 'Live token generation speed, including thinking tokens. Prompt-processing time is excluded.'); rates.push(rate); if (rates.length > 12) rates.shift(); const ceiling = Math.max(40, ...rates); // Keep all twelve positions visible; dots mark gaps without inventing samples. const slots = [...Array(12 - rates.length).fill(null), ...rates]; bars.replaceChildren(...slots.map((rate, index) => { if (rate === null || rate === 0) { const dot = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); dot.setAttribute('cx', String(index * 4 + 1)); dot.setAttribute('cy', '17'); dot.setAttribute('r', '1'); dot.setAttribute('opacity', rate === null ? '.35' : '.6'); return dot; } const bar = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); const height = Math.max(2, Math.min(16, rate / ceiling * 16)); bar.setAttribute('x', String(index * 4)); bar.setAttribute('y', (18 - height).toFixed(2)); bar.setAttribute('width', '2'); bar.setAttribute('height', height.toFixed(2)); bar.setAttribute('rx', '1'); return bar; })); chart.removeAttribute('hidden'); // If worker updates stop arriving, do not leave an old speed looking live. staleTimer = setTimeout(() => { show('generating', 'Generating…'); rates = []; bars.replaceChildren(); }, 2000); } idle(); return { update(data) { if (data.type === 'loaded') { loaded = true; idle(); } else if (data.type === 'idle' || data.type === 'initialized') idle(); else if (data.type === 'fatal') { loaded = false; show('error', 'Unavailable'); } else if (data.type === 'busy') { if (data.action === 'load') show('loading', 'Loading…', data.cachedOnly ? 'Loading local model…' : undefined); else if (data.action === 'prompt') show('prefill', 'Reading prompt…'); else if (data.action === 'shell') show('tool', 'Running bash…'); else if (data.action === 'read') show('tool', 'Reading file…'); else if (data.action === 'write') show('tool', 'Writing file…'); else show('working', 'Working…'); } else if (data.type === 'load_progress') { const label = data.phase === 'download' && data.loaded < data.total ? `Downloading ${Math.round(data.loaded / data.total * 100)}%` : data.phase === 'warmup' ? 'Warming up…' : 'Preparing…'; show('loading', label); } else if (data.type === 'inference_activity') { if (data.phase === 'prefill') show('prefill', 'Reading prompt…'); else if (data.phase === 'decode') sample(data.rate); else if (phase === 'generating' || phase === 'prefill') show('working', 'Working…'); } else if (data.type === 'agent_event' && data.event.type === 'tool_execution_start') { const labels = { bash: 'Running bash…', read: 'Reading file…', write: 'Writing file…', edit: 'Editing file…' }; show('tool', labels[data.event.toolName] ?? 'Running tool…'); } }, }; }