// Stage 05 + 06 — logits and sampling. // Shows a bar chart of candidate next tokens with probabilities, then a // "dice roll" selecting one. Returns the chosen token via onSample(token) when progress reaches 1. function StageLogits({ context, progress, active, paused, temperature, onSample, onExplain }) { // Tier 1 (local, always available, instant): deterministic bias-table + categorical fallback. // Tier 2 (LIVE via proxy, best-effort): POST /api/next-tokens → Claude Haiku 4.5. // Session-level cooldown state machine handles 400/429/503/529/timeout/network gracefully. // If proxy fails or is in cooldown, we silently stay on local — UI never stalls. const localDist = React.useMemo( () => window.LogitsEngine.computeLocalDistribution(context, temperature), [context, temperature] ); const [proxyDist, setProxyDist] = React.useState(null); const [proxyState, setProxyState] = React.useState(() => window.LogitsEngine.getProxyState()); React.useEffect(() => { let cancelled = false; setProxyDist(null); // drop stale proxy result on context/temperature change window.LogitsEngine.computeProxyDistribution(context, temperature) .then(result => { if (cancelled) return; if (result) setProxyDist(result); setProxyState(window.LogitsEngine.getProxyState()); }) .catch(() => { if (!cancelled) setProxyState(window.LogitsEngine.getProxyState()); }); return () => { cancelled = true; }; }, [context, temperature]); const distribution = (proxyDist && proxyDist.items) || localDist.items; // Badge source priority: if we have proxy data, show LIVE; else show the per-state badge // computed by the engine (LOCAL / BUDGET_EXHAUSTED / ERROR). const source = proxyDist ? proxyDist.source : (proxyState.badge === 'LIVE' ? 'local' : proxyState.badge.toLowerCase()); // sampling: deterministic given distribution + context hash const chosen = React.useMemo(() => { const joined = context.map(t => t.text).join(''); let h = 5381; for (let i = 0; i < joined.length; i++) { h = ((h << 5) + h + joined.charCodeAt(i)) | 0; } const r = Math.abs(Math.sin(h) * 10000) % 1; let cum = 0; for (const item of distribution) { cum += item.prob; if (r < cum) return item; } return distribution[0]; }, [distribution, context]); // Fire onSample on completion const firedRef = React.useRef(false); React.useEffect(() => { if (active && progress >= 0.999 && !firedRef.current) { firedRef.current = true; onSample && onSample(chosen.tok); } if (!active) firedRef.current = false; }, [active, progress, chosen, onSample]); const p = active ? progress : 0; const phaseLogits = clamp(p / 0.5, 0, 1); const phaseSample = clamp((p - 0.7) / 0.3, 0, 1); // When the stage becomes active (including via jump-while-paused), run a short // local cascade so the bars light up regardless of play state. This doesn't // affect the real stage progression — it just guarantees visual feedback. const [localReveal, setLocalReveal] = React.useState(0); React.useEffect(() => { if (!active) { setLocalReveal(0); return; } // animate 0 → 1 over ~600ms let raf, start; const tick = (t) => { if (start == null) start = t; const dt = (t - start) / 600; setLocalReveal(Math.min(1, dt)); if (dt < 1) raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf); }, [active]); // When paused, the reveal is driven by the local cascade (so jumping works). // When playing, it's driven by the real progress phase. const effectivePhaseLogits = paused ? localReveal : phaseLogits; return (
top candidates · probability {distribution.length} shown of ~50000
{distribution.slice(0, 10).map((item, i) => { const isChosen = active && phaseSample > 0 && item.tok === chosen.tok; const shown = effectivePhaseLogits > i / 10; return (
"{item.tok.replace(/ /g, '␣')}"
{(item.prob * 100).toFixed(1)}%
); })}
{/* sample indicator */}
0 ? 'var(--magenta)' : 'var(--line)'}`, borderRadius: 4, padding: '10px 14px', minWidth: 170, background: phaseSample > 0 ? 'color-mix(in oklch, var(--magenta) 8%, transparent)' : 'rgba(0,0,0,0.25)', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', gap: 6, transition: 'border-color 300ms, background 300ms', boxShadow: phaseSample > 0 ? '0 0 20px color-mix(in oklch, var(--magenta) 30%, transparent)' : 'none', }}>
0 ? 'var(--magenta)' : 'var(--text-faint)', letterSpacing: '0.1em' }}>SAMPLED
0 ? 'var(--magenta)' : 'var(--text-faint)', textShadow: phaseSample > 0 ? '0 0 14px var(--magenta)' : 'none', transition: 'color 300ms', whiteSpace: 'pre', }}> {phaseSample > 0 ? `"${chosen.tok.replace(/ /g, '␣')}"` : '···'}
p = {phaseSample > 0 ? (chosen.prob * 100).toFixed(1) + '%' : '—'}
); } // ── Badge ────────────────────────────────────────────────────────────────── // Two visible states: // LIVE (magenta, glowing) — proxy returned a real distribution. // LOCAL (dim, quiet) — anything else. The fallback is graceful by design, // so unreachable/timeout/error all just read LOCAL. // BUDGET exhaustion is actionable so it gets its own amber treatment; everything // else (network down, cooldown, no proxy configured) reads LOCAL, with the // reason surfaced only in the hover tooltip. function BadgeState({ source, proxyState }) { const isLive = source === 'proxy' || source === 'proxy-cached'; const isBudget = proxyState.badge === 'BUDGET_EXHAUSTED' && !isLive; let label, color, title; if (isLive) { label = 'LIVE'; color = 'var(--magenta)'; const dayRem = proxyState.dayRemainingUsd; const livenessNote = source === 'proxy-cached' ? ' (cached)' : ''; title = `Distribution via Anthropic proxy${livenessNote}. The Messages API doesn't expose token logits — probabilities are Claude-estimated, not measured. See proxy/README.md. ${dayRem != null ? `Budget remaining today: $${dayRem.toFixed(3)}.` : ''}`; } else if (isBudget) { label = 'BUDGET'; color = 'var(--amber)'; title = `Daily/monthly proxy budget exhausted — using local bias table until the window resets. ${proxyState.lastError}`; } else { label = 'LOCAL'; color = 'var(--text-faint)'; // Compose a tooltip that quietly explains why we're on LOCAL. const reasonBits = []; if (proxyState.badge === 'ERROR' && proxyState.lastError) { reasonBits.push(`Proxy unavailable: ${proxyState.lastError}. Will retry automatically.`); } else { reasonBits.push('Run `node proxy/server.mjs` with ANTHROPIC_API_KEY to enable LIVE (or use ./dev if .env is set).'); } title = `Using the local deterministic bias table. Ships with the HTML, no network required. ${reasonBits.join(' ')}`; } const dotGlow = isLive ? '0 0 4px var(--magenta)' : (isBudget ? '0 0 4px var(--amber)' : 'none'); const borderColor = isLive ? 'var(--magenta)' : (isBudget ? 'var(--amber)' : 'var(--line-strong)'); return ( {label} ); } window.StageLogits = StageLogits;