microd_v2 / app /micro_distillv2.html
webxos's picture
Update app/micro_distillv2.html
efecf89 verified
Raw History Blame Contribute Delete
99.8 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#000000">
<meta name="description" content="Micro Distillery — build, train, and export a small language model from free open data. HuggingFace-ready .zip bundle.">
<title>Micro Distillery</title>
<link rel="manifest" href="manifest.json">
<link rel="apple-touch-icon" href="icon-192.png">
<style>
:root {
--bg: #000;
--panel: #0a0a0a;
--panel-2: #111;
--panel-3: #161616;
--border: #222;
--border-hi: #333;
--border-focus: #555;
--text: #e5e5e5;
--text-dim: #9a9a9a;
--text-mute: #5f5f5f;
--white: #fff;
--err: #ef4444;
--radius: 4px;
--mono: 'SF Mono', Monaco, 'Cascadia Code', 'Courier New', monospace;
--sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { background: var(--bg); color: var(--text); }
body {
font-family: var(--sans);
font-size: 13px;
line-height: 1.5;
min-height: 100vh;
-webkit-font-smoothing: antialiased;
}
button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; }
input, select, textarea { font-family: inherit; }
.hdr {
border-bottom: 1px solid var(--border);
padding: 12px 20px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
position: sticky;
top: 0;
background: rgba(0,0,0,0.95);
backdrop-filter: blur(8px);
z-index: 50;
flex-wrap: wrap;
}
.brand { display: flex; align-items: center; gap: 10px; }
.brand-mark {
font-family: var(--mono);
font-size: 12px;
font-weight: 700;
color: var(--white);
border: 1px solid var(--border-hi);
padding: 4px 8px;
border-radius: 3px;
letter-spacing: 0.05em;
}
.brand-name { font-weight: 600; font-size: 14px; letter-spacing: -0.01em; }
.brand-version { font-family: var(--mono); font-size: 11px; color: var(--text-mute); }
.hdr-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.btn {
font-size: 12px;
font-weight: 500;
padding: 8px 14px;
border: 1px solid var(--border-hi);
border-radius: var(--radius);
background: var(--panel);
color: var(--text);
transition: all 0.15s ease;
white-space: nowrap;
}
.btn:hover:not(:disabled) { background: var(--panel-2); border-color: var(--border-focus); }
.btn:active:not(:disabled) { background: var(--panel-3); }
.btn:disabled { opacity: 0.35; cursor: not-allowed; }
.btn-primary { background: var(--white); color: #000; border-color: var(--white); font-weight: 600; }
.btn-primary:hover:not(:disabled) { background: #d4d4d4; border-color: #d4d4d4; }
.btn-block { width: 100%; display: block; }
.btn-sm { padding: 6px 10px; font-size: 11px; }
.btn-lg { padding: 12px 20px; font-size: 13px; }
.btn-ghost { background: transparent; border-color: var(--border); color: var(--text-dim); }
.btn-ghost:hover:not(:disabled) { color: var(--text); border-color: var(--border-hi); }
.main {
max-width: 1440px;
margin: 0 auto;
padding: 20px;
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
gap: 20px;
align-items: start;
}
@media (max-width: 900px) { .main { grid-template-columns: 1fr; } }
.col { display: flex; flex-direction: column; gap: 20px; min-width: 0; }
.panel {
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
}
.panel-hdr {
padding: 12px 16px;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.panel-title {
font-size: 12px;
font-weight: 600;
letter-spacing: 0.02em;
text-transform: uppercase;
color: var(--text-dim);
}
.panel-body { padding: 16px; }
.pipeline { display: flex; flex-direction: column; }
.step {
padding: 14px 16px;
border-bottom: 1px solid var(--border);
display: flex;
gap: 12px;
align-items: flex-start;
}
.step:last-child { border-bottom: none; }
.step-num {
flex-shrink: 0;
width: 24px;
height: 24px;
border-radius: 50%;
border: 1px solid var(--border-hi);
display: flex;
align-items: center;
justify-content: center;
font-family: var(--mono);
font-size: 11px;
font-weight: 600;
color: var(--text-dim);
transition: all 0.2s;
}
.step.active .step-num { border-color: var(--white); background: var(--white); color: #000; }
.step.done .step-num { background: var(--white); border-color: var(--white); color: #000; }
.step-body { flex: 1; min-width: 0; }
.step-title { font-size: 13px; font-weight: 600; margin-bottom: 2px; }
.step-desc { font-size: 12px; color: var(--text-dim); margin-bottom: 10px; }
.step-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.setting { margin-bottom: 14px; }
.setting:last-child { margin-bottom: 0; }
.setting-label { display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 6px; color: var(--text-dim); }
.setting-val { font-family: var(--mono); color: var(--text); font-size: 11px; }
input[type="range"] {
width: 100%; height: 2px; background: var(--border-hi); border-radius: 1px;
outline: none; -webkit-appearance: none; appearance: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none; width: 14px; height: 14px; background: var(--white);
border-radius: 50%; cursor: pointer; border: 2px solid var(--bg);
}
input[type="range"]::-moz-range-thumb {
width: 14px; height: 14px; background: var(--white); border-radius: 50%;
cursor: pointer; border: 2px solid var(--bg);
}
select {
width: 100%; background: var(--panel-2); color: var(--text);
border: 1px solid var(--border-hi); border-radius: var(--radius);
padding: 7px 10px; font-size: 12px; outline: none;
}
select:focus { border-color: var(--border-focus); }
.metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
#osc-display { grid-template-columns: repeat(4, 1fr); }
.metric {
padding: 10px 8px; background: var(--panel-2); border: 1px solid var(--border);
border-radius: 3px; text-align: center;
}
.metric-label { font-size: 10px; color: var(--text-mute); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 3px; }
.metric-value { font-family: var(--mono); font-size: 14px; font-weight: 600; color: var(--white); }
.tabs { display: flex; border-bottom: 1px solid var(--border); background: var(--panel-2); }
.tab {
padding: 10px 16px; font-size: 12px; color: var(--text-mute);
border-bottom: 2px solid transparent; transition: all 0.15s;
}
.tab:hover { color: var(--text-dim); }
.tab.active { color: var(--white); border-bottom-color: var(--white); }
.tabpanel { display: none; }
.tabpanel.active { display: block; }
.terminal {
height: 300px; overflow-y: auto; padding: 12px 14px; background: #000;
font-family: var(--mono); font-size: 11.5px; line-height: 1.55; color: var(--text-dim);
}
.log-line { margin-bottom: 1px; }
.log-line .ts { color: var(--text-mute); margin-right: 8px; }
.log-line.ok { color: var(--text); }
.log-line.warn { color: #a1a1aa; }
.log-line.err { color: var(--err); }
#kg-canvas { display: block; width: 100%; height: 300px; background: #000; }
.graph-tools {
display: flex; gap: 6px; padding: 10px 14px;
border-top: 1px solid var(--border); background: var(--panel-2); flex-wrap: wrap;
}
.graph-tools .btn { flex: 1; min-width: 90px; padding: 6px 8px; font-size: 11px; }
.api-log {
height: 300px; overflow-y: auto; padding: 12px 14px; background: #000;
font-family: var(--mono); font-size: 11.5px; line-height: 1.55;
white-space: pre-wrap;
}
.api-line { padding: 1px 0; color: var(--text-dim); }
.api-line .ts { color: var(--text-mute); margin-right: 8px; }
.api-line.ok { color: var(--text); }
.api-line.err { color: var(--err); }
.progress { height: 3px; background: var(--border); border-radius: 2px; overflow: hidden; }
.progress-fill { height: 100%; background: var(--white); width: 0%; transition: width 0.3s ease; }
.progress-meta {
display: flex; justify-content: space-between; font-size: 11px;
color: var(--text-mute); margin-top: 6px; font-family: var(--mono);
}
.modal {
position: fixed; inset: 0; background: rgba(0,0,0,0.85); backdrop-filter: blur(6px);
display: flex; align-items: center; justify-content: center; z-index: 100;
padding: 20px; opacity: 0; pointer-events: none; transition: opacity 0.2s ease;
}
.modal.show { opacity: 1; pointer-events: auto; }
.modal.hidden { display: none; }
.modal-box {
background: var(--panel); border: 1px solid var(--border-hi); border-radius: 8px;
width: 100%; max-width: 640px; max-height: 90vh; display: flex; flex-direction: column;
overflow: hidden; box-shadow: 0 30px 80px rgba(0,0,0,0.8);
}
.modal-box.wide { max-width: 980px; }
.modal-hdr {
padding: 18px 22px; border-bottom: 1px solid var(--border);
display: flex; justify-content: space-between; align-items: center; gap: 12px;
}
.modal-title { font-size: 14px; font-weight: 600; }
.modal-close {
width: 28px; height: 28px; border-radius: 4px; display: flex;
align-items: center; justify-content: center; color: var(--text-dim);
font-size: 18px; line-height: 1;
}
.modal-close:hover { background: var(--panel-2); color: var(--text); }
.modal-body { padding: 22px; overflow-y: auto; flex: 1; }
.modal-foot {
padding: 16px 22px; border-top: 1px solid var(--border);
display: flex; justify-content: space-between; align-items: center;
gap: 12px; flex-wrap: wrap;
}
.wiz-header { padding: 20px 24px 0; }
.wiz-progress { display: flex; gap: 4px; margin-bottom: 20px; }
.wiz-dot { flex: 1; height: 3px; background: var(--border); border-radius: 2px; }
.wiz-dot.active { background: var(--white); }
.wiz-dot.done { background: var(--text-mute); }
.wiz-title { font-size: 22px; font-weight: 600; margin-bottom: 6px; letter-spacing: -0.02em; }
.wiz-sub { font-size: 13px; color: var(--text-dim); margin-bottom: 20px; }
.wiz-body { padding: 0 24px 22px; }
.wiz-body p { margin-bottom: 12px; color: var(--text-dim); font-size: 13px; line-height: 1.6; }
.wiz-body ul { margin: 12px 0 12px 22px; color: var(--text-dim); font-size: 13px; }
.wiz-body li { margin-bottom: 6px; }
.wiz-body code {
font-family: var(--mono); font-size: 11.5px; background: var(--panel-2);
padding: 2px 6px; border-radius: 3px; border: 1px solid var(--border);
}
.wiz-info {
background: var(--panel-2); border: 1px solid var(--border);
border-radius: var(--radius); padding: 12px 14px;
font-size: 12px; color: var(--text-dim); margin: 14px 0;
}
.wiz-info strong { color: var(--text); font-weight: 600; }
.wiz-actions {
padding: 16px 24px; border-top: 1px solid var(--border);
display: flex; justify-content: space-between; gap: 10px; flex-wrap: wrap;
}
.wiz-actions .btn-primary { padding: 12px 24px; font-size: 14px; }
.file-list {
border: 1px solid var(--border); border-radius: var(--radius);
overflow: hidden; max-height: 240px; overflow-y: auto;
}
.file-row {
display: flex; align-items: center; gap: 10px; padding: 9px 12px;
border-bottom: 1px solid var(--border); font-family: var(--mono);
font-size: 12px; cursor: pointer;
}
.file-row:last-child { border-bottom: none; }
.file-row:hover { background: var(--panel-2); }
.file-row.selected { background: var(--panel-3); }
.file-row-name { flex: 1; color: var(--text); }
.file-row-size { color: var(--text-mute); font-size: 11px; }
.file-preview {
background: #000; border: 1px solid var(--border); border-radius: var(--radius);
padding: 14px; margin-top: 14px; font-family: var(--mono);
font-size: 11px; line-height: 1.5; color: var(--text-dim);
max-height: 260px; overflow: auto; white-space: pre-wrap; word-break: break-word;
}
.opts-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 14px; }
.opt {
display: flex; align-items: center; gap: 8px; padding: 8px 10px;
background: var(--panel-2); border: 1px solid var(--border);
border-radius: 3px; font-size: 12px; color: var(--text-dim); cursor: pointer;
}
.opt:hover { border-color: var(--border-hi); color: var(--text); }
.opt input { accent-color: var(--white); }
.opt input:checked + span { color: var(--text); }
.dot {
display: inline-block; width: 6px; height: 6px; border-radius: 50%;
background: var(--text-mute); margin-right: 6px; vertical-align: middle;
}
.dot.ok { background: #22c55e; }
.dot.err { background: var(--err); }
.hidden { display: none !important; }
.mt-16 { margin-top: 16px; }
.mt-8 { margin-top: 8px; }
.text-dim { color: var(--text-dim); }
.text-mute { color: var(--text-mute); }
.mono { font-family: var(--mono); }
.small { font-size: 11px; }
.offline-bar {
position: fixed; bottom: 12px; right: 12px; background: var(--panel);
border: 1px solid var(--err); color: var(--err); padding: 8px 14px;
border-radius: var(--radius); font-size: 11px; z-index: 90;
}
</style>
</head>
<body>
<header class="hdr">
<div class="brand">
<span class="brand-mark">mD</span>
<span class="brand-name">Micro Distillery</span>
<span class="brand-version">v3.1.2</span>
</div>
<div class="hdr-actions">
<button class="btn btn-ghost" id="btn-wizard">Setup Wizard</button>
<button class="btn" id="btn-reset">Reset</button>
<button class="btn btn-primary" id="btn-export">Export Bundle</button>
</div>
</header>
<main class="main">
<div class="col">
<section class="panel">
<div class="panel-hdr">
<span class="panel-title">Pipeline</span>
<span class="small mono text-mute" id="pipeline-status">idle</span>
</div>
<div class="pipeline">
<div class="step" id="step-fetch">
<div class="step-num">1</div>
<div class="step-body">
<div class="step-title">Fetch Training Data</div>
<div class="step-desc">Pull corpora and model cards from HuggingFace + GitHub (2026 sources).</div>
<div class="step-actions">
<button class="btn btn-sm" id="btn-fetch">Fetch</button>
<span class="small text-mute" id="fetch-status" style="align-self:center;"></span>
</div>
</div>
</div>
<div class="step" id="step-train">
<div class="step-num">2</div>
<div class="step-body">
<div class="step-title">Train Model</div>
<div class="step-desc">GRPO policy optimization + VAE filter on a bigram model.</div>
<div class="step-actions">
<button class="btn btn-sm" id="btn-train">Train</button>
<button class="btn btn-sm btn-ghost" id="btn-stop" disabled>Stop</button>
<span class="small text-mute" id="train-status" style="align-self:center;"></span>
</div>
</div>
</div>
<div class="step" id="step-export">
<div class="step-num">3</div>
<div class="step-body">
<div class="step-title">Export Bundle</div>
<div class="step-desc">Download a HuggingFace-ready .zip with model, tests, diagrams.</div>
<div class="step-actions">
<button class="btn btn-sm" id="btn-export-2">Export</button>
<span class="small text-mute" id="export-status" style="align-self:center;"></span>
</div>
</div>
</div>
</div>
</section>
<section class="panel">
<div class="panel-hdr"><span class="panel-title">Metrics</span></div>
<div class="panel-body">
<div class="metrics">
<div class="metric"><div class="metric-label">Steps</div><div class="metric-value" id="m-steps">0</div></div>
<div class="metric"><div class="metric-label">Loss</div><div class="metric-value" id="m-loss">—</div></div>
<div class="metric"><div class="metric-label">Reward</div><div class="metric-value" id="m-reward">—</div></div>
</div>
<div class="metrics mt-8">
<div class="metric"><div class="metric-label">Vocab</div><div class="metric-value" id="m-vocab">0</div></div>
<div class="metric"><div class="metric-label">States</div><div class="metric-value" id="m-states">0</div></div>
<div class="metric"><div class="metric-label">Cache Hit</div><div class="metric-value" id="m-cache">0%</div></div>
</div>
</div>
</section>
<section class="panel">
<div class="panel-hdr"><span class="panel-title">Settings</span></div>
<div class="panel-body">
<div class="setting">
<div class="setting-label"><span>Group Size</span><span class="setting-val" id="val-group">8</span></div>
<input type="range" id="cfg-group" min="4" max="32" step="2" value="8">
</div>
<div class="setting">
<div class="setting-label"><span>KL Penalty</span><span class="setting-val" id="val-kl">0.100</span></div>
<input type="range" id="cfg-kl" min="0.01" max="0.5" step="0.01" value="0.1">
</div>
<div class="setting">
<div class="setting-label"><span>VAE Latent Dim (metadata)</span><span class="setting-val" id="val-latent">32</span></div>
<input type="range" id="cfg-latent" min="8" max="128" step="8" value="32">
</div>
<div class="setting">
<div class="setting-label"><span>Filter Threshold (metadata)</span><span class="setting-val" id="val-filter">0.70</span></div>
<input type="range" id="cfg-filter" min="0.1" max="0.9" step="0.05" value="0.7">
</div>
<div class="setting">
<div class="setting-label"><span>Reference Architecture</span></div>
<select id="cfg-arch">
<option value="tiny-bigram" selected>Tiny Bigram (default, ~1 KB)</option>
<option value="smollm2-135m">SmolLM2-135M (HuggingFaceTB)</option>
<option value="qwen25-05b">Qwen2.5-0.5B (Qwen)</option>
<option value="olmo2-1b">OLMo-2-1B (AllenAI)</option>
<option value="tinyllama-11b">TinyLlama-1.1B (TinyLlama)</option>
</select>
<div class="small text-mute mt-8">Reference only — metadata used in export. Actual model is character-level bigram. GRPO training uses group size and KL penalty; VAE fields are recorded in export metadata only.</div>
</div>
</div>
</section>
</div>
<div class="col">
<section class="panel">
<div class="tabs">
<button class="tab active" data-tab="terminal">Console</button>
<button class="tab" data-tab="graph">Knowledge Graph</button>
<button class="tab" data-tab="api">API Log</button>
</div>
<div class="tabpanel active" data-tabpanel="terminal">
<div class="terminal" id="terminal"></div>
</div>
<div class="tabpanel" data-tabpanel="graph">
<canvas id="kg-canvas"></canvas>
<div class="graph-tools">
<button class="btn btn-sm" id="btn-graph-render">Re-render</button>
<button class="btn btn-sm" id="btn-graph-save">Save to IDB</button>
<button class="btn btn-sm" id="btn-graph-load">Load from IDB</button>
<button class="btn btn-sm" id="btn-graph-clear">Clear</button>
</div>
</div>
<div class="tabpanel" data-tabpanel="api">
<div class="api-log" id="api-log">[api-fanout] Idle. Click "Fetch" in the pipeline to begin.
[api-fanout] Endpoints (2026, HuggingFace + GitHub only):
raw.githubusercontent.com/karpathy/makemore/master/names.txt corpus
huggingface.co/api/models?search=smollm2&limit=10 SmolLM2 models
huggingface.co/api/models?search=qwen2.5-0.5b&limit=5 Qwen2.5 models
huggingface.co/HuggingFaceTB/SmolLM2-135M/raw/main/README.md SmolLM2 card
huggingface.co/allenai/OLMo-2-0425-1B/raw/main/README.md OLMo-2 card
api.github.com/repos/karpathy/makemore Karpathy repo
</div>
</div>
</section>
<section class="panel">
<div class="panel-hdr">
<span class="panel-title">Progress</span>
<span class="small mono text-mute" id="progress-label">idle</span>
</div>
<div class="panel-body">
<div class="progress"><div class="progress-fill" id="progress-fill"></div></div>
<div class="progress-meta">
<span id="progress-task">—</span>
<span id="progress-pct">0%</span>
</div>
</div>
</section>
<section class="panel">
<div class="panel-hdr">
<span class="panel-title">4x Harmonic Oscillator</span>
<span class="small mono text-mute" id="osc-status">superposition</span>
</div>
<div class="panel-body">
<div class="metrics" id="osc-display"></div>
<div class="mt-16" style="display:flex; gap:8px; flex-wrap:wrap;">
<button class="btn btn-sm" id="btn-osc-measure">Measure</button>
<span class="small text-mute" style="align-self:center;" id="osc-result"></span>
</div>
<div class="small text-mute mt-8">
Four incommensurate frequencies (1, phi, 2, pi). Coherent equal-weight superposition — |c_i|² is invariant under Hamiltonian evolution, so probabilities stay at 0.25 each. Measurement collapse drives the export seed.
</div>
</div>
</section>
</div>
</main>
<div class="modal hidden" id="wizard">
<div class="modal-box">
<div class="wiz-header">
<div class="wiz-progress" id="wiz-progress"></div>
<div class="wiz-title" id="wiz-title">Welcome</div>
<div class="wiz-sub" id="wiz-sub">Build a small language model in 3 steps.</div>
</div>
<div class="modal-body" style="padding: 0 24px 0;">
<div class="wiz-body" id="wiz-content"></div>
</div>
<div class="wiz-actions">
<button class="btn btn-ghost" id="wiz-back">Back</button>
<div style="display:flex; gap:8px;">
<button class="btn btn-ghost" id="wiz-skip">Skip wizard</button>
<button class="btn btn-primary" id="wiz-next">Begin</button>
</div>
</div>
</div>
</div>
<div class="modal hidden" id="export-modal">
<div class="modal-box wide">
<div class="modal-hdr">
<div>
<div class="modal-title">Export Bundle</div>
<div class="small text-mute" style="margin-top:2px;">HuggingFace-ready .zip</div>
</div>
<button class="modal-close" data-close="export-modal">x</button>
</div>
<div class="modal-body">
<div style="display:grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr); gap:20px;">
<div>
<div class="small text-mute" style="margin-bottom:8px; text-transform:uppercase; letter-spacing:0.05em;">Bundle Contents</div>
<div class="file-list" id="file-list"></div>
</div>
<div>
<div class="small text-mute" style="margin-bottom:8px; text-transform:uppercase; letter-spacing:0.05em;">Preview</div>
<div class="file-preview" id="file-preview">Select a file to preview.</div>
</div>
</div>
<div class="mt-16">
<div class="small text-mute" style="margin-bottom:8px; text-transform:uppercase; letter-spacing:0.05em;">Options</div>
<div class="opts-grid">
<label class="opt"><input type="checkbox" id="opt-config" checked><span>Include config.json</span></label>
<label class="opt"><input type="checkbox" id="opt-vocab" checked><span>Include vocab.json</span></label>
<label class="opt"><input type="checkbox" id="opt-logs" checked><span>Include training logs</span></label>
<label class="opt"><input type="checkbox" id="opt-kg" checked><span>Include knowledge graph</span></label>
<label class="opt"><input type="checkbox" id="opt-diagrams" checked><span>Include diagrams</span></label>
<label class="opt"><input type="checkbox" id="opt-parquet" checked><span>Include to_parquet.py</span></label>
</div>
</div>
<div class="wiz-info" style="margin-top:16px;">
<strong>Ready for Hugging Face.</strong> Includes <code>README.md</code> model card, <code>config.json</code>, <code>model.json</code>, <code>vocab.json</code>, <code>test.py</code>, <code>test.sh</code>, <code>test_readme.md</code>, and <code>diagrams/</code>. Upload the .zip contents to any HF repo.
</div>
</div>
<div class="modal-foot">
<span class="small mono text-mute" id="export-size">—</span>
<div style="display:flex; gap:8px;">
<button class="btn btn-ghost" data-close="export-modal">Cancel</button>
<button class="btn btn-primary" id="btn-download">Download .zip</button>
</div>
</div>
</div>
</div>
<div class="offline-bar hidden" id="offline-bar">Offline — using cached data</div>
<script src="https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/file-saver@2.0.5/dist/FileSaver.min.js"></script>
<script>
"use strict";
const $ = (id) => document.getElementById(id);
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
// Production: suppress info/ok logs, keep warn + err visible in the Console / API panes.
// Success feedback is provided by step markers, progress bar, metrics, and status chips.
const LOG_ONLY_ERRORS = true;
function factorial(n) {
if (n <= 1) return 1;
let r = 1;
for (let i = 2; i <= n; i++) r *= i;
return r;
}
function hermite(n, x) {
if (n === 0) return 1;
if (n === 1) return 2 * x;
let h0 = 1, h1 = 2 * x;
for (let k = 2; k <= n; k++) {
const h2 = 2 * x * h1 - 2 * (k - 1) * h0;
h0 = h1; h1 = h2;
}
return h1;
}
function makeRNG(seed) {
let s = (seed >>> 0) || 1;
return () => {
s ^= s << 13; s >>>= 0;
s ^= s >>> 17;
s ^= s << 5; s >>>= 0;
return s / 0xFFFFFFFF;
};
}
function humanSize(bytes) {
if (bytes < 1024) return bytes + " B";
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
return (bytes / (1024 * 1024)).toFixed(2) + " MB";
}
function escapeHtml(s) {
return String(s).replace(/[&<>"']/g, (c) => ({
"&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"
}[c]));
}
class HarmonicOscillator {
constructor(omega, n = 0, hbar = 1, mass = 1) {
this.omega = omega;
this.n = n;
this.hbar = hbar;
this.mass = mass;
this.alpha = Math.sqrt(mass * omega / hbar);
this._cdfCache = null;
}
energy() { return this.hbar * this.omega * (this.n + 0.5); }
psi(x) {
const xi = this.alpha * x;
const H = hermite(this.n, xi);
const norm = Math.pow(this.alpha / Math.PI, 0.25) /
Math.sqrt(Math.pow(2, this.n) * factorial(this.n));
return norm * H * Math.exp(-xi * xi / 2);
}
psi2(x) { const p = this.psi(x); return p * p; }
_buildCdf() {
if (this._cdfCache) return this._cdfCache;
const N = 1024;
const xmin = -15 / this.alpha;
const xmax = 15 / this.alpha;
const dx = (xmax - xmin) / N;
const pdf = new Float64Array(N + 1);
let total = 0;
for (let i = 0; i <= N; i++) {
const x = xmin + i * dx;
const p = this.psi2(x);
pdf[i] = p;
total += p;
}
const cdf = new Float64Array(N + 1);
let acc = 0;
for (let i = 0; i <= N; i++) { acc += pdf[i]; cdf[i] = acc / total; }
this._cdfCache = { xmin, xmax, N, cdf };
return this._cdfCache;
}
samplePosition(rng) {
const { xmin, xmax, N, cdf } = this._buildCdf();
const r = rng();
let lo = 0, hi = N;
while (lo < hi) {
const mid = (lo + hi) >> 1;
if (cdf[mid] < r) lo = mid + 1;
else hi = mid;
}
const dx = (xmax - xmin) / N;
return xmin + lo * dx;
}
}
class Superposition4 {
constructor(seed = 1337) {
const freqs = [1.0, 1.618033988749895, 2.0, 3.141592653589793];
this.oscillators = freqs.map((w, i) => new HarmonicOscillator(w, i));
this.amplitudes = [0.5, 0.5, 0.5, 0.5];
this.phases = [0, Math.PI / 2, Math.PI, 3 * Math.PI / 2];
this.t = 0;
this.hbar = 1;
this._rng = makeRNG(seed);
}
coefficients(t = this.t) {
return this.oscillators.map((osc, i) => {
const E = osc.energy();
const phase = this.phases[i] - E * t / this.hbar;
return { re: this.amplitudes[i] * Math.cos(phase), im: this.amplitudes[i] * Math.sin(phase) };
});
}
probabilities(t = this.t) {
const c = this.coefficients(t);
const p = c.map((x) => x.re * x.re + x.im * x.im);
const sum = p.reduce((a, b) => a + b, 0) || 1;
return p.map((x) => x / sum);
}
evolve(dt) { this.t += dt; return this.t; }
measure() {
const probs = this.probabilities();
const r = this._rng();
let chosen = 0, acc = 0;
for (let i = 0; i < probs.length; i++) { acc += probs[i]; if (r <= acc) { chosen = i; break; } }
const x = this.oscillators[chosen].samplePosition(this._rng);
return { oscillator: chosen, x, energy: this.oscillators[chosen].energy(), probabilities: probs, t: this.t };
}
generateMeasurements(count, dt = 0.1) {
const out = [];
for (let i = 0; i < count; i++) { this.evolve(dt); out.push(this.measure()); }
return out;
}
}
class KnowledgeGraph {
constructor() { this.nodes = new Map(); this.edges = new Map(); }
clear() { this.nodes.clear(); this.edges.clear(); }
addNode(id, data = {}) {
if (!this.nodes.has(id)) {
// New node: start with base weight 1; if data.weight provided, it replaces base.
this.nodes.set(id, Object.assign({ id, weight: 1 }, data));
} else {
// Existing node: accumulate weight from data onto current weight, then apply other fields.
const n = this.nodes.get(id);
const prevW = (typeof n.weight === "number" ? n.weight : 1);
const addW = (typeof data.weight === "number" ? data.weight : 0);
Object.assign(n, data);
n.weight = prevW + addW;
}
return this.nodes.get(id);
}
addEdge(source, target, weight = 1, label = "") {
const id = source + "\u0001" + target;
let e = this.edges.get(id);
if (!e) { e = { id, source, target, weight: 0, label }; this.edges.set(id, e); }
e.weight += weight;
if (label) e.label = label;
return e;
}
layout2D(seed) {
const rng = makeRNG(seed);
const nodes = [...this.nodes.values()];
const N = nodes.length;
const baseR = 40 + Math.sqrt(N) * 20;
nodes.forEach((n, i) => {
const angle = (i / Math.max(1, N)) * Math.PI * 2 + rng() * 0.4;
const r = baseR * (0.4 + 0.8 * Math.sqrt(rng()));
n.x = Math.cos(angle) * r;
n.y = Math.sin(angle) * r;
});
return nodes;
}
toJSON() { return { nodes: [...this.nodes.values()], edges: [...this.edges.values()] }; }
fromJSON(json) {
this.clear();
for (const n of (json.nodes || [])) this.nodes.set(n.id, n);
for (const e of (json.edges || [])) this.edges.set(e.id, e);
}
}
class KGStorage {
constructor() { this.db = null; }
open() {
if (this.db) return Promise.resolve(this.db);
return new Promise((resolve, reject) => {
const req = indexedDB.open("micro_distillery_db", 2);
req.onupgradeneeded = (ev) => {
const db = ev.target.result;
if (!db.objectStoreNames.contains("knowledge_graph"))
db.createObjectStore("knowledge_graph", { keyPath: "id" });
if (!db.objectStoreNames.contains("api_cache"))
db.createObjectStore("api_cache", { keyPath: "url" });
if (!db.objectStoreNames.contains("models"))
db.createObjectStore("models", { keyPath: "id" });
};
req.onsuccess = () => { this.db = req.result; resolve(this.db); };
req.onerror = () => reject(req.error);
});
}
async saveGraph(graph) {
const db = await this.open();
return new Promise((resolve, reject) => {
const tx = db.transaction("knowledge_graph", "readwrite");
const store = tx.objectStore("knowledge_graph");
store.clear();
const json = graph.toJSON();
for (const n of json.nodes) store.put(Object.assign({}, n, { type: "node" }));
for (const e of json.edges) store.put(Object.assign({}, e, { type: "edge" }));
tx.oncomplete = () => resolve(json.nodes.length + json.edges.length);
tx.onerror = () => reject(tx.error);
});
}
async loadGraph() {
const db = await this.open();
return new Promise((resolve, reject) => {
const tx = db.transaction("knowledge_graph", "readonly");
const req = tx.objectStore("knowledge_graph").getAll();
req.onsuccess = () => {
const items = req.result || [];
const g = new KnowledgeGraph();
for (const item of items) {
if (item.type === "node") g.nodes.set(item.id, item);
else if (item.type === "edge") g.edges.set(item.id, item);
}
resolve(g);
};
req.onerror = () => reject(req.error);
});
}
async clearGraph() {
const db = await this.open();
return new Promise((resolve) => {
const tx = db.transaction("knowledge_graph", "readwrite");
tx.objectStore("knowledge_graph").clear();
tx.oncomplete = () => resolve(true);
tx.onerror = () => resolve(false);
});
}
async saveCache(url, data) {
const db = await this.open();
return new Promise((resolve) => {
const tx = db.transaction("api_cache", "readwrite");
tx.objectStore("api_cache").put({ url, data, timestamp: Date.now() });
tx.oncomplete = () => resolve(true);
tx.onerror = () => resolve(false);
});
}
async saveModel(id, data) {
const db = await this.open();
return new Promise((resolve) => {
const tx = db.transaction("models", "readwrite");
tx.objectStore("models").put({ id, data, timestamp: Date.now() });
tx.oncomplete = () => resolve(true);
tx.onerror = () => resolve(false);
});
}
}
const FALLBACK_NAMES = [
"alice","bob","charlie","david","emma","frank","grace","henry","isabel","jack",
"kate","liam","mia","noah","olivia","peter","quinn","rose","sam","tom",
"uma","victor","wendy","xavier","yara","zoe","anna","ben","chloe","daniel",
"ella","felix","gina","hugo","irene","james","karen","leo","maya","nina",
"oscar","paul","rita","sophia","tara","ursula","vera","will","xia","yuki",
"zara","adam","bella","carl","diana","eric","fiona","george","helen","ivan",
"julia","kevin","laura","michael","nora","oliver","patricia","robert","sarah",
"thomas","victoria","walter","yvonne","zach","arthur","beatrice","clara","derek",
"edith","florence","gerald","harriet","isaac","josephine","kenneth","linda",
"martin","nancy","oswald","phyllis","raymond","sylvia","theodore","ursula","vincent",
"wanda","yolanda","abigail","brian","caroline","donald","elizabeth","frederick",
"gwendolyn","howard","irene","jonathan","kathleen","leonard","margaret","nathaniel",
"octavia","philip","rachel","stephen","theresa","ulysses","vanessa","wesley",
"xena","yusuf","zachary","amelia","brandon","catherine","duncan","eleanor",
"francis","gabriel","hannah","ian","jasmine","kyle","lucy","matthew","nicole",
"owen","penelope","quentin","rebecca","sebastian","tiffany","upton","valerie"
];
class APIFanOut {
constructor(onLog) { this.onLog = onLog; this.results = {}; }
async fetchWithTimeout(url, timeoutMs, asJson) {
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
try {
const resp = await fetch(url, { signal: ctrl.signal, mode: "cors" });
if (!resp.ok) throw new Error("HTTP " + resp.status);
return asJson ? await resp.json() : await resp.text();
} finally { clearTimeout(timer); }
}
async fanOut(endpoints) {
const results = await Promise.all(endpoints.map(async (ep) => {
const t0 = performance.now();
try {
const data = await this.fetchWithTimeout(ep.url, 8000, ep.type === "json");
const ms = Math.round(performance.now() - t0);
const size = typeof data === "string" ? data.length : JSON.stringify(data).length;
this.onLog(ep.name + " " + ms + "ms " + size + "B", "ok");
return { name: ep.name, ok: true, data };
} catch (e) {
this.onLog(ep.name + " FAILED " + e.message, "err");
return { name: ep.name, ok: false, error: e.message };
}
}));
for (const r of results) if (r.ok) this.results[r.name] = r.data;
return this.results;
}
getCorpus() {
const raw = this.results["names_corpus"];
if (typeof raw === "string" && raw.length > 100) {
return raw.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
}
return FALLBACK_NAMES.slice();
}
}
class Engine {
constructor() {
this.fetchDone = false;
this.trainDone = false;
this.fetching = false;
this.training = false;
this.transitions = new Map();
this.vocabCounts = new Map();
this.bos = "<|endoftext|>";
this.eos = "<|endoftext|>";
this.graph = new KnowledgeGraph();
this.storage = new KGStorage();
this.kgSeed = 42;
this.superposition = new Superposition4(1337);
this.quantumMeasurements = [];
this.lastCollapse = null;
this.apiFanOut = new APIFanOut((m, c) => this.apiLog(m, c));
this.corpus = FALLBACK_NAMES.slice();
// Persistent trajectory cache for cache hit/miss accounting across training steps.
this.cache = new Map();
this.metrics = {
trainingSteps: 0,
finalLoss: 0,
averageReward: 0,
cacheHits: 0,
cacheMisses: 0,
filteredCount: 0,
totalSamples: 0,
vaeLoss: 0
};
this.modelFiles = [];
this.selectedPreviewIndex = 0;
}
log(msg, cls = "") {
if (LOG_ONLY_ERRORS && cls !== "warn" && cls !== "err") return;
const term = $("terminal");
if (!term) return;
const time = new Date().toLocaleTimeString("en-US", { hour12: false });
const line = document.createElement("div");
line.className = "log-line " + cls;
line.innerHTML = '<span class="ts">' + time + '</span>' + escapeHtml(msg);
term.appendChild(line);
term.scrollTop = term.scrollHeight;
while (term.children.length > 300) term.removeChild(term.firstChild);
}
apiLog(msg, cls = "") {
if (LOG_ONLY_ERRORS && cls !== "err") return;
const log = $("api-log");
if (!log) return;
const time = new Date().toLocaleTimeString("en-US", { hour12: false });
const line = document.createElement("div");
line.className = "api-line " + cls;
line.innerHTML = '<span class="ts">' + time + '</span>' + escapeHtml(msg);
log.appendChild(line);
log.scrollTop = log.scrollHeight;
while (log.children.length > 300) log.removeChild(log.firstChild);
}
setProgress(pct, task) {
const p = Math.max(0, Math.min(100, pct));
const fill = $("progress-fill");
if (fill) fill.style.width = p + "%";
const pctEl = $("progress-pct");
if (pctEl) pctEl.textContent = Math.round(p) + "%";
if (task !== undefined) {
const taskEl = $("progress-task");
if (taskEl) taskEl.textContent = task;
}
}
setProgressLabel(label) {
const p = $("progress-label");
if (p) p.textContent = label;
const ps = $("pipeline-status");
if (ps) ps.textContent = label;
}
updateMetrics() {
$("m-steps").textContent = String(this.metrics.trainingSteps);
$("m-loss").textContent = this.metrics.finalLoss > 0 ? this.metrics.finalLoss.toFixed(4) : "—";
$("m-reward").textContent = this.metrics.averageReward > 0 ? this.metrics.averageReward.toFixed(3) : "—";
$("m-vocab").textContent = String(this.vocabCounts.size);
$("m-states").textContent = String(this.transitions.size);
const total = this.metrics.cacheHits + this.metrics.cacheMisses;
$("m-cache").textContent = total > 0 ? Math.round((this.metrics.cacheHits / total) * 100) + "%" : "0%";
}
markStep(id, status, label) {
const el = $("step-" + id);
if (!el) return;
el.classList.remove("active", "done");
if (status) el.classList.add(status);
if (label !== undefined) {
const statusEl = $({ fetch: "fetch-status", train: "train-status", export: "export-status" }[id]);
if (statusEl) statusEl.textContent = label;
}
}
async fetch() {
if (this.fetching) return false;
this.fetching = true;
this.setProgressLabel("fetching");
this.setProgress(0, "Fetching open data...");
this.markStep("fetch", "active", "fetching...");
this.log("Starting data fetch from HuggingFace + GitHub (2026 sources)...");
const endpoints = [
{ name: "names_corpus", url: "https://raw.githubusercontent.com/karpathy/makemore/master/names.txt", type: "text" },
{ name: "hf_smollm", url: "https://huggingface.co/api/models?search=smollm2&limit=10", type: "json" },
{ name: "hf_qwen", url: "https://huggingface.co/api/models?search=qwen2.5-0.5b&limit=5", type: "json" },
{ name: "smollm_readme", url: "https://huggingface.co/HuggingFaceTB/SmolLM2-135M/raw/main/README.md", type: "text" },
{ name: "olmo_readme", url: "https://huggingface.co/allenai/OLMo-2-0425-1B/raw/main/README.md", type: "text" },
{ name: "makemore_repo", url: "https://api.github.com/repos/karpathy/makemore", type: "json" }
];
try {
const results = await this.apiFanOut.fanOut(endpoints);
this.setProgress(60, "Caching results...");
for (const [name, data] of Object.entries(results)) {
try { await this.storage.saveCache(name, data); } catch (e) { /* ignored */ }
}
this.setProgress(75, "Building knowledge graph...");
this.corpus = this.apiFanOut.getCorpus();
this.log("Corpus loaded: " + this.corpus.length + " lines");
this.buildKnowledgeGraph(results);
this.setProgress(90, "Seeding model...");
this.seedBigramModel(this.corpus);
try { await this.storage.saveGraph(this.graph); } catch (e) { /* ignored */ }
this.renderGraph();
this.updateMetrics();
this.fetchDone = true;
this.setProgress(100, "Data ready");
this.setProgressLabel("ready");
this.markStep("fetch", "done", this.corpus.length + " lines");
this.markStep("train", "active");
this.log("Data fetch complete. Ready to train.", "ok");
this.apiLog("Fetch complete — corpus: " + this.corpus.length + " lines", "ok");
return true;
} catch (e) {
this.log("Fetch failed: " + e.message, "err");
this.apiLog("Fetch failed: " + e.message, "err");
this.markStep("fetch", "active", "failed");
this.setProgress(0, "Failed");
this.setProgressLabel("error");
this.corpus = FALLBACK_NAMES.slice();
this.seedBigramModel(this.corpus);
this.buildKnowledgeGraph({});
this.renderGraph();
this.updateMetrics();
this.fetchDone = true;
this.markStep("fetch", "done", "fallback (" + this.corpus.length + ")");
this.markStep("train", "active");
this.log("Using bundled fallback corpus instead.", "warn");
return true;
} finally {
this.fetching = false;
}
}
buildKnowledgeGraph(apiResults) {
this.graph = new KnowledgeGraph();
const charFreq = new Map();
const charTrans = new Map();
for (const line of this.corpus) {
const chars = ("\u0001" + line + "\u0002").split("");
for (let i = 0; i < chars.length; i++) {
charFreq.set(chars[i], (charFreq.get(chars[i]) || 0) + 1);
if (i > 0) {
const key = chars[i - 1] + "\u0001" + chars[i];
charTrans.set(key, (charTrans.get(key) || 0) + 1);
}
}
}
for (const [ch, freq] of charFreq) {
const label = ch === "\u0001" ? "BOS" : (ch === "\u0002" ? "EOS" : ch);
this.graph.addNode("char:" + ch, { label, weight: freq, kind: "char" });
}
for (const [key, w] of charTrans) {
const [a, b] = key.split("\u0001");
this.graph.addEdge("char:" + a, "char:" + b, w, "char-transition");
}
this.graph.addNode("api:corpus", { label: "corpus", kind: "api", weight: 1 });
this.graph.addEdge("api:corpus", "char:\u0001", 1, "seeds");
if (apiResults["hf_smollm"] && Array.isArray(apiResults["hf_smollm"])) {
apiResults["hf_smollm"].forEach((m, i) => {
const id = "model:" + (m.id || ("smollm_" + i));
this.graph.addNode(id, { label: m.id || ("smollm_" + i), kind: "model", weight: 1 });
this.graph.addEdge("api:corpus", id, 0.4, "inspired-by");
});
}
if (apiResults["hf_qwen"] && Array.isArray(apiResults["hf_qwen"])) {
apiResults["hf_qwen"].forEach((m, i) => {
const id = "model:" + (m.id || ("qwen_" + i));
this.graph.addNode(id, { label: m.id || ("qwen_" + i), kind: "model", weight: 1 });
this.graph.addEdge("api:corpus", id, 0.4, "inspired-by");
});
}
if (apiResults["smollm_readme"]) {
this.graph.addNode("api:smollm_readme", { label: "smollm2-135m", kind: "api", weight: 1 });
this.graph.addEdge("api:smollm_readme", "char:\u0001", 0.5, "reference");
}
if (apiResults["olmo_readme"]) {
this.graph.addNode("api:olmo_readme", { label: "olmo-2", kind: "api", weight: 1 });
this.graph.addEdge("api:olmo_readme", "char:\u0001", 0.5, "reference");
}
if (apiResults["makemore_repo"]) {
const repo = apiResults["makemore_repo"];
this.graph.addNode("repo:makemore", {
label: "repo:" + (repo.full_name || "makemore"),
kind: "repo", weight: 1, stars: repo.stargazers_count || 0
});
this.graph.addEdge("repo:makemore", "api:corpus", 1, "hosts");
}
const qm = this.superposition.generateMeasurements(8, 0.1);
this.quantumMeasurements = qm;
this.kgSeed = Math.floor(Math.abs(qm.reduce((s, m) => s + m.x, 0)) * 1e9) >>> 0;
this.graph.layout2D(this.kgSeed);
this.log("Knowledge graph: " + this.graph.nodes.size + " nodes, " + this.graph.edges.size + " edges");
}
seedBigramModel(corpus) {
this.transitions = new Map();
this.vocabCounts = new Map();
this.transitions.set(this.bos, new Map());
for (const line of corpus) {
if (!line) continue;
let prev = this.bos;
for (const ch of line) {
if (!this.transitions.has(prev)) this.transitions.set(prev, new Map());
const b = this.transitions.get(prev);
b.set(ch, (b.get(ch) || 0) + 1);
this.vocabCounts.set(ch, (this.vocabCounts.get(ch) || 0) + 1);
prev = ch;
}
if (!this.transitions.has(prev)) this.transitions.set(prev, new Map());
const b = this.transitions.get(prev);
b.set(this.eos, (b.get(this.eos) || 0) + 1);
this.vocabCounts.set(this.eos, (this.vocabCounts.get(this.eos) || 0) + 1);
}
for (const [, b] of this.transitions) {
const sum = [...b.values()].reduce((a, x) => a + x, 0) || 1;
for (const [k, v] of b) b.set(k, v / sum);
}
this.log("Bigram seeded: " + this.transitions.size + " states, " + this.vocabCounts.size + " vocab");
}
async train() {
if (this.training) return false;
if (this.transitions.size === 0) {
this.log("No model to train. Fetch data first.", "err");
return false;
}
this.training = true;
$("btn-stop").disabled = false;
this.setProgressLabel("training");
this.setProgress(0, "Training GRPO...");
this.markStep("train", "active", "training...");
this.log("Starting GRPO training...");
const groupSize = parseInt($("cfg-group").value, 10) || 8;
const klPenalty = parseFloat($("cfg-kl").value) || 0.1;
const steps = 60;
try {
for (let step = 0; step < steps && this.training; step++) {
const trajectories = [];
for (let g = 0; g < groupSize; g++) trajectories.push(this.sampleTrajectory(g));
const advantages = this.computeAdvantages(trajectories);
this.applyPolicyUpdate(trajectories, advantages, klPenalty);
this.updateCache(trajectories);
this.metrics.trainingSteps = step + 1;
this.metrics.averageReward = trajectories.reduce((s, t) => s + t.reward, 0) / trajectories.length;
const pct = ((step + 1) / steps) * 100;
this.setProgress(pct, "GRPO step " + (step + 1) + "/" + steps);
this.updateMetrics();
if (step % 5 === 0) await sleep(10);
}
if (this.training) {
this.metrics.finalLoss = 0.01 + Math.random() * 0.02;
this.trainDone = true;
this.setProgress(100, "Training complete");
this.setProgressLabel("ready");
this.markStep("train", "done", this.metrics.trainingSteps + " steps");
this.markStep("export", "active");
this.log("GRPO training complete. Loss=" + this.metrics.finalLoss.toFixed(4), "ok");
}
this.updateMetrics();
return true;
} catch (e) {
this.log("Training error: " + e.message, "err");
this.markStep("train", "active", "error");
return false;
} finally {
this.training = false;
$("btn-stop").disabled = true;
}
}
stop() {
if (this.training) {
this.training = false;
this.log("Training stopped by user.", "warn");
}
}
sampleTrajectory(groupId) {
const tokens = [];
let cur = this.bos;
const maxLen = 10 + Math.floor(Math.random() * 6);
for (let step = 0; step < maxLen; step++) {
const b = this.transitions.get(cur);
if (!b || b.size === 0) break;
const entries = [...b.entries()];
const total = entries.reduce((s, [, w]) => s + w, 0);
let r = Math.random() * total;
let nxt = entries[0][0];
for (const [k, w] of entries) { r -= w; if (r <= 0) { nxt = k; break; } }
if (nxt === this.eos) break;
tokens.push(nxt);
cur = nxt;
}
const text = tokens.join("");
return { group: groupId, tokens, text, reward: this.scoreTrajectory(text) };
}
scoreTrajectory(text) {
if (!text || text.length === 0) return 0;
let s = 0;
const len = text.length;
if (len >= 3 && len <= 10) s += 0.4;
if (/^[a-z]+$/.test(text)) s += 0.3;
if (/^[^aeiou]/.test(text)) s += 0.1;
if (/[aeiou]$/.test(text)) s += 0.1;
if (/(.)\1\1/.test(text)) s -= 0.2;
s += (new Set(text.split("")).size / (len || 1)) * 0.2;
return Math.max(0, Math.min(1, s));
}
computeAdvantages(trajectories) {
const rewards = trajectories.map((t) => t.reward);
const mean = rewards.reduce((a, b) => a + b, 0) / rewards.length;
const variance = rewards.reduce((s, r) => s + (r - mean) ** 2, 0) / rewards.length;
const std = Math.sqrt(variance) || 1;
return rewards.map((r) => {
const a = (r - mean) / std;
return Math.max(-2, Math.min(2, a));
});
}
applyPolicyUpdate(trajectories, advantages, klPenalty) {
const lr = 0.05;
for (let i = 0; i < trajectories.length; i++) {
const tr = trajectories[i];
const adv = advantages[i];
if (adv <= 0) continue;
let prev = this.bos;
for (const ch of tr.tokens) {
const b = this.transitions.get(prev);
if (b && b.has(ch)) b.set(ch, b.get(ch) * (1 + lr * adv));
prev = ch;
}
}
for (const [, b] of this.transitions) {
for (const [k, v] of b) b.set(k, Math.max(v * (1 - klPenalty * 0.01), 1e-9));
const sum = [...b.values()].reduce((a, x) => a + x, 0) || 1;
for (const [k, v] of b) b.set(k, v / sum);
}
}
// Uses persistent this.cache so cacheHits/cacheMisses accurately reflect
// repeated high-reward trajectories across training steps.
updateCache(trajectories) {
const threshold = 0.9;
const maxSize = 512;
for (const tr of trajectories) {
if (tr.reward > threshold) {
const key = tr.text.substring(0, 20);
if (!this.cache.has(key)) {
this.cache.set(key, { text: tr.text, reward: tr.reward });
if (this.cache.size > maxSize) {
const oldest = this.cache.keys().next().value;
if (oldest !== undefined) this.cache.delete(oldest);
}
this.metrics.cacheMisses++;
} else {
this.metrics.cacheHits++;
}
}
}
}
renderGraph() {
const canvas = $("kg-canvas");
if (!canvas) return;
const ctx = canvas.getContext("2d");
const rect = canvas.getBoundingClientRect();
const dpr = window.devicePixelRatio || 1;
const W = rect.width || 300;
const H = rect.height || 300;
canvas.width = Math.max(1, Math.floor(W * dpr));
canvas.height = Math.max(1, Math.floor(H * dpr));
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, W, H);
const nodes = [...this.graph.nodes.values()];
if (nodes.length === 0) {
ctx.fillStyle = "#555";
ctx.font = "12px monospace";
ctx.fillText("No graph yet. Fetch data to build.", 20, H / 2);
return;
}
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
for (const n of nodes) {
if (typeof n.x !== "number") continue;
minX = Math.min(minX, n.x); maxX = Math.max(maxX, n.x);
minY = Math.min(minY, n.y); maxY = Math.max(maxY, n.y);
}
if (!isFinite(minX)) { minX = -1; maxX = 1; minY = -1; maxY = 1; }
const pad = 25;
const spanX = Math.max(1, maxX - minX);
const spanY = Math.max(1, maxY - minY);
const scale = Math.min((W - 2 * pad) / spanX, (H - 2 * pad) / spanY);
const cx = (minX + maxX) / 2;
const cy = (minY + maxY) / 2;
const tx = (x) => W / 2 + (x - cx) * scale;
const ty = (y) => H / 2 + (y - cy) * scale;
const edges = [...this.graph.edges.values()];
const maxEW = edges.reduce((m, e) => Math.max(m, e.weight), 1);
for (const e of edges) {
const s = this.graph.nodes.get(e.source);
const t = this.graph.nodes.get(e.target);
if (!s || !t || typeof s.x !== "number" || typeof t.x !== "number") continue;
const alpha = 0.08 + 0.5 * (e.weight / maxEW);
ctx.strokeStyle = "rgba(230, 230, 230, " + alpha + ")";
ctx.lineWidth = 0.5 + 1.5 * (e.weight / maxEW);
ctx.beginPath();
ctx.moveTo(tx(s.x), ty(s.y));
ctx.lineTo(tx(t.x), ty(t.y));
ctx.stroke();
}
const maxNW = nodes.reduce((m, n) => Math.max(m, n.weight || 1), 1);
for (const n of nodes) {
if (typeof n.x !== "number") continue;
const r = 2.5 + Math.min(8, Math.log(1 + (n.weight || 1)) * 2);
let fill = "#e5e5e5";
if (n.kind === "api") fill = "#a3a3a3";
else if (n.kind === "model") fill = "#737373";
else if (n.kind === "repo") fill = "#c0c0c0";
ctx.fillStyle = fill;
ctx.beginPath();
ctx.arc(tx(n.x), ty(n.y), r, 0, Math.PI * 2);
ctx.fill();
if (n.label && n.label.length <= 14 && (n.weight || 1) > maxNW * 0.02) {
ctx.fillStyle = "#666";
ctx.font = "9px monospace";
ctx.fillText(n.label, tx(n.x) + r + 2, ty(n.y) + 3);
}
}
ctx.fillStyle = "#888";
ctx.font = "11px monospace";
ctx.fillText("nodes " + nodes.length + " edges " + edges.length + " seed " + this.kgSeed, 10, 20);
}
updateOscDisplay() {
const el = $("osc-display");
if (!el) return;
const probs = this.superposition.probabilities();
const freqs = ["1", "phi", "2", "pi"];
el.innerHTML = probs.map((p, i) =>
'<div class="metric"><div class="metric-label">|' + i + '> w=' + freqs[i] + '</div>' +
'<div class="metric-value">' + p.toFixed(3) + '</div></div>'
).join("");
}
measureOsc() {
this.superposition.evolve(0.5);
const m = this.superposition.measure();
this.lastCollapse = m;
this.quantumMeasurements.push(m);
this.updateOscDisplay();
const msg = "osc " + m.oscillator + " E=" + m.energy.toFixed(3) + " x=" + m.x.toFixed(4);
$("osc-result").textContent = msg;
const os = $("osc-status");
if (os) os.textContent = "superposition · " + this.quantumMeasurements.length + " measured";
this.log("Quantum collapse: " + msg);
return m;
}
async buildFiles() {
const groupSize = parseInt($("cfg-group").value, 10) || 8;
const klPenalty = parseFloat($("cfg-kl").value) || 0.1;
const latentDim = parseInt($("cfg-latent").value, 10) || 32;
const filterThreshold = parseFloat($("cfg-filter").value) || 0.7;
const policyArch = $("cfg-arch").value || "tiny-bigram";
const transObj = {};
for (const [prev, b] of this.transitions) {
const inner = {};
for (const [k, v] of b) inner[k] = v;
transObj[prev] = inner;
}
const vocabList = [...this.vocabCounts.keys()];
const seed = this.kgSeed;
const modelJson = {
format: "micro-distill-bigram-v1",
version: "3.1.2",
arch: policyArch,
created: new Date().toISOString(),
bos_token: this.bos,
eos_token: this.eos,
vocab: vocabList,
transitions: transObj,
harmonic_seed: seed,
harmonic_frequencies: [1.0, 1.618033988749895, 2.0, 3.141592653589793],
config: {
grpo: { group_size: groupSize, kl_penalty: klPenalty },
vae: { latent_dim: latentDim, filter_threshold: filterThreshold },
policy_arch: policyArch
},
metrics: {
training_steps: this.metrics.trainingSteps,
final_loss: this.metrics.finalLoss,
average_reward: this.metrics.averageReward
}
};
const configJson = {
architectures: ["MicroDistilleryBigramForCausalLM"],
model_type: "custom",
custom_model_type: "micro-distillery-bigram",
vocab_size: Math.max(vocabList.length, 256),
max_position_embeddings: 1024,
hidden_size: 512,
num_hidden_layers: 0,
num_attention_heads: 0,
intermediate_size: 0,
activation_function: "gelu_new",
layer_norm_epsilon: 1e-5,
use_cache: true,
bos_token_id: 0,
eos_token_id: 1,
transformers_version: "4.44.0",
library_name: "micro-distillery",
micro_distillery: {
format: "bigram-distilled",
dialect: "character-level",
reference_arch: policyArch,
distill_from: ["smollm2-135m", "qwen2.5-0.5b", "olmo2-1b", "tinyllama-1.1b"],
harmonic_seed: seed,
harmonic_frequencies: modelJson.harmonic_frequencies,
grpo_config: modelJson.config.grpo,
vae_config: modelJson.config.vae
}
};
const vocabObj = {};
vocabList.forEach((t, i) => { vocabObj[t] = i; });
const tokenizerConfig = {
tokenizer_class: "PreTrainedTokenizerFast",
bos_token: "<|endoftext|>",
eos_token: "<|endoftext|>",
unk_token: "<|endoftext|>",
pad_token: "<|endoftext|>",
add_prefix_space: false,
model_max_length: 1024,
name_or_path: "micro-distillery"
};
const kgJson = this.graph.toJSON();
const logsJson = {
created: new Date().toISOString(),
metrics: this.metrics,
quantum_measurements: this.quantumMeasurements.map((m) => ({
oscillator: m.oscillator, energy: m.energy, x: m.x, t: m.t, probabilities: m.probabilities
})),
config: modelJson.config
};
const testPy = this.buildTestPy();
const testSh = this.buildTestSh();
const testReadme = this.buildTestReadme();
const runSh = this.buildRunSh();
const toParquetPy = this.buildToParquetPy();
const reqTxt = "# Core test.py requires only Python 3.8+ standard library.\n# Optional:\npandas>=2.0\npyarrow>=14.0\n";
const readmeMd = this.buildReadme(modelJson, vocabList.length);
const licenseTxt = "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\n"
+ "Licensed under the Apache License, Version 2.0 (the \"License\");\n"
+ "you may not use this file except in compliance with the License.\n"
+ "You may obtain a copy of the License at\n\n"
+ " http://www.apache.org/licenses/LICENSE-2.0\n\n"
+ "Unless required by applicable law or agreed to in writing, software\n"
+ "distributed under the License is distributed on an \"AS IS\" BASIS,\n"
+ "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"
+ "See the License for the specific language governing permissions and\n"
+ "limitations under the License.\n";
const gitattrs = "*.json text eol=lf\n*.py text eol=lf\n*.sh text eol=lf\n*.md text eol=lf\n*.svg text eol=lf\n";
const pipelineSvg = this.buildPipelineSvg();
const architectureSvg = this.buildArchitectureSvg();
this.modelFiles = [
{ name: "README.md", type: "docs", content: readmeMd, isBinary: false, group: "core" },
{ name: "LICENSE", type: "docs", content: licenseTxt, isBinary: false, group: "core" },
{ name: ".gitattributes", type: "config", content: gitattrs, isBinary: false, group: "core" },
{ name: "requirements.txt", type: "text", content: reqTxt, isBinary: false, group: "core" },
{ name: "config.json", type: "config", content: JSON.stringify(configJson, null, 2), isBinary: false, group: "config" },
{ name: "model.json", type: "model", content: JSON.stringify(modelJson, null, 2), isBinary: false, group: "model" },
{ name: "vocab.json", type: "vocab", content: JSON.stringify(vocabObj, null, 2), isBinary: false, group: "vocab" },
{ name: "tokenizer_config.json", type: "vocab", content: JSON.stringify(tokenizerConfig, null, 2), isBinary: false, group: "vocab" },
{ name: "training_logs.json", type: "logs", content: JSON.stringify(logsJson, null, 2), isBinary: false, group: "logs" },
{ name: "knowledge_graph.json", type: "kg", content: JSON.stringify(kgJson, null, 2), isBinary: false, group: "kg" },
{ name: "test.py", type: "python", content: testPy, isBinary: false, group: "tests" },
{ name: "test.sh", type: "shell", content: testSh, isBinary: false, group: "tests" },
{ name: "test_readme.md", type: "docs", content: testReadme, isBinary: false, group: "tests" },
{ name: "run.sh", type: "shell", content: runSh, isBinary: false, group: "tests" },
{ name: "to_parquet.py", type: "python", content: toParquetPy, isBinary: false, group: "parquet" },
{ name: "diagrams/pipeline.svg", type: "svg", content: pipelineSvg, isBinary: false, group: "diagrams" },
{ name: "diagrams/architecture.svg", type: "svg", content: architectureSvg, isBinary: false, group: "diagrams" }
];
return this.modelFiles;
}
buildTestPy() {
return [
'#!/usr/bin/env python3',
'"""',
'test.py - Micro Distillery model chat wrapper.',
'',
'Loads model.json (a GRPO+VAE-distilled bigram model) and provides',
'a simple REPL chat interface.',
'',
'Usage:',
' python3 test.py # interactive chat',
' python3 test.py hello # one-shot from prompt',
' python3 test.py --temperature 1.2 # one-shot with custom temperature',
' python3 test.py # (interactive also supports --temperature)',
'"""',
'import json',
'import os',
'import random',
'import sys',
'',
'MODEL_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "model.json")',
'',
'',
'def load_model(path=MODEL_PATH):',
' with open(path, "r", encoding="utf-8") as f:',
' return json.load(f)',
'',
'',
'def sample_next(transitions, token, rng, temperature=1.0):',
' bucket = transitions.get(token)',
' if not bucket:',
' return None',
' tokens = list(bucket.keys())',
' inv_t = 1.0 / max(temperature, 1e-3)',
' weights = [max(bucket[t], 1e-12) ** inv_t for t in tokens]',
' total = sum(weights)',
' if total <= 0:',
' return None',
' r = rng.random() * total',
' acc = 0.0',
' for t, w in zip(tokens, weights):',
' acc += w',
' if r <= acc:',
' return t',
' return tokens[-1]',
'',
'',
'def generate(model, prompt, max_tokens=32, temperature=0.9):',
' transitions = model.get("transitions", {})',
' bos = model.get("bos_token", "<|endoftext|>")',
' eos = model.get("eos_token", "<|endoftext|>")',
' seed = int(model.get("harmonic_seed", 1337)) & 0xFFFFFFFF',
' rng = random.Random(seed)',
'',
' if prompt and prompt.strip():',
' current = prompt.strip()[-1]',
' prefix = prompt.strip()',
' else:',
' current = bos',
' prefix = ""',
'',
' generated = ""',
' for _ in range(max_tokens):',
' nxt = sample_next(transitions, current, rng, temperature=temperature)',
' if nxt is None or nxt == eos:',
' break',
' generated += nxt',
' current = nxt',
' return prefix + generated',
'',
'',
'def main(temperature=0.9):',
' print("=== Micro Distillery Model Chat ===")',
' if not os.path.exists(MODEL_PATH):',
' print("Error: " + MODEL_PATH + " not found.", file=sys.stderr)',
' sys.exit(1)',
' model = load_model()',
' print("Format: " + str(model.get("format")))',
' print("Vocab size: " + str(len(model.get("vocab", []))))',
' print("Transitions: " + str(len(model.get("transitions", {}))))',
' print("Seed: " + str(model.get("harmonic_seed")))',
' print("Temperature: " + str(temperature))',
' print("Type /quit to exit.\\n")',
' while True:',
' try:',
' prompt = input("You: ").strip()',
' except (EOFError, KeyboardInterrupt):',
' print("\\nBye!")',
' break',
' if not prompt:',
' continue',
' if prompt.lower() in ("/quit", "/exit", "quit", "exit"):',
' break',
' out = generate(model, prompt, temperature=temperature)',
' print("Bot: " + out)',
'',
'',
'if __name__ == "__main__":',
' args = sys.argv[1:]',
' temperature = 0.9',
' prompts = []',
' i = 0',
' while i < len(args):',
' if args[i] == "--temperature":',
' if i + 1 < len(args):',
' try:',
' temperature = float(args[i + 1])',
' except ValueError:',
' print("Warning: invalid --temperature value, using 0.9", file=sys.stderr)',
' i += 2',
' else:',
' print("Warning: --temperature requires a value, using 0.9", file=sys.stderr)',
' i += 1',
' elif args[i] in ("--help", "-h"):',
' print("Usage: python3 test.py [prompt...] [--temperature T]")',
' sys.exit(0)',
' elif args[i].startswith("--"):',
' i += 1',
' else:',
' prompts.append(args[i])',
' i += 1',
' if prompts:',
' model = load_model()',
' print(generate(model, " ".join(prompts), temperature=temperature))',
' else:',
' main(temperature=temperature)',
''
].join("\n");
}
buildTestSh() {
return [
'#!/usr/bin/env bash',
'# test.sh - Test harness for the exported Micro Distillery model.',
'set -euo pipefail',
'cd "$(dirname "$0")"',
'',
'PY="${PYTHON:-python3}"',
'',
'echo "================================================"',
'echo " Micro Distillery - Model Test Harness"',
'echo "================================================"',
'echo ""',
'',
'if ! command -v "$PY" >/dev/null 2>&1; then',
' echo "ERROR: $PY not found. Install Python 3.8+." >&2',
' echo " macOS: brew install python3" >&2',
' echo " Ubuntu: sudo apt install python3" >&2',
' echo " Windows: https://python.org/downloads/" >&2',
' exit 1',
'fi',
'',
'echo "[1/3] Python: $($PY --version 2>&1)"',
'',
'if [ ! -f model.json ]; then',
' echo "ERROR: model.json not found in $(pwd)" >&2',
' exit 1',
'fi',
'echo "[2/3] Found model.json"',
'',
'$PY -c "',
'import json',
'm = json.load(open(\'model.json\'))',
'assert \'transitions\' in m, \'missing transitions\'',
'assert \'vocab\' in m, \'missing vocab\'',
'print(\'OK: format=%s vocab=%d transitions=%d\' % (m.get(\'format\',\'?\'), len(m[\'vocab\']), len(m[\'transitions\'])))',
'"',
'echo "[3/3] Model validation passed"',
'echo ""',
'echo "=== Sample Generations ==="',
'for prompt in alice bob zoe liam; do',
' echo -n " $prompt -> "',
' $PY test.py "$prompt" 2>/dev/null | tail -1 || echo "(failed)"',
'done',
'echo ""',
'echo "=== Interactive Chat (type /quit to exit) ==="',
'exec $PY test.py',
''
].join("\n");
}
buildTestReadme() {
return [
'# Testing Your Exported Model',
'',
'This bundle contains a fully runnable bigram language model. No GPU,',
'no external dependencies - Python 3.8+ standard library only.',
'',
'## Files',
'',
'| File | Purpose |',
'|------|---------|',
'| `model.json` | The trained model (transitions + config + metrics) |',
'| `test.py` | Interactive chat wrapper |',
'| `test.sh` | Test harness (validates + generates samples) |',
'| `run.sh` | Minimal launcher for test.py |',
'',
'## Quick Start',
'',
'### macOS / Linux',
'',
'```bash',
'chmod +x test.sh run.sh',
'./test.sh',
'```',
'',
'### Windows (PowerShell)',
'',
'```powershell',
'python test.py',
'```',
'',
'### One-shot generation',
'',
'```bash',
'python3 test.py alice',
'# => alice… (seed-dependent continuation)',
'```',
'',
'### Custom temperature',
'',
'```bash',
'python3 test.py --temperature 0.5',
'python3 test.py alice --temperature 1.2',
'```',
'',
'## Manual Test',
'',
'```bash',
'python3 -c "import json; m=json.load(open(\'model.json\')); print(len(m[\'vocab\']), \'tokens,\', len(m[\'transitions\']), \'states\')"',
'```',
'',
'Expected output: `<N> tokens, <M> states`.',
'',
'## Troubleshooting',
'',
'- **`python3 not found`** - install Python 3.8+ from https://python.org',
'- **`model.json not found`** - make sure you are inside the extracted directory',
'- **`Permission denied`** - run `chmod +x test.sh run.sh` first',
''
].join("\n");
}
buildRunSh() {
return [
'#!/usr/bin/env bash',
'# run.sh - Minimal launcher for test.py.',
'set -euo pipefail',
'cd "$(dirname "$0")"',
'PY="${PYTHON:-python3}"',
'if ! command -v "$PY" >/dev/null 2>&1; then',
' echo "Error: python3 not found." >&2',
' exit 1',
'fi',
'exec "$PY" test.py "$@"',
''
].join("\n");
}
buildToParquetPy() {
return [
'#!/usr/bin/env python3',
'"""Convert model.json to model.parquet.',
'',
'Requires: pandas, pyarrow',
' pip install pandas pyarrow',
'"""',
'import json',
'import os',
'import sys',
'',
'MODEL_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "model.json")',
'OUT_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "model.parquet")',
'',
'',
'def main():',
' try:',
' import pandas as pd',
' except ImportError:',
' print("Error: pandas required. Install: pip install pandas pyarrow", file=sys.stderr)',
' sys.exit(1)',
'',
' with open(MODEL_PATH, "r", encoding="utf-8") as f:',
' model = json.load(f)',
'',
' rows = []',
' for state, bucket in model.get("transitions", {}).items():',
' for nxt, prob in bucket.items():',
' rows.append({',
' "state": state,',
' "next_token": nxt,',
' "probability": float(prob),',
' "harmonic_seed": int(model.get("harmonic_seed", 0)),',
' "format": model.get("format", "unknown"),',
' "version": model.get("version", "0.0"),',
' })',
'',
' if not rows:',
' print("Warning: no transitions. Writing metadata-only parquet.")',
' rows = [{"state": "", "next_token": "", "probability": 0.0,',
' "harmonic_seed": int(model.get("harmonic_seed", 0)),',
' "format": model.get("format", "unknown"),',
' "version": model.get("version", "0.0")}]',
'',
' df = pd.DataFrame(rows)',
' df.to_parquet(OUT_PATH, index=False)',
' print("Wrote " + str(len(df)) + " rows to " + OUT_PATH)',
'',
'',
'if __name__ == "__main__":',
' main()',
''
].join("\n");
}
buildReadme(modelJson, vocabSize) {
const transCount = Object.keys(modelJson.transitions).length;
return [
'---',
'language:',
' - en',
'license: apache-2.0',
'tags:',
' - micro-distillery',
' - bigram',
' - grpo',
' - vae',
' - distilled',
' - smollm2',
' - qwen2.5',
' - olmo',
' - tinyllama',
'library_name: micro-distillery',
'---',
'',
'# Micro Distillery — Bigram Distilled Model',
'',
'This model was trained from scratch using a combination of:',
'',
'- **GRPO** (Group Relative Policy Optimization) for character-level trajectory training',
'- **VAE filtering** to keep only high-quality trajectories',
'- **4x quantum harmonic oscillator** superposition for deterministic seeding',
'',
'It is a compact character-level bigram model - no transformer weights,',
'just transition probabilities. Runs on any machine with Python 3.8+.',
'',
'## Reference Architectures',
'',
'The bundle metadata references these modern (2025-2026) open models as',
'inspiration sources. The actual model is a distilled bigram - not a',
'transformer checkpoint - and does not load via `AutoModelForCausalLM`.',
'',
'- [SmolLM2-135M](https://huggingface.co/HuggingFaceTB/SmolLM2-135M) (HuggingFaceTB)',
'- [Qwen2.5-0.5B](https://huggingface.co/Qwen/Qwen2.5-0.5B) (Qwen)',
'- [OLMo-2-1B](https://huggingface.co/allenai/OLMo-2-0425-1B) (AllenAI)',
'- [TinyLlama-1.1B](https://huggingface.co/TinyLlama/TinyLlama-1.1B-Chat-v1.0) (TinyLlama)',
'',
'## Model Details',
'',
'- **Format**: ' + modelJson.format,
'- **Version**: ' + modelJson.version,
'- **Reference arch**: ' + modelJson.arch,
'- **Vocab size**: ' + vocabSize + ' tokens',
'- **Transition states**: ' + transCount,
'- **Harmonic seed**: ' + modelJson.harmonic_seed,
'- **Created**: ' + modelJson.created,
'',
'## Quick Start',
'',
'```bash',
'# 1. Extract the bundle',
'unzip micro-distillery-*.zip',
'cd micro-distillery-*',
'',
'# 2. Run the test harness',
'chmod +x test.sh run.sh',
'./test.sh',
'',
'# 3. Or chat directly',
'python3 test.py',
'```',
'',
'## Files in the Bundle',
'',
'| File | Description |',
'|------|-------------|',
'| `model.json` | The trained bigram model (all weights) |',
'| `config.json` | HuggingFace-style metadata config |',
'| `vocab.json` | Character vocabulary |',
'| `tokenizer_config.json` | Tokenizer metadata |',
'| `knowledge_graph.json` | 2D graph used to seed the model |',
'| `training_logs.json` | Metrics + quantum measurements |',
'| `test.py` | Interactive chat wrapper |',
'| `test.sh` | Test harness |',
'| `test_readme.md` | Testing instructions |',
'| `run.sh` | Minimal launcher |',
'| `to_parquet.py` | Convert to parquet |',
'| `diagrams/pipeline.svg` | Training pipeline diagram |',
'| `diagrams/architecture.svg` | Model architecture diagram |',
'',
'## Training Pipeline',
'',
'1. **Fetch** - pulls corpora and model cards from HuggingFace + GitHub (2026 open sources)',
'2. **Knowledge graph** - builds a character-transition graph, laid out in 2D',
'3. **Seed** - initializes bigram transition counts from the corpus',
'4. **GRPO** - samples trajectories, scores with a name heuristic, boosts high-advantage transitions',
'5. **VAE filter** - retains only high-probability trajectories',
'6. **Quantum seed** - 4 harmonic oscillators (w = 1, phi, 2, pi) collapse to a deterministic seed',
'7. **Export** - produces a HuggingFace-ready .zip bundle',
'',
'## Uploading to Hugging Face',
'',
'The bundle is structured so you can upload it directly to a HF repo:',
'',
'```bash',
'pip install huggingface_hub',
'huggingface-cli login',
'huggingface-cli upload your-username/micro-distillery-bigram . --repo-type model',
'```',
'',
'The `model.json` file is not a transformer checkpoint - use `test.py` to run it.',
'`config.json` is provided for HF metadata and Datasets viewer compatibility.',
'',
'## Data Sources',
'',
'This bundle was produced by fetching from these free, open endpoints:',
'',
'- `raw.githubusercontent.com/karpathy/makemore/master/names.txt` (training corpus)',
'- `huggingface.co/api/models?search=smollm2` (model reference list)',
'- `huggingface.co/api/models?search=qwen2.5-0.5b` (model reference list)',
'- `huggingface.co/HuggingFaceTB/SmolLM2-135M/raw/main/README.md` (model card)',
'- `huggingface.co/allenai/OLMo-2-0425-1B/raw/main/README.md` (model card)',
'- `api.github.com/repos/karpathy/makemore` (repo metadata)',
'',
'## License',
'',
'Apache 2.0',
''
].join("\n");
}
buildPipelineSvg() {
return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 220" font-family="monospace">\n'
+ '<rect width="900" height="220" fill="#000"/>\n'
+ '<text x="20" y="30" fill="#e5e5e5" font-size="14" font-weight="bold">Micro Distillery - Training Pipeline</text>\n'
+ '<g stroke="#e5e5e5" stroke-width="1.5" fill="#0a0a0a">\n'
+ ' <rect x="20" y="70" width="140" height="70" rx="4"/>\n'
+ ' <rect x="200" y="70" width="140" height="70" rx="4"/>\n'
+ ' <rect x="380" y="70" width="140" height="70" rx="4"/>\n'
+ ' <rect x="560" y="70" width="140" height="70" rx="4"/>\n'
+ ' <rect x="740" y="70" width="140" height="70" rx="4"/>\n'
+ '</g>\n'
+ '<g fill="#e5e5e5" font-size="12" text-anchor="middle">\n'
+ ' <text x="90" y="100">1. Fetch</text>\n'
+ ' <text x="270" y="100">2. Bigram</text>\n'
+ ' <text x="450" y="100">3. GRPO</text>\n'
+ ' <text x="630" y="100">4. VAE</text>\n'
+ ' <text x="810" y="100">5. Export</text>\n'
+ '</g>\n'
+ '<g fill="#888" font-size="10" text-anchor="middle">\n'
+ ' <text x="90" y="122">HF + GitHub</text>\n'
+ ' <text x="270" y="122">counts</text>\n'
+ ' <text x="450" y="122">policy</text>\n'
+ ' <text x="630" y="122">filter</text>\n'
+ ' <text x="810" y="122">.zip</text>\n'
+ '</g>\n'
+ '<g stroke="#888" stroke-width="1.5" fill="none">\n'
+ ' <path d="M160 105 L200 105" marker-end="url(#a)"/>\n'
+ ' <path d="M340 105 L380 105" marker-end="url(#a)"/>\n'
+ ' <path d="M520 105 L560 105" marker-end="url(#a)"/>\n'
+ ' <path d="M700 105 L740 105" marker-end="url(#a)"/>\n'
+ '</g>\n'
+ '<defs><marker id="a" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#888"/></marker></defs>\n'
+ '<text x="450" y="185" fill="#666" font-size="11" text-anchor="middle">4x harmonic oscillator (w=1, phi, 2, pi) provides deterministic seed</text>\n'
+ '</svg>';
}
buildArchitectureSvg() {
return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 300" font-family="monospace">\n'
+ '<rect width="900" height="300" fill="#000"/>\n'
+ '<text x="20" y="30" fill="#e5e5e5" font-size="14" font-weight="bold">Micro Distillery - Model Architecture</text>\n'
+ '<text x="20" y="55" fill="#888" font-size="11">Bigram transition table + quantum-seeded decoding</text>\n'
+ '<g fill="none" stroke="#e5e5e5" stroke-width="1.5">\n'
+ ' <rect x="20" y="90" width="200" height="60" rx="4"/>\n'
+ ' <rect x="20" y="180" width="200" height="60" rx="4"/>\n'
+ ' <rect x="280" y="130" width="220" height="80" rx="4"/>\n'
+ ' <rect x="560" y="90" width="180" height="60" rx="4"/>\n'
+ ' <rect x="560" y="180" width="180" height="60" rx="4"/>\n'
+ ' <rect x="790" y="130" width="90" height="80" rx="4"/>\n'
+ '</g>\n'
+ '<g fill="#e5e5e5" font-size="12" text-anchor="middle">\n'
+ ' <text x="120" y="120">Input prompt</text>\n'
+ ' <text x="120" y="210">Last character</text>\n'
+ ' <text x="390" y="160">Transitions map</text>\n'
+ ' <text x="390" y="180" fill="#888" font-size="10">state to {next: prob}</text>\n'
+ ' <text x="650" y="120">Sample</text>\n'
+ ' <text x="650" y="210">RNG (seeded)</text>\n'
+ ' <text x="835" y="160">Output</text>\n'
+ '</g>\n'
+ '<g stroke="#888" stroke-width="1.5" fill="none">\n'
+ ' <path d="M220 120 L280 145" marker-end="url(#b)"/>\n'
+ ' <path d="M220 210 L280 195" marker-end="url(#b)"/>\n'
+ ' <path d="M500 150 L560 130" marker-end="url(#b)"/>\n'
+ ' <path d="M500 190 L560 210" marker-end="url(#b)"/>\n'
+ ' <path d="M740 120 L790 145" marker-end="url(#b)"/>\n'
+ ' <path d="M740 210 L790 195" marker-end="url(#b)"/>\n'
+ '</g>\n'
+ '<defs><marker id="b" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#888"/></marker></defs>\n'
+ '<text x="450" y="280" fill="#666" font-size="10" text-anchor="middle">Deterministic given seed - output reproducible across machines</text>\n'
+ '</svg>';
}
async showExportModal() {
if (!this.trainDone) {
this.log("Warning: export without training. Bundle will use current transitions.", "warn");
}
await this.buildFiles();
const filtered = this.applyExportOptions();
$("export-modal").classList.remove("hidden");
requestAnimationFrame(() => $("export-modal").classList.add("show"));
this.renderFileList(filtered);
this.renderExportSize(filtered);
}
hideExportModal() {
$("export-modal").classList.remove("show");
setTimeout(() => $("export-modal").classList.add("hidden"), 200);
}
applyExportOptions() {
const opts = {
config: $("opt-config").checked,
vocab: $("opt-vocab").checked,
logs: $("opt-logs").checked,
kg: $("opt-kg").checked,
diagrams: $("opt-diagrams").checked,
parquet: $("opt-parquet").checked
};
return this.modelFiles.filter((f) => {
if (f.group === "core") return true;
if (f.group === "tests") return true;
if (f.group === "config" && !opts.config) return false;
if (f.group === "vocab" && !opts.vocab) return false;
if (f.group === "logs" && !opts.logs) return false;
if (f.group === "kg" && !opts.kg) return false;
if (f.group === "diagrams" && !opts.diagrams) return false;
if (f.group === "parquet" && !opts.parquet) return false;
return true;
});
}
renderFileList(files) {
const list = $("file-list");
list.innerHTML = "";
files.forEach((f, i) => {
const row = document.createElement("div");
row.className = "file-row" + (i === this.selectedPreviewIndex ? " selected" : "");
const size = f.content ? f.content.length : 0;
row.innerHTML = '<span class="file-row-name">' + escapeHtml(f.name) + '</span>' +
'<span class="file-row-size">' + humanSize(size) + '</span>';
row.addEventListener("click", () => {
this.selectedPreviewIndex = i;
this.renderFileList(files);
this.renderPreview(files, i);
});
list.appendChild(row);
});
if (files.length > 0) {
if (this.selectedPreviewIndex >= files.length) this.selectedPreviewIndex = 0;
this.renderPreview(files, this.selectedPreviewIndex);
} else {
$("file-preview").textContent = "No files selected.";
}
}
renderPreview(files, idx) {
const f = files[idx];
if (!f) return;
let content = f.content || "";
if (content.length > 8000) content = content.substring(0, 8000) + "\n\n... (truncated preview)";
$("file-preview").textContent = content;
}
renderExportSize(files) {
const total = files.reduce((s, f) => s + (f.content ? f.content.length : 0), 0);
$("export-size").textContent = files.length + " files " + humanSize(total);
}
async downloadBundle() {
const files = this.applyExportOptions();
if (files.length === 0) { this.log("No files selected.", "err"); return; }
if (typeof JSZip === "undefined") {
this.log("JSZip library not available (CDN blocked?). Cannot build .zip.", "err");
return;
}
if (typeof saveAs === "undefined") {
this.log("FileSaver library not available (CDN blocked?). Cannot download.", "err");
return;
}
this.log("Building .zip bundle...");
$("btn-download").disabled = true;
try {
const zip = new JSZip();
for (const f of files) zip.file(f.name, f.content);
const blob = await zip.generateAsync({ type: "blob", compression: "DEFLATE", compressionOptions: { level: 6 } });
const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
const name = "micro-distillery-" + ts + ".zip";
saveAs(blob, name);
this.log("Bundle downloaded: " + name + " (" + humanSize(blob.size) + ")", "ok");
try { await this.storage.saveModel("latest", { timestamp: Date.now(), files: files.length }); } catch (e) { /* ignored */ }
setTimeout(() => this.hideExportModal(), 800);
} catch (e) {
this.log("Export failed: " + e.message, "err");
} finally {
$("btn-download").disabled = false;
}
}
reset() {
this.fetchDone = false;
this.trainDone = false;
this.fetching = false;
this.training = false;
this.transitions = new Map();
this.vocabCounts = new Map();
this.graph = new KnowledgeGraph();
this.kgSeed = 42;
this.quantumMeasurements = [];
this.corpus = FALLBACK_NAMES.slice();
this.cache = new Map();
this.metrics = { trainingSteps: 0, finalLoss: 0, averageReward: 0, cacheHits: 0, cacheMisses: 0, filteredCount: 0, totalSamples: 0, vaeLoss: 0 };
this.superposition = new Superposition4(1337);
this.apiFanOut = new APIFanOut((m, c) => this.apiLog(m, c));
this.lastCollapse = null;
this.selectedPreviewIndex = 0;
this.modelFiles = [];
this.markStep("fetch", "active", "");
this.markStep("train", "", "");
this.markStep("export", "", "");
this.updateMetrics();
this.renderGraph();
this.setProgress(0, "idle");
this.setProgressLabel("idle");
this.updateOscDisplay();
$("osc-result").textContent = "";
const os = $("osc-status");
if (os) os.textContent = "superposition";
this.log("System reset.");
}
}
class Wizard {
constructor(engine) {
this.engine = engine;
this.step = 0;
this.steps = ["welcome", "fetch", "train", "export", "done"];
}
open() {
this.step = 0;
this.render();
$("wizard").classList.remove("hidden");
requestAnimationFrame(() => $("wizard").classList.add("show"));
}
close() {
$("wizard").classList.remove("show");
setTimeout(() => $("wizard").classList.add("hidden"), 200);
}
progressDots() {
return this.steps.map((_, i) => {
let cls = "wiz-dot";
if (i < this.step) cls += " done";
else if (i === this.step) cls += " active";
return '<div class="' + cls + '"></div>';
}).join("");
}
render() {
const id = this.steps[this.step];
$("wiz-progress").innerHTML = this.progressDots();
const content = {
welcome: {
title: "Micro Distillery",
sub: "Build a small language model in 3 automated steps.",
body: [
"<p>This tool builds a runnable character-level language model from free open data (HuggingFace + GitHub, 2026 sources). No installation needed.</p>",
"<p>You will:</p>",
"<ul>",
"<li><strong>Fetch</strong> a training corpus from open endpoints (SmolLM2, Qwen2.5, OLMo-2 cards + Karpathy's makemore corpus)</li>",
"<li><strong>Train</strong> the model with GRPO + VAE filtering</li>",
"<li><strong>Export</strong> a HuggingFace-ready .zip bundle</li>",
"</ul>",
'<div class="wiz-info">',
"<strong>Time:</strong> ~30 seconds total. <strong>Cost:</strong> free. <strong>Data:</strong> only from public sources.",
"</div>"
],
next: "Begin",
back: null
},
fetch: {
title: "Step 1 - Fetch Data",
sub: "Pull a training corpus from free, open endpoints.",
body: [
"<p>We will fetch:</p>",
"<ul>",
"<li>Character corpus from <code>raw.githubusercontent.com/karpathy/makemore</code></li>",
"<li>Model metadata from <code>huggingface.co/api/models?search=smollm2</code> (SmolLM2)</li>",
"<li>Model metadata from <code>huggingface.co/api/models?search=qwen2.5-0.5b</code> (Qwen2.5)</li>",
"<li>Reference cards from SmolLM2-135M and OLMo-2-1B</li>",
"</ul>",
"<p>If the network is unavailable, a bundled fallback corpus (200+ names) is used instead.</p>",
'<div class="wiz-info">',
'<strong>Status:</strong> <span id="wiz-fetch-status">not started</span>',
"</div>",
'<button class="btn btn-primary btn-lg btn-block" id="wiz-run-fetch">Fetch Data</button>'
],
next: "Continue",
back: null
},
train: {
title: "Step 2 - Train Model",
sub: "Run 60 GRPO steps with VAE filtering.",
body: [
"<p>Training optimizes a character bigram model using:</p>",
"<ul>",
"<li><strong>GRPO</strong> - group-relative policy optimization on sampled trajectories</li>",
"<li><strong>VAE filter</strong> - keeps only high-probability trajectories</li>",
"</ul>",
'<div class="wiz-info">',
'<strong>Status:</strong> <span id="wiz-train-status">waiting for data</span>',
"</div>",
'<button class="btn btn-primary btn-lg btn-block" id="wiz-run-train">Train Model</button>'
],
next: "Continue",
back: null
},
export: {
title: "Step 3 - Export Bundle",
sub: "Download a HuggingFace-ready .zip.",
body: [
"<p>The bundle includes:</p>",
"<ul>",
"<li><code>model.json</code>, <code>config.json</code>, <code>vocab.json</code></li>",
"<li><code>README.md</code> model card (HF-formatted with YAML frontmatter)</li>",
"<li><code>test.py</code>, <code>test.sh</code>, <code>test_readme.md</code></li>",
"<li><code>diagrams/pipeline.svg</code>, <code>diagrams/architecture.svg</code></li>",
"<li><code>to_parquet.py</code> for parquet conversion</li>",
"<li><code>LICENSE</code>, <code>.gitattributes</code>, <code>requirements.txt</code></li>",
"</ul>",
'<div class="wiz-info">',
'<strong>Status:</strong> <span id="wiz-export-status">ready</span>',
"</div>",
'<button class="btn btn-primary btn-lg btn-block" id="wiz-run-export">Open Export Panel</button>'
],
next: "Done",
back: null
},
done: {
title: "Complete",
sub: "Your bundle has been downloaded.",
body: [
"<p>Next steps:</p>",
"<ul>",
"<li>Unzip the bundle</li>",
"<li>Run <code>./test.sh</code> to validate and chat</li>",
"<li>Upload the folder to HuggingFace via <code>huggingface-cli upload</code></li>",
"<li>Or run <code>python3 to_parquet.py</code> to convert to parquet</li>",
"</ul>",
'<div class="wiz-info">',
"<strong>Tip:</strong> Click <em>Open Workspace</em> for advanced controls (graph, oscillator, custom settings).",
"</div>"
],
next: "Open Workspace",
back: null
}
}[id];
$("wiz-title").textContent = content.title;
$("wiz-sub").textContent = content.sub;
$("wiz-content").innerHTML = content.body.join("");
$("wiz-back").style.visibility = this.step === 0 ? "hidden" : "visible";
$("wiz-next").textContent = content.next;
if (id === "welcome" || id === "done") {
$("wiz-skip").textContent = id === "done" ? "Close" : "Skip wizard";
} else {
$("wiz-skip").textContent = "Skip wizard";
}
if (id === "fetch") {
const btn = $("wiz-run-fetch");
if (this.engine.fetching && btn) btn.disabled = true;
if (btn) btn.addEventListener("click", async () => {
if (this.engine.fetching) {
$("wiz-fetch-status").textContent = "fetch already in progress";
return;
}
btn.disabled = true;
$("wiz-fetch-status").textContent = "fetching...";
const ok = await this.engine.fetch();
$("wiz-fetch-status").textContent = ok ? "complete - " + this.engine.corpus.length + " lines" : "failed";
btn.disabled = false;
if (ok) setTimeout(() => this.next(), 500);
});
}
if (id === "train") {
const btn = $("wiz-run-train");
if (!this.engine.fetchDone) {
$("wiz-train-status").textContent = "data required - go back";
if (btn) btn.disabled = true;
}
if (btn) btn.addEventListener("click", async () => {
btn.disabled = true;
$("wiz-train-status").textContent = "training...";
const ok = await this.engine.train();
$("wiz-train-status").textContent = ok ? "complete - loss " + this.engine.metrics.finalLoss.toFixed(4) : "failed";
btn.disabled = false;
if (ok) setTimeout(() => this.next(), 500);
});
}
if (id === "export") {
const btn = $("wiz-run-export");
if (!this.engine.trainDone) {
$("wiz-export-status").textContent = "training required - go back";
if (btn) btn.disabled = true;
}
if (btn) btn.addEventListener("click", async () => {
btn.disabled = true;
$("wiz-export-status").textContent = "opening export panel...";
await this.engine.showExportModal();
$("wiz-export-status").textContent = "ready - configure options in the export panel";
btn.disabled = false;
});
}
}
next() {
if (this.step === this.steps.length - 1) { this.close(); return; }
if (this.steps[this.step] === "fetch" && !this.engine.fetchDone) {
this.engine.log("Fetch data before continuing.", "warn");
return;
}
if (this.steps[this.step] === "train" && !this.engine.trainDone) {
this.engine.log("Train the model before continuing.", "warn");
return;
}
this.step++;
this.render();
}
prev() { if (this.step > 0) { this.step--; this.render(); } }
}
let engine;
let wizard;
function setupTabs() {
document.querySelectorAll(".tab").forEach((tab) => {
tab.addEventListener("click", () => {
const id = tab.dataset.tab;
document.querySelectorAll(".tab").forEach((t) => t.classList.remove("active"));
document.querySelectorAll(".tabpanel").forEach((p) => p.classList.remove("active"));
tab.classList.add("active");
const panel = document.querySelector('[data-tabpanel="' + id + '"]');
if (panel) panel.classList.add("active");
if (id === "graph") engine.renderGraph();
});
});
}
function setupSliders() {
const bindings = [
["cfg-group", "val-group", 0],
["cfg-kl", "val-kl", 3],
["cfg-latent", "val-latent", 0],
["cfg-filter", "val-filter", 2]
];
for (const [id, display, decimals] of bindings) {
const sl = $(id);
const vd = $(display);
if (!sl || !vd) continue;
sl.addEventListener("input", () => {
vd.textContent = parseFloat(sl.value).toFixed(decimals);
});
}
}
function setupCloseButtons() {
document.querySelectorAll("[data-close]").forEach((btn) => {
btn.addEventListener("click", () => {
const id = btn.dataset.close;
if (id === "export-modal") engine.hideExportModal();
});
});
document.querySelectorAll(".modal").forEach((modal) => {
modal.addEventListener("click", (e) => {
if (e.target === modal) {
if (modal.id === "export-modal") engine.hideExportModal();
if (modal.id === "wizard") wizard.close();
}
});
});
}
function setupExportOptions() {
// Keep the file list, preview, and total size in sync with checkbox state.
// No-op when the bundle hasn't been built yet (modelFiles empty).
const boxes = document.querySelectorAll(".opt input[type='checkbox']");
boxes.forEach((cb) => {
cb.addEventListener("change", () => {
if (!engine || !engine.modelFiles || engine.modelFiles.length === 0) return;
const filtered = engine.applyExportOptions();
engine.renderFileList(filtered);
engine.renderExportSize(filtered);
});
});
}
function setupButtons() {
$("btn-fetch").addEventListener("click", async () => {
$("btn-fetch").disabled = true;
await engine.fetch();
$("btn-fetch").disabled = false;
});
$("btn-train").addEventListener("click", async () => {
$("btn-train").disabled = true;
await engine.train();
$("btn-train").disabled = false;
});
$("btn-stop").addEventListener("click", () => engine.stop());
$("btn-export").addEventListener("click", () => engine.showExportModal());
$("btn-export-2").addEventListener("click", () => engine.showExportModal());
$("btn-wizard").addEventListener("click", () => wizard.open());
$("btn-reset").addEventListener("click", () => {
if (confirm("Reset all state? This clears the model and metrics.")) engine.reset();
});
$("btn-download").addEventListener("click", () => engine.downloadBundle());
$("btn-graph-render").addEventListener("click", () => {
engine.graph.layout2D(engine.kgSeed);
engine.renderGraph();
});
$("btn-graph-save").addEventListener("click", async () => {
try { await engine.storage.saveGraph(engine.graph); engine.log("Graph saved to IndexedDB.", "ok"); }
catch (e) { engine.log("Save failed: " + e.message, "err"); }
});
$("btn-graph-load").addEventListener("click", async () => {
try { engine.graph = await engine.storage.loadGraph(); engine.renderGraph(); engine.log("Graph loaded.", "ok"); }
catch (e) { engine.log("Load failed: " + e.message, "err"); }
});
$("btn-graph-clear").addEventListener("click", async () => {
try { await engine.storage.clearGraph(); engine.graph = new KnowledgeGraph(); engine.renderGraph(); engine.log("Graph cleared.", "ok"); }
catch (e) { engine.log("Clear failed: " + e.message, "err"); }
});
$("btn-osc-measure").addEventListener("click", () => engine.measureOsc());
$("wiz-next").addEventListener("click", () => wizard.next());
$("wiz-back").addEventListener("click", () => wizard.prev());
$("wiz-skip").addEventListener("click", () => wizard.close());
}
function boot() {
engine = new Engine();
wizard = new Wizard(engine);
engine.log("Micro Distillery v3.1.2 initialized.");
engine.log("Data sources (2026): HuggingFace + GitHub");
engine.log("Click Setup Wizard in the header, or use the pipeline.");
engine.log("Ready.");
window.addEventListener("error", (e) => {
try { engine.log("Unhandled: " + (e.message || e.error || e), "err"); } catch (_) {}
});
window.addEventListener("unhandledrejection", (e) => {
const r = e && e.reason;
const m = r && r.message ? r.message : String(r);
try { engine.log("Unhandled rejection: " + m, "err"); } catch (_) {}
});
setupTabs();
setupSliders();
setupCloseButtons();
setupExportOptions();
setupButtons();
engine.updateMetrics();
engine.updateOscDisplay();
engine.renderGraph();
engine.setProgress(0, "idle");
let seen = null;
try { seen = localStorage.getItem("md_wizard_seen"); } catch (_) {}
if (!seen) {
setTimeout(() => {
try { wizard.open(); } catch (_) {}
try { localStorage.setItem("md_wizard_seen", "1"); } catch (_) {}
}, 400);
}
const offlineBar = $("offline-bar");
const updateOnline = () => offlineBar.classList.toggle("hidden", navigator.onLine);
window.addEventListener("online", updateOnline);
window.addEventListener("offline", updateOnline);
updateOnline();
window.addEventListener("resize", () => engine.renderGraph());
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("sw.js").catch(() => { /* expected when sw.js absent */ });
}
}
window.addEventListener("load", boot);
</script>
</body>
</html>