mlx-bun 0.0.10 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -32
- package/THIRD_PARTY_LICENSES.md +10 -0
- package/docs/archive/mlx-bun-lab-report.html +275 -0
- package/docs/archive/wwdc-mlx-bun.html +848 -0
- package/docs/design/audio-input-plan.md +356 -0
- package/docs/design/batching-perf-path.md +58 -35
- package/docs/design/batching-v2-plan.md +23 -9
- package/docs/design/bucketing-stage.md +2 -1
- package/docs/design/colibri-glm52-port.md +1479 -0
- package/docs/design/compat-cli-surface-design.md +4 -3
- package/docs/design/decode-speed-program.md +172 -0
- package/docs/design/docs-reorg-plan.md +1 -1
- package/docs/design/dspark-serving-program.md +371 -0
- package/docs/design/dspark-speculative-decoding.md +167 -5
- package/docs/design/faithful-l1-consolidation.md +310 -0
- package/docs/design/generic-model-support.md +8 -5
- package/docs/design/grammar-spec-batching-integration.md +301 -0
- package/docs/design/minicpm5-decode-megakernel.md +16 -11
- package/docs/design/mlx-lm-tool-parity-plan.md +11 -1
- package/docs/design/omlx-adoption-map.md +14 -6
- package/docs/design/orpo-training.md +34 -30
- package/docs/design/paged-kv-cache.md +214 -0
- package/docs/design/parallel-slots.md +82 -49
- package/docs/design/parity-tier-dag.md +43 -33
- package/docs/design/pre-colibri-stabilization.md +329 -0
- package/docs/design/repo-cleanup-plan.md +189 -0
- package/docs/design/runtime-isolation.md +19 -3
- package/docs/design/segmented-backward-training.md +3 -2
- package/docs/design/ssd-kv-cold-tier.md +130 -0
- package/docs/design/structured-output.md +484 -0
- package/docs/design/the-dreaming-master-plan.md +2 -3
- package/docs/design/turboquant-kv.md +235 -0
- package/docs/design/unified-engine-frontier-plan.md +774 -0
- package/docs/design/web-chat-beat-matrix.md +315 -0
- package/docs/design/web-chat-redesign.md +1112 -0
- package/docs/investigations/colibri-oracle-pin.md +1284 -0
- package/docs/investigations/dspark-handoff.md +182 -16
- package/docs/investigations/indexshare-performance-spike.md +357 -0
- package/docs/investigations/lab-build-journal.md +2 -2
- package/docs/investigations/openwiki-evaluation.md +249 -0
- package/docs/planning/IDEAS.md +14 -0
- package/docs/planning/PRODUCT_ROADMAP.md +2 -3
- package/docs/planning/chat-ui-vision-memory-first.md +199 -0
- package/docs/planning/chat-ui-vision.md +50 -0
- package/docs/planning/memory-docs-and-dag-plan.md +15 -6
- package/docs/planning/release-notes-v0.0.11.md +239 -0
- package/docs/planning/release-notes-v0.0.12.md +80 -0
- package/docs/reference/cli.md +63 -18
- package/docs/reference/distribution.md +37 -6
- package/docs/reference/embedding.md +40 -16
- package/docs/reference/features-matrix.md +99 -0
- package/docs/reference/library-api.md +69 -20
- package/docs/reference/memory.md +26 -6
- package/docs/reference/models.md +60 -3
- package/docs/reference/orpo-quickstart.md +3 -4
- package/docs/reference/server-api.md +717 -24
- package/docs/reference/server-config.md +265 -129
- package/docs/reference/training.md +24 -16
- package/package.json +5 -3
- package/src/anthropic.ts +67 -44
- package/src/audio/conformer.ts +647 -0
- package/src/audio/decode.ts +133 -0
- package/src/audio/features.ts +202 -0
- package/src/audio/transcode.ts +67 -0
- package/src/chat-template.ts +155 -10
- package/src/cli.ts +483 -113
- package/src/config.ts +116 -24
- package/src/diffusion/diffusion-generate.ts +14 -8
- package/src/download.ts +143 -12
- package/src/eval/README.md +7 -6
- package/src/eval/ifeval.ts +63 -120
- package/src/eval/kl.ts +59 -3
- package/src/eval/quality-db.ts +2 -2
- package/src/eval/runner.ts +17 -8
- package/src/eval/tasks/ifeval.ts +167 -31
- package/src/expert-io.ts +391 -0
- package/src/expert-offload.ts +15 -2
- package/src/expert-residency.ts +1134 -0
- package/src/expert-usage.ts +415 -0
- package/src/flags.ts +13 -0
- package/src/generate.ts +438 -35
- package/src/grammar.ts +585 -0
- package/src/hf-push.ts +1 -1
- package/src/hub-rest.ts +242 -0
- package/src/index.ts +14 -2
- package/src/jobs/runner.ts +37 -18
- package/src/kv-store.ts +586 -93
- package/src/lora.ts +85 -14
- package/src/md-modules.d.ts +42 -0
- package/src/media-fetch.ts +426 -0
- package/src/memory/rest.ts +245 -0
- package/src/memory/vault.ts +74 -0
- package/src/mlx/array.ts +149 -19
- package/src/mlx/autograd.ts +53 -16
- package/src/mlx/checkpoint.ts +46 -13
- package/src/mlx/custom-vjp.ts +83 -35
- package/src/mlx/ffi.ts +27 -3
- package/src/mlx/metal-kernel.ts +7 -4
- package/src/mlx/ops.ts +153 -23
- package/src/mlx/turboquant-ops.ts +831 -0
- package/src/mlx/turboquant-tables.ts +237 -0
- package/src/model/batched-mask.ts +62 -1
- package/src/model/batched-quant.ts +206 -0
- package/src/model/batched-rotating-quant.ts +346 -0
- package/src/model/batched-rotating.ts +46 -1
- package/src/model/compiled-decode.ts +4 -15
- package/src/model/factory.ts +103 -6
- package/src/model/gemma4-base.ts +395 -39
- package/src/model/gemma4.ts +122 -33
- package/src/model/generated/gemma4-12b.ts +9 -13
- package/src/model/generated/gemma4-26b.ts +10 -5
- package/src/model/generated/gemma4-e4b.ts +14 -9
- package/src/model/glm52-atlas.ts +511 -0
- package/src/model/glm52-cache.ts +706 -0
- package/src/model/glm52-config.ts +262 -0
- package/src/model/glm52-container.ts +305 -0
- package/src/model/glm52-coupling.ts +388 -0
- package/src/model/glm52-dsa.ts +602 -0
- package/src/model/glm52-expert-layout.ts +253 -0
- package/src/model/glm52-layout.ts +152 -0
- package/src/model/glm52-memory.ts +473 -0
- package/src/model/glm52-mla.ts +786 -0
- package/src/model/glm52-moe.ts +223 -0
- package/src/model/glm52-pilot.ts +537 -0
- package/src/model/glm52-reference.ts +445 -0
- package/src/model/glm52-residency.ts +557 -0
- package/src/model/glm52-resident-weights.ts +135 -0
- package/src/model/glm52-streamed-experts.ts +453 -0
- package/src/model/glm52-streamed-metal.ts +610 -0
- package/src/model/glm52-weights.ts +503 -0
- package/src/model/glm52.ts +1153 -0
- package/src/model/minicpm5.ts +58 -102
- package/src/model/paged-kv.ts +253 -0
- package/src/model/qwen3-delta.ts +76 -15
- package/src/model/qwen3-moe.ts +404 -0
- package/src/model/qwen3.ts +9 -8
- package/src/model/qwen3_5.ts +88 -23
- package/src/model/support.ts +19 -1
- package/src/model/universal/dense.ts +40 -5
- package/src/model/universal/rope.ts +36 -21
- package/src/native/expert_io.c +523 -0
- package/src/native-pack.ts +6 -4
- package/src/pi-web.ts +1299 -82
- package/src/preflight.ts +27 -8
- package/src/prompt-cache.ts +293 -33
- package/src/quantize/quantizer.ts +16 -1
- package/src/registry.ts +57 -2
- package/src/responses.ts +62 -49
- package/src/serve/batch-scheduler.ts +1050 -78
- package/src/serve/generation-gateway.ts +252 -24
- package/src/serve/isolate.ts +417 -0
- package/src/serve/lane-registry.ts +44 -0
- package/src/serve/session-search.ts +326 -0
- package/src/server.ts +1701 -175
- package/src/spec/assistant-source.ts +150 -0
- package/src/spec/deepspec-source.ts +143 -0
- package/src/spec/dflash-source.ts +124 -0
- package/src/spec/dspark/ab-stats.ts +136 -0
- package/src/spec/dspark/calibration.ts +112 -0
- package/src/spec/dspark/deepspec-module.ts +801 -0
- package/src/spec/dspark/generate-dflash.ts +72 -20
- package/src/spec/dspark/loader.ts +23 -0
- package/src/spec/dspark/module-dflash.ts +278 -36
- package/src/spec/dspark/quantize-drafter.ts +91 -0
- package/src/spec/generate.ts +59 -25
- package/src/spec/glm52-mtp-source.ts +392 -0
- package/src/spec/ngram-source.ts +132 -0
- package/src/spec/serve-loop.ts +617 -0
- package/src/spec/source.ts +118 -0
- package/src/spec/two-model.ts +171 -0
- package/src/ssd-cache.ts +111 -35
- package/src/tokenizer.ts +288 -2
- package/src/tool-approvals.ts +131 -0
- package/src/tool-call.ts +438 -22
- package/src/train/flash-cce.ts +2 -2
- package/src/train/job.ts +0 -12
- package/src/train/lora-params.ts +17 -6
- package/src/train/trainer.ts +60 -30
- package/src/vision/prompt.ts +217 -48
- package/src/web/app.html +1197 -1791
- package/src/web/app.js +5694 -0
- package/src/web/icon.svg +13 -0
- package/src/web/manifest.webmanifest +24 -0
- package/src/web/src/adapters-panel.ts +288 -0
- package/src/web/src/api.ts +57 -0
- package/src/web/src/assistant.ts +485 -0
- package/src/web/src/chat.ts +1050 -0
- package/src/web/src/composer.ts +959 -0
- package/src/web/src/dataset.ts +132 -0
- package/src/web/src/finetune.ts +267 -0
- package/src/web/src/hub.ts +428 -0
- package/src/web/src/main.ts +98 -0
- package/src/web/src/markdown.ts +0 -0
- package/src/web/src/memory-panel.ts +572 -0
- package/src/web/src/model-picker.ts +159 -0
- package/src/web/src/palette.ts +346 -0
- package/src/web/src/protocol.ts +62 -0
- package/src/web/src/quantize.ts +167 -0
- package/src/web/src/rag.ts +274 -0
- package/src/web/src/sessions.ts +469 -0
- package/src/web/src/shell.ts +804 -0
- package/src/web/src/status.ts +300 -0
- package/src/web/src/ui-catalog.ts +92 -0
- package/src/web/sw.js +71 -0
- package/src/web/vendor/README.md +61 -0
- package/src/web/vendor/hljs-LICENSE +29 -0
- package/src/web/vendor/hljs-theme.css +59 -0
- package/src/web/vendor/hljs.js +26 -0
- package/src/web-tools.ts +54 -2
- package/src/weights.ts +82 -19
- package/src/model/fused-decode-kernel.ts +0 -312
- package/src/model/fused-geglu-kernel.ts +0 -198
- package/src/model/fused-mlp-kernel.ts +0 -169
- package/src/model/fused-swiglu-kernel.ts +0 -240
- package/src/model/steel-linear-kernel.ts +0 -96
- package/src/status-page.html +0 -497
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
// GENERATED-ADJACENT source module — part of the src/web/src/* split (plan
|
|
2
|
+
// §7/§9 Phase 2). Built into src/web/app.js by scripts/build-web.ts.
|
|
3
|
+
//
|
|
4
|
+
// STATUS CONTROLLER — ported dashboard (poll /stats + /v1/models + /fit +
|
|
5
|
+
// /library + /downloads). Behavior-identical port of the original
|
|
6
|
+
// controllers.status IIFE in app.html.
|
|
7
|
+
|
|
8
|
+
import { $, gb, mb, num } from "./shell";
|
|
9
|
+
import { esc } from "./markdown";
|
|
10
|
+
|
|
11
|
+
interface DownloadRow {
|
|
12
|
+
repoId: string;
|
|
13
|
+
state: "active" | "done" | "error";
|
|
14
|
+
totalBytes: number;
|
|
15
|
+
receivedBytes: number;
|
|
16
|
+
bytesPerSec?: number;
|
|
17
|
+
currentFile?: string;
|
|
18
|
+
filesDone: number;
|
|
19
|
+
filesTotal: number;
|
|
20
|
+
startedAt: number;
|
|
21
|
+
error?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface LibraryModel {
|
|
25
|
+
repo_id: string;
|
|
26
|
+
size_bytes: number;
|
|
27
|
+
serving?: boolean;
|
|
28
|
+
supported?: boolean;
|
|
29
|
+
model_type?: string;
|
|
30
|
+
vision?: boolean;
|
|
31
|
+
assessment?: {
|
|
32
|
+
fits?: boolean;
|
|
33
|
+
max_safe_context?: number;
|
|
34
|
+
predicted_decode_tps?: number;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface SkuRow {
|
|
39
|
+
sku: string;
|
|
40
|
+
ram_gb: number;
|
|
41
|
+
fits: boolean;
|
|
42
|
+
max_context: number;
|
|
43
|
+
decode_tps: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createStatusController() {
|
|
47
|
+
let timer: ReturnType<typeof setInterval> | null = null, libTimer: ReturnType<typeof setInterval> | null = null, fitLoaded = false;
|
|
48
|
+
const dlRates: Record<string, { bytes: number; t: number }> = {};
|
|
49
|
+
// Library staleness (web-ui-pass-plan.md #3): /library is server-cached for
|
|
50
|
+
// 30s, but the client used to fetch it exactly once per page load
|
|
51
|
+
// (gated on `fitLoaded`) — a quantize job finishing, a fresh download
|
|
52
|
+
// landing, or a manual drop into the HF cache never showed up without a
|
|
53
|
+
// hard reload. Poll it independently on a ~15s cadence while this view is
|
|
54
|
+
// visible (started in enter()/stopped in leave(), same as the main tick).
|
|
55
|
+
|
|
56
|
+
async function tick(): Promise<void> {
|
|
57
|
+
try {
|
|
58
|
+
const [stats, models] = await Promise.all([
|
|
59
|
+
fetch("/stats").then((r) => r.json()),
|
|
60
|
+
fetch("/v1/models").then((r) => r.json()),
|
|
61
|
+
]);
|
|
62
|
+
const m = models.data && models.data[0];
|
|
63
|
+
$("st-model-id").textContent = (m && m.id) || "—";
|
|
64
|
+
const a = stats.admission || {};
|
|
65
|
+
$("st-model-meta").textContent = num(m && m.context_window) + " token window · weights " + gb(a.weights_bytes);
|
|
66
|
+
$("st-safe-ctx").textContent = num(a.max_safe_context);
|
|
67
|
+
|
|
68
|
+
$("st-weights").textContent = gb(a.weights_bytes);
|
|
69
|
+
$("st-usable").textContent = gb(a.usable_bytes);
|
|
70
|
+
$("st-budget").textContent = a.memory_budget_bytes ? gb(a.memory_budget_bytes) : "off";
|
|
71
|
+
$("st-mem-bar").style.width = a.usable_bytes ? Math.min(100, 100 * a.weights_bytes / a.usable_bytes) + "%" : "0%";
|
|
72
|
+
|
|
73
|
+
const pc = stats.prompt_cache || {};
|
|
74
|
+
const lookups = (pc.hits || 0) + (pc.misses || 0);
|
|
75
|
+
$("st-pc-hits").textContent = lookups ? Math.round(100 * pc.hits / lookups) + "%" : "no lookups";
|
|
76
|
+
$("st-pc-entries").textContent = num(pc.entries);
|
|
77
|
+
$("st-pc-bytes").textContent = mb(pc.bytes) + " / " + mb(pc.max_bytes);
|
|
78
|
+
$("st-pc-bar").style.width = pc.max_bytes ? Math.min(100, 100 * pc.bytes / pc.max_bytes) + "%" : "0%";
|
|
79
|
+
|
|
80
|
+
const rs = stats.response_store || {};
|
|
81
|
+
$("st-rs-entries").textContent = num(rs.entries);
|
|
82
|
+
$("st-rs-bytes").textContent = mb(rs.bytes) + " / " + mb(rs.max_bytes);
|
|
83
|
+
$("st-rs-ttl").textContent = rs.ttl_ms ? (rs.ttl_ms / 60000) + " min" : "—";
|
|
84
|
+
$("st-rs-bar").style.width = rs.max_bytes ? Math.min(100, 100 * rs.bytes / rs.max_bytes) + "%" : "0%";
|
|
85
|
+
|
|
86
|
+
const kv = stats.kv_quant || {};
|
|
87
|
+
$("st-kv-mode").innerHTML = "<code>" + esc(kv.mode || "—") + "</code>";
|
|
88
|
+
const att = kv.attention;
|
|
89
|
+
$("st-kv-layers").innerHTML = Object.entries(kv.layers || {})
|
|
90
|
+
.map(([k, v]) => '<div class="kv"><b>' + esc(k) + ' layers</b><span class="num">' + esc(v) + "</span></div>").join("") +
|
|
91
|
+
(att ? '<div class="kv"><b>attention</b><span class="num">' + esc(att.global) + " global · " + esc(att.sliding_window) + " sliding</span></div>" : "");
|
|
92
|
+
|
|
93
|
+
const glm = stats.glm52;
|
|
94
|
+
const expert = glm && glm.expert_runtime;
|
|
95
|
+
const expertCard = $("st-glm-expert-card");
|
|
96
|
+
expertCard.style.display = glm ? "" : "none";
|
|
97
|
+
if (glm) {
|
|
98
|
+
const rate = (d: { hits?: number; misses?: number } | null | undefined): string => {
|
|
99
|
+
const total = (d?.hits || 0) + (d?.misses || 0);
|
|
100
|
+
return total ? (100 * (d?.hits || 0) / total).toFixed(1) + "%" : "—";
|
|
101
|
+
};
|
|
102
|
+
const turn = expert && expert.last_turn;
|
|
103
|
+
$("st-glm-main-hit").textContent = rate(turn?.main?.demand);
|
|
104
|
+
$("st-glm-main-read").textContent = turn?.main?.demand
|
|
105
|
+
? gb(turn.main.demand.readBytes) : "—";
|
|
106
|
+
$("st-glm-mtp-hit").textContent = rate(turn?.mtp?.demand);
|
|
107
|
+
$("st-glm-mtp-read").textContent = turn?.mtp?.demand
|
|
108
|
+
? gb(turn.mtp.demand.readBytes) : "—";
|
|
109
|
+
const main = expert?.main_residency;
|
|
110
|
+
const mtp = expert?.mtp_residency;
|
|
111
|
+
$("st-glm-residency").textContent = main
|
|
112
|
+
? "resident slots main " + num(main.resident + main.pinned) +
|
|
113
|
+
" / " + num(main.resident + main.pinned + main.working) +
|
|
114
|
+
(mtp ? " · MTP " + num(mtp.resident + mtp.pinned) +
|
|
115
|
+
" / " + num(mtp.resident + mtp.pinned + mtp.working) : "")
|
|
116
|
+
: "expert runtime telemetry unavailable";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
await loadDownloads();
|
|
120
|
+
if (!fitLoaded) { await loadFit(); await loadLibrary(); }
|
|
121
|
+
if (stats.server && stats.server.started_at) {
|
|
122
|
+
const up = (Date.now() - stats.server.started_at) / 1000;
|
|
123
|
+
$("st-uptime").textContent = up < 90 ? Math.round(up) + "s" : up < 5400 ? Math.round(up / 60) + "m" : (up / 3600).toFixed(1) + "h";
|
|
124
|
+
}
|
|
125
|
+
$("st-updated").textContent = new Date().toLocaleTimeString();
|
|
126
|
+
} catch {
|
|
127
|
+
/* nav pill already reflects connectivity; leave last-good values on screen */
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function loadDownloads(): Promise<void> {
|
|
132
|
+
let downloads: DownloadRow[] | undefined;
|
|
133
|
+
try { ({ downloads } = await fetch("/downloads").then((r) => r.json())); } catch { return; }
|
|
134
|
+
const card = $("st-dl-card");
|
|
135
|
+
if (!downloads || downloads.length === 0) { card.style.display = "none"; return; }
|
|
136
|
+
card.style.display = "";
|
|
137
|
+
const order = downloads.slice().sort((a, b) =>
|
|
138
|
+
(a.state === "active" ? 0 : 1) - (b.state === "active" ? 0 : 1) || b.startedAt - a.startedAt);
|
|
139
|
+
$("st-dl-list").innerHTML = order.map((d) => {
|
|
140
|
+
const pct = d.totalBytes ? Math.min(100, 100 * d.receivedBytes / d.totalBytes) : 0;
|
|
141
|
+
let detail: string, barCls = "";
|
|
142
|
+
if (d.state === "active") {
|
|
143
|
+
let bps = d.bytesPerSec || 0;
|
|
144
|
+
if (!bps) {
|
|
145
|
+
const prev = dlRates[d.repoId], now = performance.now();
|
|
146
|
+
if (prev && now > prev.t) bps = (d.receivedBytes - prev.bytes) / ((now - prev.t) / 1000);
|
|
147
|
+
dlRates[d.repoId] = { bytes: d.receivedBytes, t: now };
|
|
148
|
+
}
|
|
149
|
+
const speed = bps > 0 ? " · " + mb(bps) + "/s · ~" + Math.max(1, Math.round((d.totalBytes - d.receivedBytes) / bps / 60)) + " min left" : "";
|
|
150
|
+
detail = gb(d.receivedBytes) + " / " + gb(d.totalBytes) + " · " + pct.toFixed(0) + "%" + speed +
|
|
151
|
+
(d.currentFile ? " · <code>" + esc(d.currentFile) + "</code>" : "");
|
|
152
|
+
barCls = "warm";
|
|
153
|
+
} else if (d.state === "done") {
|
|
154
|
+
detail = gb(d.totalBytes) + ' · <span style="color:var(--green);font-weight:600">complete</span>';
|
|
155
|
+
} else {
|
|
156
|
+
detail = '<span style="color:var(--red);font-weight:600">error</span> · ' + esc(d.error || "");
|
|
157
|
+
}
|
|
158
|
+
return '<div style="padding:10px 0">' +
|
|
159
|
+
'<div class="kv" style="border:none"><b>' + esc(d.repoId) + "</b>" +
|
|
160
|
+
'<span class="num">' + d.filesDone + "/" + d.filesTotal + " files</span></div>" +
|
|
161
|
+
'<div style="font-size:13.5px;color:var(--dim)" class="num">' + detail + "</div>" +
|
|
162
|
+
'<div class="meter ' + barCls + '"><i style="width:' + pct + '%"></i></div></div>';
|
|
163
|
+
}).join("");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function loadLibrary(): Promise<void> {
|
|
167
|
+
let models: LibraryModel[] | undefined;
|
|
168
|
+
try { ({ models } = await fetch("/library").then((r) => r.json())); } catch { return; }
|
|
169
|
+
if (!models) return;
|
|
170
|
+
$("st-lib-body").innerHTML = models.slice()
|
|
171
|
+
.sort((a, b) => (b.serving ? 1 : 0) - (a.serving ? 1 : 0) || b.size_bytes - a.size_bytes)
|
|
172
|
+
.map((m) => {
|
|
173
|
+
const a = m.assessment;
|
|
174
|
+
let status: string;
|
|
175
|
+
if (m.serving) status = '<span style="color:var(--blue);font-weight:700">SERVING</span>';
|
|
176
|
+
else if (!m.supported) status = '<span style="color:var(--dimmer)">unsupported (' + esc(m.model_type) + ")</span>";
|
|
177
|
+
else if (a && a.fits) status = '<span style="color:var(--green);font-weight:600">fits</span>';
|
|
178
|
+
else status = '<span style="color:var(--red);font-weight:600">too big</span>';
|
|
179
|
+
// web-ui-pass-plan.md #20: max_safe_context is meaningful whenever
|
|
180
|
+
// it's > 0, even when `fits` is false at the library's default 8192
|
|
181
|
+
// probe context — e.g. a model that doesn't fit at 8192 tokens but
|
|
182
|
+
// does fit at a smaller context still gets a real number here rather
|
|
183
|
+
// than an uninformative "—". predicted_decode_tps stays gated on
|
|
184
|
+
// `fits` since it's only computed for the fitting case server-side.
|
|
185
|
+
const safeCtx = m.supported && a && (a.max_safe_context ?? 0) > 0 ? num(a.max_safe_context) : "—";
|
|
186
|
+
return "<tr" + (m.serving ? ' class="you"' : "") + "><td>" + esc(m.repo_id) +
|
|
187
|
+
(m.vision ? ' · <span style="color:var(--dim)">vision</span>' : "") +
|
|
188
|
+
'</td><td class="num">' + gb(m.size_bytes) + "</td><td>" + status + "</td>" +
|
|
189
|
+
'<td class="num">' + safeCtx + "</td>" +
|
|
190
|
+
'<td class="num">' + (m.supported && a && a.fits ? (a.predicted_decode_tps ?? 0).toFixed(0) + " tok/s" : "—") + "</td></tr>";
|
|
191
|
+
}).join("");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function loadFit(): Promise<void> {
|
|
195
|
+
interface FitResponse {
|
|
196
|
+
report?: {
|
|
197
|
+
fits?: boolean;
|
|
198
|
+
weights_bytes?: number; kv_bytes?: number; transient_bytes?: number;
|
|
199
|
+
total_bytes?: number; usable_bytes?: number; predicted_decode_tps?: number;
|
|
200
|
+
};
|
|
201
|
+
measured_decode_tps?: number;
|
|
202
|
+
typical_decode_tps?: number;
|
|
203
|
+
typical_context_tokens?: number;
|
|
204
|
+
context_tokens?: number;
|
|
205
|
+
machine?: { ram_bytes?: number; bandwidth_gbs?: number; chip?: string };
|
|
206
|
+
sku_matrix_ctx?: number;
|
|
207
|
+
sku_matrix?: SkuRow[];
|
|
208
|
+
glm52?: {
|
|
209
|
+
artifact_disk_bytes?: number | null;
|
|
210
|
+
main_expert_slab_bytes?: number;
|
|
211
|
+
mtp_expert_slab_bytes?: number;
|
|
212
|
+
direct_oracle_warm_decode_tps?: number;
|
|
213
|
+
aspirational_decode_tps?: number;
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
let f: FitResponse;
|
|
217
|
+
try { f = await fetch("/fit").then((r) => r.json()); } catch { return; }
|
|
218
|
+
fitLoaded = true;
|
|
219
|
+
const r = f.report || {};
|
|
220
|
+
const tps = (value: number): string => value < 1 ? value.toFixed(2) : value.toFixed(1);
|
|
221
|
+
if (f.measured_decode_tps) {
|
|
222
|
+
$("st-tps").textContent = tps(f.measured_decode_tps);
|
|
223
|
+
$("st-tps").closest(".card")!.querySelector("h3")!.textContent = "Decode · measured";
|
|
224
|
+
$("st-tps-cap").textContent = f.glm52
|
|
225
|
+
? "quality-preserving warm · direct oracle " +
|
|
226
|
+
tps(f.glm52.direct_oracle_warm_decode_tps || 0) +
|
|
227
|
+
" · aspiration " + tps(f.glm52.aspirational_decode_tps || 0)
|
|
228
|
+
: "mlx-bun benchmark on this machine · predicted " +
|
|
229
|
+
tps(f.typical_decode_tps || 0) + " at " + num(f.typical_context_tokens) + " ctx";
|
|
230
|
+
} else if (f.typical_decode_tps) {
|
|
231
|
+
$("st-tps").textContent = f.typical_decode_tps.toFixed(1);
|
|
232
|
+
$("st-tps-cap").textContent = "at " + num(f.typical_context_tokens) + " context · " + (r.predicted_decode_tps || 0).toFixed(1) + " tok/s at the " + num(f.context_tokens) + " max (full-KV reads)";
|
|
233
|
+
} else {
|
|
234
|
+
$("st-tps").textContent = (r.predicted_decode_tps || 0).toFixed(1);
|
|
235
|
+
}
|
|
236
|
+
$("st-m-ram").textContent = gb(f.machine && f.machine.ram_bytes);
|
|
237
|
+
$("st-m-bw").textContent = (f.machine && f.machine.bandwidth_gbs || "—") + " GB/s";
|
|
238
|
+
$("st-m-fits").innerHTML = r.fits ? '<span style="color:var(--green);font-weight:700">FITS</span>' : '<span style="color:var(--red);font-weight:700">DOES NOT FIT</span>';
|
|
239
|
+
$("st-fit-ctx").textContent = num(f.context_tokens);
|
|
240
|
+
document.querySelectorAll<HTMLElement>("[data-glm-fit]").forEach((row) => {
|
|
241
|
+
row.style.display = f.glm52 ? "" : "none";
|
|
242
|
+
});
|
|
243
|
+
$("st-f-weights-label").textContent = f.glm52 ? "resident weights" : "weights";
|
|
244
|
+
$("st-f-transient-label").textContent = f.glm52 ? "other reserves" : "transient";
|
|
245
|
+
$("st-f-artifact").textContent = f.glm52?.artifact_disk_bytes
|
|
246
|
+
? gb(f.glm52.artifact_disk_bytes) : "registry unavailable";
|
|
247
|
+
$("st-f-main-slab").textContent = gb(f.glm52?.main_expert_slab_bytes);
|
|
248
|
+
$("st-f-mtp-slab").textContent = gb(f.glm52?.mtp_expert_slab_bytes);
|
|
249
|
+
$("st-f-weights").textContent = gb(r.weights_bytes);
|
|
250
|
+
$("st-f-kv").textContent = gb(r.kv_bytes);
|
|
251
|
+
$("st-f-transient").textContent = gb(r.transient_bytes);
|
|
252
|
+
$("st-f-total").textContent = gb(r.total_bytes) + " / " + gb(r.usable_bytes);
|
|
253
|
+
$("st-fit-bar").style.width = r.usable_bytes ? Math.min(100, 100 * (r.total_bytes ?? 0) / r.usable_bytes) + "%" : "0%";
|
|
254
|
+
|
|
255
|
+
$("st-sku-ctx").textContent = num(f.sku_matrix_ctx);
|
|
256
|
+
const myRam = Math.round((f.machine && f.machine.ram_bytes || 0) / 2 ** 30);
|
|
257
|
+
const myChip = (f.machine && f.machine.chip) || null;
|
|
258
|
+
const rows = f.sku_matrix || [];
|
|
259
|
+
let youIdx = rows.findIndex((row) => myChip && row.sku === myChip && row.ram_gb === myRam);
|
|
260
|
+
if (youIdx < 0 && myChip) {
|
|
261
|
+
let best = Infinity;
|
|
262
|
+
rows.forEach((row, i) => { if (row.sku === myChip && Math.abs(row.ram_gb - myRam) < best) { best = Math.abs(row.ram_gb - myRam); youIdx = i; } });
|
|
263
|
+
}
|
|
264
|
+
if (youIdx < 0) youIdx = rows.findIndex((row) => row.ram_gb === myRam);
|
|
265
|
+
$("st-sku-body").innerHTML = rows.map((row, i) => {
|
|
266
|
+
const you = i === youIdx;
|
|
267
|
+
const dimCls = row.fits ? "num" : "num fit-no";
|
|
268
|
+
return "<tr" + (you ? ' class="you"' : "") + "><td>" + esc(row.sku) + (you ? " · <b>this machine</b>" : "") +
|
|
269
|
+
'</td><td class="num">' + row.ram_gb + " GB</td>" +
|
|
270
|
+
'<td class="' + (row.fits ? "fit-yes" : "fit-no") + '">' + (row.fits ? "fits" : "no") + "</td>" +
|
|
271
|
+
'<td class="' + dimCls + '">' + (row.max_context > 0 ? num(row.max_context) : "0 — weights alone don’t fit") + "</td>" +
|
|
272
|
+
'<td class="' + dimCls + '">' + (row.decode_tps > 0 ? "~" + tps(row.decode_tps) + " tok/s" : "—") + "</td></tr>";
|
|
273
|
+
}).join("");
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
init() {
|
|
278
|
+
// trigger scroll reveals once mounted
|
|
279
|
+
const page = $("status-page");
|
|
280
|
+
requestAnimationFrame(() => page.classList.add("lit"));
|
|
281
|
+
page.classList.add("lit");
|
|
282
|
+
},
|
|
283
|
+
enter() {
|
|
284
|
+
tick();
|
|
285
|
+
timer = setInterval(tick, 2000);
|
|
286
|
+
loadLibrary();
|
|
287
|
+
libTimer = setInterval(loadLibrary, 15_000);
|
|
288
|
+
},
|
|
289
|
+
leave() {
|
|
290
|
+
if (timer) clearInterval(timer); timer = null;
|
|
291
|
+
if (libTimer) clearInterval(libTimer); libTimer = null;
|
|
292
|
+
},
|
|
293
|
+
// Exposed so a quantize job's `done` event (this same controller's own
|
|
294
|
+
// quantize flow, see controllers.quantize below) can force an immediate
|
|
295
|
+
// refresh instead of waiting up to 15s — the server invalidates its
|
|
296
|
+
// /library cache the moment the job completes (server.ts onComplete),
|
|
297
|
+
// so an immediate re-fetch is guaranteed fresh, not a race.
|
|
298
|
+
refreshLibrary: loadLibrary,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// GENERATED-ADJACENT source module — part of the src/web/src/* split (plan
|
|
2
|
+
// §7/§9 Phase 3, §6.6 "app-aware assistant"). Built into src/web/app.js by
|
|
3
|
+
// scripts/build-web.ts.
|
|
4
|
+
//
|
|
5
|
+
// The frontend half of the app-aware assistant's route/spotlight catalog.
|
|
6
|
+
// Mirrors PortfolioManager's ui-catalog.json pattern (client/src/lib/
|
|
7
|
+
// ui-catalog.ts + server/src/agent/ui-catalog.ts, read together with
|
|
8
|
+
// server/src/agent/portfolio-tools.ts as the reference implementation) but
|
|
9
|
+
// inlined as a TS module (not a shared JSON file) since mlx-bun's server and
|
|
10
|
+
// browser bundles are separate build outputs (scripts/build-web.ts vs bun's
|
|
11
|
+
// own TS execution) with no shared-JSON-import convention today — pi-web.ts
|
|
12
|
+
// mirrors ROUTE_IDS/SPOTLIGHT_TARGETS as plain string arrays for its own
|
|
13
|
+
// server-side validation (navigate_app/spotlight_ui reject anything not in
|
|
14
|
+
// that list BEFORE touching the browser) so route validity is checked at
|
|
15
|
+
// both ends without a build-time shared asset.
|
|
16
|
+
//
|
|
17
|
+
// Keep this list's ROUTE_IDS in sync with shell.ts's ROUTES tuple (chat,
|
|
18
|
+
// quantize, finetune, dataset, status) plus the Developer-only overlays that
|
|
19
|
+
// aren't hash-routes (memory panel, hub panel, settings) — those are
|
|
20
|
+
// `view`s, not `route`s (see UiContext.view below), reached by opening an
|
|
21
|
+
// overlay rather than changing the URL hash.
|
|
22
|
+
|
|
23
|
+
/** A hash-routed page (shell.ts's Route union, minus "routes" — the DAG
|
|
24
|
+
* diagram tab isn't a useful assistant destination and is itself
|
|
25
|
+
* feature-detected/hidden when unavailable). */
|
|
26
|
+
export const ROUTE_IDS = ["chat", "quantize", "finetune", "dataset", "status"] as const;
|
|
27
|
+
export type RouteId = (typeof ROUTE_IDS)[number];
|
|
28
|
+
|
|
29
|
+
/** A non-route overlay/panel the assistant can navigate to or spotlight
|
|
30
|
+
* within. Opening one of these does not change location.hash. */
|
|
31
|
+
export const VIEW_IDS = ["memory-panel", "hub-panel", "settings", "adapters-panel"] as const;
|
|
32
|
+
export type ViewId = (typeof VIEW_IDS)[number];
|
|
33
|
+
|
|
34
|
+
export function isRouteId(v: string): v is RouteId {
|
|
35
|
+
return (ROUTE_IDS as readonly string[]).includes(v);
|
|
36
|
+
}
|
|
37
|
+
export function isViewId(v: string): v is ViewId {
|
|
38
|
+
return (VIEW_IDS as readonly string[]).includes(v);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Human labels for routes, used in the "unknown route" tool-error message
|
|
42
|
+
* and nowhere else — kept tiny on purpose. */
|
|
43
|
+
export const ROUTE_LABELS: Record<RouteId, string> = {
|
|
44
|
+
chat: "Chat",
|
|
45
|
+
quantize: "Quantize",
|
|
46
|
+
finetune: "Fine-tune",
|
|
47
|
+
dataset: "Build Dataset",
|
|
48
|
+
status: "Status",
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** A curated spotlight target: a stable id -> the selector to highlight +
|
|
52
|
+
* a human label for the popover title, plus which route/view it lives on
|
|
53
|
+
* (spotlight_ui navigates there first if the browser isn't already on it).
|
|
54
|
+
* This is the "catalog target" resolution path in resolve-spotlight.ts's
|
|
55
|
+
* terms — the fallback when ref/label/selector don't resolve from the live
|
|
56
|
+
* DOM or the last uiSnapshot. Anchors added to app.html via data-spotlight
|
|
57
|
+
* in this same wave; ids are stable and boring (element id minus "chat-"
|
|
58
|
+
* prefix noise where it reads fine either way). */
|
|
59
|
+
export interface SpotlightTarget {
|
|
60
|
+
selector: string;
|
|
61
|
+
label: string;
|
|
62
|
+
route?: RouteId;
|
|
63
|
+
view?: ViewId;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const SPOTLIGHT_TARGETS: Record<string, SpotlightTarget> = {
|
|
67
|
+
composer: { selector: "#chat-box", label: "Message box", route: "chat" },
|
|
68
|
+
send: { selector: "#chat-send", label: "Send", route: "chat" },
|
|
69
|
+
"new-chat": { selector: "#chat-new", label: "New chat", route: "chat" },
|
|
70
|
+
"session-search": { selector: "#chat-sess-search", label: "Search chats", route: "chat" },
|
|
71
|
+
"sampling-pill": { selector: "#chat-sampling", label: "Sampling", route: "chat" },
|
|
72
|
+
"prompt-pill": { selector: "#chat-sysprompt", label: "System prompt", route: "chat" },
|
|
73
|
+
"adapter-select": { selector: "#chat-adapter", label: "LoRA adapter", route: "chat" },
|
|
74
|
+
"memory-entry": { selector: "#chat-memory-entry", label: "Memory", route: "chat" },
|
|
75
|
+
"developer-toggle": { selector: "#nav-developer", label: "Developer toggle" },
|
|
76
|
+
"model-picker": { selector: "#nav-model", label: "Active model" },
|
|
77
|
+
"hub-browse": { selector: "#model-pop-browse", label: "Browse models", view: "hub-panel" },
|
|
78
|
+
"quantize-source": { selector: "#q-model", label: "Source model", route: "quantize" },
|
|
79
|
+
"finetune-base": { selector: "#f-model", label: "Base model", route: "finetune" },
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export function getSpotlightTarget(id: string): SpotlightTarget | null {
|
|
83
|
+
return SPOTLIGHT_TARGETS[id] ?? null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Resolve a free-form route/page string (either a bare route id or a
|
|
87
|
+
* "#/route" hash) the way navigate_app's tool param is described to the
|
|
88
|
+
* model. Returns null for anything not in ROUTE_IDS. */
|
|
89
|
+
export function resolveRouteId(routeOrPage: string): RouteId | null {
|
|
90
|
+
const bare = routeOrPage.trim().replace(/^#\/?/, "");
|
|
91
|
+
return isRouteId(bare) ? bare : null;
|
|
92
|
+
}
|
package/src/web/sw.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// mlx-bun service worker — SHELL ONLY, not offline chat.
|
|
2
|
+
//
|
|
3
|
+
// Scope (plan §9 Phase 3, beat-matrix Axis 10 "PWA installability"):
|
|
4
|
+
// installability + instant shell load. The app is useless without the
|
|
5
|
+
// local inference server actually running (every chat turn is a live
|
|
6
|
+
// WebSocket to this same origin's model process), so caching API/WS
|
|
7
|
+
// traffic would be actively misleading — a "successful" cached response
|
|
8
|
+
// with no model behind it. This worker therefore caches exactly the
|
|
9
|
+
// static shell files needed to paint the UI instantly on repeat visits,
|
|
10
|
+
// and explicitly does NOT intercept /api/*, /v1/*, /ws/*, /downloads, or
|
|
11
|
+
// any other dynamic route — those always go straight to the network,
|
|
12
|
+
// cache-worker or not.
|
|
13
|
+
//
|
|
14
|
+
// Strategy: cache-first for the shell assets below (instant paint from
|
|
15
|
+
// cache, revalidate in the background), network-first fallthrough for
|
|
16
|
+
// everything else (a cache miss or a non-shell request just fetches
|
|
17
|
+
// normally, same as if this worker didn't exist).
|
|
18
|
+
//
|
|
19
|
+
// Versioned by CACHE_NAME so a deploy invalidates old shells; bump the
|
|
20
|
+
// suffix here if the shell file set changes.
|
|
21
|
+
const CACHE_NAME = "mlxbun-shell-v1";
|
|
22
|
+
const SHELL_FILES = [
|
|
23
|
+
"/",
|
|
24
|
+
"/assets/app.js",
|
|
25
|
+
"/assets/hljs.js",
|
|
26
|
+
"/assets/hljs.css",
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
self.addEventListener("install", (event) => {
|
|
30
|
+
event.waitUntil(
|
|
31
|
+
caches.open(CACHE_NAME).then((cache) => cache.addAll(SHELL_FILES)).then(() => self.skipWaiting()),
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
self.addEventListener("activate", (event) => {
|
|
36
|
+
event.waitUntil(
|
|
37
|
+
caches.keys()
|
|
38
|
+
.then((names) => Promise.all(names.filter((n) => n !== CACHE_NAME).map((n) => caches.delete(n))))
|
|
39
|
+
.then(() => self.clients.claim()),
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
self.addEventListener("fetch", (event) => {
|
|
44
|
+
const req = event.request;
|
|
45
|
+
// Only ever handle GET; never intercept the WebSocket upgrade or any
|
|
46
|
+
// mutating request.
|
|
47
|
+
if (req.method !== "GET") return;
|
|
48
|
+
|
|
49
|
+
const url = new URL(req.url);
|
|
50
|
+
// Same-origin only, and only the exact shell files — everything else
|
|
51
|
+
// (including /api/*, /v1/*, /downloads, /curves, /dag, and any future
|
|
52
|
+
// route) falls through to the network untouched. This is the load-
|
|
53
|
+
// bearing line: it's what keeps "offline chat" from silently
|
|
54
|
+
// half-working with stale data instead of failing honestly.
|
|
55
|
+
if (url.origin !== self.location.origin || !SHELL_FILES.includes(url.pathname)) return;
|
|
56
|
+
|
|
57
|
+
event.respondWith(
|
|
58
|
+
caches.match(req).then((cached) => {
|
|
59
|
+
const network = fetch(req)
|
|
60
|
+
.then((res) => {
|
|
61
|
+
if (res && res.ok) caches.open(CACHE_NAME).then((cache) => cache.put(req, res.clone()));
|
|
62
|
+
return res;
|
|
63
|
+
})
|
|
64
|
+
.catch(() => cached); // offline and nothing cached yet — let it reject naturally
|
|
65
|
+
// Cache-first: serve the cached shell instantly if we have it, and
|
|
66
|
+
// still revalidate in the background so the next load picks up a
|
|
67
|
+
// rebuilt app.js. No cache yet (first visit) -> wait on the network.
|
|
68
|
+
return cached || network;
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# src/web/vendor/ — vendored, no-CDN web assets
|
|
2
|
+
|
|
3
|
+
Assets here are bundled locally and served same-origin by `src/server.ts`
|
|
4
|
+
(`GET /assets/<name>`, `with { type: "text" }` import — same pattern as
|
|
5
|
+
`src/web/app.html` itself). Nothing in this app loads a script or
|
|
6
|
+
stylesheet from a CDN; this directory is where that tradeoff lives.
|
|
7
|
+
|
|
8
|
+
## hljs.js — syntax highlighting
|
|
9
|
+
|
|
10
|
+
`hljs.js` is [highlight.js](https://github.com/highlightjs/highlight.js)
|
|
11
|
+
(BSD-3-Clause, license text in `hljs-LICENSE`) core engine + a curated
|
|
12
|
+
language set: javascript/typescript, python, bash/shell, json, html/xml,
|
|
13
|
+
css, c/cpp, rust, go, sql, yaml, markdown, diff, plaintext. Registered
|
|
14
|
+
languages expose themselves as `class="language-<name>"` on `<code>`
|
|
15
|
+
elements — see `mdCodeBlock()` and `highlightIn()` in `app.html`.
|
|
16
|
+
|
|
17
|
+
To rebuild (e.g. to bump the version or change the language set):
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
mkdir /tmp/hljs-build && cd /tmp/hljs-build
|
|
21
|
+
npm init -y
|
|
22
|
+
npm install highlight.js@<version> --no-save
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Write an `entry.js` that imports `highlight.js/lib/core` and
|
|
26
|
+
`highlight.js/lib/languages/<lang>` for each language you want, calling
|
|
27
|
+
`hljs.registerLanguage(name, lang)` for each (see mlx-bun's own build log /
|
|
28
|
+
commit that introduced this file for the exact entry.js used), then:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
globalThis.hljs = hljs; # inside entry.js, after registering
|
|
32
|
+
bun build ./entry.js --minify --outfile=hljs.bundle.js
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Prepend a short license/provenance header comment (BSD-3-Clause,
|
|
36
|
+
upstream URL, rebuild instructions — copy the one already at the top of
|
|
37
|
+
`hljs.js`) and copy the result over `src/web/vendor/hljs.js`.
|
|
38
|
+
|
|
39
|
+
**Size discipline:** the hygiene gate (`scripts/check-hygiene.ts`) fails
|
|
40
|
+
any tracked file over 1 MB unless it's on the explicit allowlist. The
|
|
41
|
+
current bundle (core + 14 languages) is ~83 KB — comfortably under the
|
|
42
|
+
cap. If you add enough languages to bust 1 MB, either trim the language
|
|
43
|
+
list or add a justified, size-capped `ALLOW` entry in
|
|
44
|
+
`scripts/check-hygiene.ts` (see the existing entries there for the
|
|
45
|
+
pattern) — don't silently let it grow past the cap.
|
|
46
|
+
|
|
47
|
+
`hljs-theme.css` is NOT generated from highlight.js's stock themes — it's
|
|
48
|
+
hand-written, mapping the standard `hljs-*` token classes onto mlx-bun's
|
|
49
|
+
own design tokens (see the file's own header comment) so code blocks match
|
|
50
|
+
the app's palette in both light and dark.
|
|
51
|
+
|
|
52
|
+
## KaTeX (math) — deliberately not vendored
|
|
53
|
+
|
|
54
|
+
Investigated for the same treatment; skipped. KaTeX's CSS depends on its
|
|
55
|
+
own bundled math font (`dist/fonts/`, ~60 files / ~1.1 MB of woff2/woff/ttf)
|
|
56
|
+
for correct glyph metrics — there's no clean "system font fallback" path
|
|
57
|
+
that renders math correctly. Vendoring it would mean adding ~1 MB of
|
|
58
|
+
binary font files, which the hygiene gate is specifically designed to keep
|
|
59
|
+
out of this repo (see `scripts/check-hygiene.ts`'s header). Needs the
|
|
60
|
+
build-step/asset-hosting decision from `docs/design/web-chat-redesign.md`
|
|
61
|
+
§7 before it's worth revisiting.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2006, Ivan Sagalaev.
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
* Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* Vendored highlight.js theme, hand-derived from mlx-bun's own design
|
|
2
|
+
* tokens (see the :root / [data-theme="light"] blocks in src/web/app.html)
|
|
3
|
+
* rather than one of highlight.js's stock themes — keeps code-block colors
|
|
4
|
+
* on the same palette as the rest of the app in both themes. Code blocks
|
|
5
|
+
* deliberately keep a dark chrome in BOTH themes (see app.html's
|
|
6
|
+
* --code-bg/--code-ink comment), so this stylesheet targets a fixed dark
|
|
7
|
+
* surface rather than swapping with [data-theme="light"] itself; only the
|
|
8
|
+
* accent hues are shared with the live token set.
|
|
9
|
+
*
|
|
10
|
+
* Applies to `.bubble pre code.hljs` (see the highlightIn() hook in
|
|
11
|
+
* app.html) — every `<span class="hljs-*">` produced by the vendored
|
|
12
|
+
* hljs.js bundle (src/web/vendor/hljs.js, BSD-3-Clause, see its header).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
.bubble pre code.hljs{ display:block; }
|
|
16
|
+
|
|
17
|
+
.hljs-comment,
|
|
18
|
+
.hljs-quote{ color:#86868b; font-style:italic; }
|
|
19
|
+
|
|
20
|
+
.hljs-keyword,
|
|
21
|
+
.hljs-selector-tag,
|
|
22
|
+
.hljs-subst,
|
|
23
|
+
.hljs-doctag{ color:#ff375f; }
|
|
24
|
+
|
|
25
|
+
.hljs-string,
|
|
26
|
+
.hljs-regexp,
|
|
27
|
+
.hljs-addition,
|
|
28
|
+
.hljs-attr,
|
|
29
|
+
.hljs-meta-string{ color:#30d158; }
|
|
30
|
+
|
|
31
|
+
.hljs-number,
|
|
32
|
+
.hljs-literal,
|
|
33
|
+
.hljs-deletion{ color:#ff9f0a; }
|
|
34
|
+
|
|
35
|
+
.hljs-title,
|
|
36
|
+
.hljs-title.function_,
|
|
37
|
+
.hljs-title.class_,
|
|
38
|
+
.hljs-section{ color:#64d2ff; }
|
|
39
|
+
|
|
40
|
+
.hljs-type,
|
|
41
|
+
.hljs-class .hljs-title,
|
|
42
|
+
.hljs-built_in{ color:#bf5af2; }
|
|
43
|
+
|
|
44
|
+
.hljs-name,
|
|
45
|
+
.hljs-tag,
|
|
46
|
+
.hljs-selector-id,
|
|
47
|
+
.hljs-selector-class{ color:#0a84ff; }
|
|
48
|
+
|
|
49
|
+
.hljs-variable,
|
|
50
|
+
.hljs-template-variable,
|
|
51
|
+
.hljs-attribute{ color:#ffd60a; }
|
|
52
|
+
|
|
53
|
+
.hljs-symbol,
|
|
54
|
+
.hljs-bullet,
|
|
55
|
+
.hljs-link,
|
|
56
|
+
.hljs-meta{ color:#5e5ce6; }
|
|
57
|
+
|
|
58
|
+
.hljs-emphasis{ font-style:italic; }
|
|
59
|
+
.hljs-strong{ font-weight:700; }
|