File size: 4,792 Bytes
39371ea
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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…');
      }
    },
  };
}