mlx-bun 0.4.0 → 1.0.0

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.
Files changed (601) hide show
  1. package/README.md +1453 -88
  2. package/THIRD_PARTY_NOTICES.md +882 -0
  3. package/bin/mlx-bun.convert.mjs +4 -0
  4. package/bin/mlx-bun.fuse.mjs +4 -0
  5. package/bin/mlx-bun.generate.mjs +4 -0
  6. package/bin/mlx-bun.lora.mjs +4 -0
  7. package/bin/mlx-bun.mjs +13 -21
  8. package/bin/mlx-bun.server.mjs +4 -0
  9. package/bin/mlx-bun.upload.mjs +4 -0
  10. package/dist/web/app.js +9584 -0
  11. package/package.json +50 -53
  12. package/src/cli/ane-install.ts +76 -0
  13. package/src/cli/args.ts +179 -0
  14. package/src/cli/engine-entry.ts +68 -0
  15. package/src/cli/inference.ts +214 -0
  16. package/src/cli/job-entry.ts +97 -0
  17. package/src/cli/library-host.ts +117 -0
  18. package/src/cli/main.ts +57 -0
  19. package/src/cli/memory-engine.ts +163 -0
  20. package/src/cli/memory.ts +36 -0
  21. package/src/cli/mlx-lm-aliases.ts +309 -0
  22. package/src/cli/model-choice.ts +33 -0
  23. package/src/cli/model-selection.ts +84 -0
  24. package/src/cli/module-host.ts +11 -0
  25. package/src/cli/module-verbs.ts +50 -0
  26. package/src/cli/numerical-policy.ts +12 -0
  27. package/src/cli/perplexity.ts +89 -0
  28. package/src/cli/serve-host.ts +423 -0
  29. package/src/cli/serve-isolated.ts +217 -0
  30. package/src/cli/serve-options.ts +106 -0
  31. package/src/cli/serve-state.ts +205 -0
  32. package/src/cli/serve.ts +500 -0
  33. package/src/cli/served-model-host.ts +102 -0
  34. package/src/cli/server-entry.ts +148 -0
  35. package/src/cli/serving-unit.ts +177 -0
  36. package/src/{tui.ts → cli/terminal.ts} +13 -8
  37. package/src/cli/worker-adapters.ts +34 -0
  38. package/src/cli/worker-entry.ts +272 -0
  39. package/src/cli/worker-unit.ts +179 -0
  40. package/src/engine/adapter-operation.ts +26 -0
  41. package/src/engine/cache-services.ts +114 -0
  42. package/src/engine/completion.ts +39 -0
  43. package/src/engine/generation-gateway.ts +358 -0
  44. package/src/engine/index.ts +59 -0
  45. package/src/engine/kv-budget.ts +120 -0
  46. package/src/engine/media-preparation.ts +232 -0
  47. package/src/engine/model-binding.ts +24 -0
  48. package/src/engine/model-host.ts +513 -0
  49. package/src/engine/model-serving.ts +89 -0
  50. package/src/{serve → engine}/preparation.ts +10 -8
  51. package/src/{serve → engine}/session-completion-engine.ts +6 -6
  52. package/src/engine/telemetry.ts +109 -0
  53. package/src/hub/downloads.ts +116 -0
  54. package/src/jobs/db.ts +16 -7
  55. package/src/jobs/events.ts +29 -0
  56. package/src/jobs/executable.ts +5 -0
  57. package/src/jobs/host.ts +51 -0
  58. package/src/jobs/in-process.ts +55 -0
  59. package/src/{contracts/jobs.ts → jobs/protocol.ts} +7 -9
  60. package/src/jobs/runner.ts +150 -231
  61. package/src/jobs/service.ts +75 -0
  62. package/src/jobs/sse.ts +59 -23
  63. package/src/jobs/worker-memory.ts +46 -0
  64. package/src/jobs/worker-process.ts +163 -0
  65. package/src/jobs/worker-supervisor.ts +258 -0
  66. package/src/modules.ts +58 -0
  67. package/src/publishing/catalog-hub.ts +18 -0
  68. package/src/publishing/credentials.ts +31 -0
  69. package/src/publishing/upload.ts +37 -0
  70. package/src/residency/leased-adapters.ts +21 -0
  71. package/src/residency/local-records.ts +16 -0
  72. package/src/residency/model-residency.ts +405 -0
  73. package/src/residency/record-index.ts +28 -0
  74. package/src/residency/resident-estimate.ts +50 -0
  75. package/src/{anthropic.ts → server/anthropic.ts} +3 -81
  76. package/src/server/cache-routes.ts +36 -0
  77. package/src/{serve → server}/chat-request.ts +12 -10
  78. package/src/{serve → server}/chat-stage.ts +27 -22
  79. package/src/{client.ts → server/client.ts} +28 -2
  80. package/src/{serve → server}/completion-executor.ts +42 -42
  81. package/src/server/decision-body.ts +55 -0
  82. package/src/server/decision-routes.ts +134 -0
  83. package/src/server/decision-wire.ts +228 -0
  84. package/src/{serve → server}/discovery-routes.ts +42 -83
  85. package/src/server/generated-token-history.ts +57 -0
  86. package/src/{serve → server}/http.ts +25 -13
  87. package/src/{serve → server}/inference-request.ts +5 -5
  88. package/src/server/job-routes.ts +20 -0
  89. package/src/server/media-prompt.ts +63 -0
  90. package/src/server/memory-completion-client.ts +46 -0
  91. package/src/server/model-routes.ts +192 -0
  92. package/src/{serve → server}/openai-wire.ts +3 -2
  93. package/src/{serve → server}/pipeline.ts +1 -1
  94. package/src/server/prompt-builder.ts +13 -0
  95. package/src/server/prompt-contracts.ts +17 -0
  96. package/src/server/proxy-routes.ts +187 -0
  97. package/src/server/publishing-routes.ts +41 -0
  98. package/src/{serve → server}/request-plan.ts +8 -8
  99. package/src/{serve → server}/request-prep.ts +82 -68
  100. package/src/{serve → server}/responses-client.ts +30 -13
  101. package/src/{responses.ts → server/responses.ts} +12 -93
  102. package/src/server/routes.ts +205 -0
  103. package/src/server/start.ts +72 -0
  104. package/src/server/status-routes.ts +108 -0
  105. package/src/{serve → server}/text-completion-stage.ts +25 -4
  106. package/src/server/text-prompt.ts +12 -0
  107. package/src/{serve → server}/token-streams.ts +36 -38
  108. package/src/server/worker-routes.ts +311 -0
  109. package/src/storage/paths.ts +61 -0
  110. package/src/web/assets.ts +37 -0
  111. package/src/web/{src → browser}/api.ts +5 -23
  112. package/src/web/browser/chat-host.ts +17 -0
  113. package/src/web/browser/installed-panels.ts +6 -0
  114. package/src/web/browser/main.ts +55 -0
  115. package/src/web/browser/markdown.ts +279 -0
  116. package/src/web/browser/memory-panel.ts +9 -0
  117. package/src/web/browser/palette.ts +64 -0
  118. package/src/web/browser/protocol.ts +13 -0
  119. package/src/web/browser/shell.ts +467 -0
  120. package/src/web/{src → browser}/status.ts +32 -36
  121. package/src/web/build.ts +51 -0
  122. package/src/web/public/app.html +1054 -0
  123. package/src/web/{sw.js → public/sw.js} +4 -3
  124. package/src/web/{vendor → public/vendor}/hljs-theme.css +2 -2
  125. package/src/web/{vendor → public/vendor}/hljs.js +2 -2
  126. package/THIRD_PARTY_LICENSES.md +0 -51
  127. package/docs/README.md +0 -94
  128. package/docs/archive/hlg-sampling.md +0 -350
  129. package/docs/archive/investigations/audio-input-plan.md +0 -356
  130. package/docs/archive/investigations/chunk-finetune-experiment.md +0 -84
  131. package/docs/archive/investigations/colibri-glm52-port.md +0 -1486
  132. package/docs/archive/investigations/colibri-oracle-pin.md +0 -1284
  133. package/docs/archive/investigations/curve-bisector-routing.md +0 -123
  134. package/docs/archive/investigations/decode-roofline-lookagain.md +0 -348
  135. package/docs/archive/investigations/diffusion-gemma-port.md +0 -602
  136. package/docs/archive/investigations/dspark-handoff.md +0 -224
  137. package/docs/archive/investigations/expert-offload-single-user-moe.md +0 -292
  138. package/docs/archive/investigations/faithful-l1-consolidation.md +0 -310
  139. package/docs/archive/investigations/hlg-sampling-investigation.md +0 -970
  140. package/docs/archive/investigations/indexshare-performance-spike.md +0 -357
  141. package/docs/archive/investigations/kernel-perf-review-2026-07.md +0 -171
  142. package/docs/archive/investigations/lab-build-journal.md +0 -157
  143. package/docs/archive/investigations/minicpm5-decode-megakernel.md +0 -489
  144. package/docs/archive/investigations/openjarvis-fit.md +0 -198
  145. package/docs/archive/investigations/openwiki-evaluation.md +0 -249
  146. package/docs/archive/investigations/optimization_plan.md +0 -346
  147. package/docs/archive/investigations/orpo-base-uf-experiment-and-directions.md +0 -120
  148. package/docs/archive/investigations/orpo-flash-cce-pin-leak.md +0 -167
  149. package/docs/archive/investigations/orpo-uf-testing-handoff.md +0 -126
  150. package/docs/archive/investigations/pareto-specialized-runtime-findings.md +0 -92
  151. package/docs/archive/investigations/pi-builtin-investigation.md +0 -308
  152. package/docs/archive/investigations/pre-colibri-stabilization.md +0 -329
  153. package/docs/archive/investigations/qwen38-27b-campaign-log.md +0 -1092
  154. package/docs/archive/investigations/segmented-backward-handoff.md +0 -148
  155. package/docs/archive/investigations/starter-model-port-handoff.md +0 -342
  156. package/docs/archive/investigations/steel-flash-cce-handoff.md +0 -489
  157. package/docs/archive/investigations/the-dreaming-handoff.md +0 -141
  158. package/docs/archive/investigations/trainer-validation-experiment.md +0 -173
  159. package/docs/archive/planning/chat-ui-vision-memory-first.md +0 -199
  160. package/docs/archive/planning/chat-ui-vision.md +0 -50
  161. package/docs/archive/planning/chunk-task-roadmap.md +0 -77
  162. package/docs/archive/planning/curve-sampler-research-plan.md +0 -331
  163. package/docs/archive/planning/journal.md +0 -316
  164. package/docs/archive/planning/memory-docs-and-dag-plan.md +0 -194
  165. package/docs/archive/planning/pi-integration-review.md +0 -178
  166. package/docs/archive/planning/release-notes-v0.0.10.md +0 -57
  167. package/docs/archive/planning/release-notes-v0.0.11.md +0 -239
  168. package/docs/archive/planning/release-notes-v0.0.12.md +0 -80
  169. package/docs/archive/planning/release-notes-v0.0.13.md +0 -77
  170. package/docs/archive/planning/release-notes-v0.0.9.md +0 -122
  171. package/docs/archive/planning/release-notes-v0.2.0.md +0 -49
  172. package/docs/archive/planning/release-notes-v0.3.0.md +0 -35
  173. package/docs/archive/planning/web-ui-pass-plan.md +0 -213
  174. package/docs/archive/planning/website-readme-pass-plan.md +0 -224
  175. package/docs/design/batching.md +0 -1841
  176. package/docs/design/decode-speed-program.md +0 -2349
  177. package/docs/design/dreaming-nightly-pipeline.md +0 -684
  178. package/docs/design/generic-model-support.md +0 -705
  179. package/docs/design/kv-cache.md +0 -824
  180. package/docs/design/orpo-training.md +0 -680
  181. package/docs/design/speculative-decoding.md +0 -1990
  182. package/docs/design/turboquant.md +0 -2212
  183. package/docs/design/unified-engine-frontier-plan.md +0 -1880
  184. package/docs/design/web-chat-redesign.md +0 -695
  185. package/docs/planning/PRODUCT_ROADMAP.md +0 -594
  186. package/docs/planning/ResearchTopics.md +0 -119
  187. package/docs/planning/release-notes-v0.4.0.md +0 -45
  188. package/docs/planning/video-series.md +0 -421
  189. package/docs/reference/benchmarks.md +0 -3613
  190. package/docs/reference/cli.md +0 -585
  191. package/docs/reference/distribution.md +0 -355
  192. package/docs/reference/environment.md +0 -398
  193. package/docs/reference/glossary.md +0 -39
  194. package/docs/reference/library-api.md +0 -674
  195. package/docs/reference/memory.md +0 -260
  196. package/docs/reference/models.md +0 -334
  197. package/docs/reference/server-api.md +0 -1775
  198. package/docs/reference/server-config.md +0 -846
  199. package/docs/reference/training.md +0 -462
  200. package/docs/reference/troubleshooting.md +0 -158
  201. package/src/admission-plan.ts +0 -18
  202. package/src/audio/conformer.ts +0 -647
  203. package/src/audio/decode.ts +0 -133
  204. package/src/audio/features.ts +0 -202
  205. package/src/audio/transcode.ts +0 -67
  206. package/src/backends/mlx/assistant-target.ts +0 -46
  207. package/src/backends/mlx/autoregressive-draft-rows.ts +0 -120
  208. package/src/backends/mlx/autoregressive.ts +0 -108
  209. package/src/backends/mlx/batch-group.ts +0 -1790
  210. package/src/backends/mlx/cache-attachment.ts +0 -67
  211. package/src/backends/mlx/cache-layout.ts +0 -31
  212. package/src/backends/mlx/checkpoint-state.ts +0 -53
  213. package/src/backends/mlx/continuation-persistence.ts +0 -106
  214. package/src/backends/mlx/continuation-request.ts +0 -43
  215. package/src/backends/mlx/continuation.ts +0 -77
  216. package/src/backends/mlx/diffusion.ts +0 -39
  217. package/src/backends/mlx/draft-target.ts +0 -32
  218. package/src/backends/mlx/evaluation.ts +0 -105
  219. package/src/backends/mlx/fill-append.ts +0 -45
  220. package/src/backends/mlx/full-kv-row-append.ts +0 -32
  221. package/src/backends/mlx/full-kv-row-donor.ts +0 -54
  222. package/src/backends/mlx/full-transitioning-kv-rows.ts +0 -65
  223. package/src/backends/mlx/gateway-binding.ts +0 -130
  224. package/src/backends/mlx/graph.ts +0 -38
  225. package/src/backends/mlx/kv-codec.ts +0 -9
  226. package/src/backends/mlx/kv-maintenance.ts +0 -133
  227. package/src/backends/mlx/kv-tensor-rows.ts +0 -212
  228. package/src/backends/mlx/memory-client.ts +0 -189
  229. package/src/backends/mlx/methods.ts +0 -90
  230. package/src/backends/mlx/model-prompt.ts +0 -164
  231. package/src/backends/mlx/model-serving.ts +0 -79
  232. package/src/backends/mlx/prefill-cohort.ts +0 -89
  233. package/src/backends/mlx/prefill-rows.ts +0 -200
  234. package/src/backends/mlx/prefill.ts +0 -33
  235. package/src/backends/mlx/projected-context-rows.ts +0 -72
  236. package/src/backends/mlx/request-policy.ts +0 -20
  237. package/src/backends/mlx/request-state-policy.ts +0 -41
  238. package/src/backends/mlx/rollback.ts +0 -89
  239. package/src/backends/mlx/row-sampling.ts +0 -49
  240. package/src/backends/mlx/serial-executor.ts +0 -242
  241. package/src/backends/mlx/speculative-group.ts +0 -359
  242. package/src/backends/mlx/speculative-round.ts +0 -161
  243. package/src/backends/mlx/speculative.ts +0 -114
  244. package/src/backends/mlx/state-rows.ts +0 -104
  245. package/src/backends/mlx/state-views.ts +0 -46
  246. package/src/backends/mlx/target-layout-capability.ts +0 -35
  247. package/src/backends/mlx/text-engine.ts +0 -73
  248. package/src/backends/mlx/transitioning-kv-rows.ts +0 -165
  249. package/src/chat-template.ts +0 -382
  250. package/src/cli.ts +0 -3168
  251. package/src/config.ts +0 -399
  252. package/src/contracts/completion.ts +0 -16
  253. package/src/contracts/execution.ts +0 -60
  254. package/src/contracts/generation.ts +0 -103
  255. package/src/contracts/host.ts +0 -7
  256. package/src/contracts/pi-provider.ts +0 -30
  257. package/src/contracts/pi.ts +0 -259
  258. package/src/contracts/prefix-cache.ts +0 -23
  259. package/src/contracts/resources.ts +0 -25
  260. package/src/contracts/sampling.ts +0 -23
  261. package/src/contracts/scheduling.ts +0 -45
  262. package/src/contracts/tsconfig.json +0 -14
  263. package/src/dataset/generators.ts +0 -1087
  264. package/src/dataset/index.ts +0 -12
  265. package/src/dataset/job.ts +0 -81
  266. package/src/dataset/llm.ts +0 -71
  267. package/src/dataset/registry.ts +0 -528
  268. package/src/diffusion/diffusion-generate.ts +0 -507
  269. package/src/download.ts +0 -505
  270. package/src/embed.ts +0 -77
  271. package/src/engine/admission.ts +0 -59
  272. package/src/engine/cancellation.ts +0 -38
  273. package/src/engine/engine.ts +0 -56
  274. package/src/engine/execution-coordinator.ts +0 -70
  275. package/src/engine/execution-plan.ts +0 -53
  276. package/src/engine/resources.ts +0 -53
  277. package/src/engine/scheduler.ts +0 -72
  278. package/src/engine/session.ts +0 -247
  279. package/src/eval/README.md +0 -130
  280. package/src/eval/ifeval.ts +0 -81
  281. package/src/eval/kl-prompts.ts +0 -29
  282. package/src/eval/kl.ts +0 -590
  283. package/src/eval/perplexity.ts +0 -164
  284. package/src/eval/quality-db.ts +0 -92
  285. package/src/eval/reference-logits.ts +0 -207
  286. package/src/eval/runner.ts +0 -124
  287. package/src/eval/score.ts +0 -49
  288. package/src/eval/tasks/bfcl.ts +0 -535
  289. package/src/eval/tasks/gsm8k.ts +0 -98
  290. package/src/eval/tasks/hashhop.ts +0 -139
  291. package/src/eval/tasks/humaneval.ts +0 -275
  292. package/src/eval/tasks/ifeval.ts +0 -525
  293. package/src/eval/tasks/mmlu.ts +0 -157
  294. package/src/eval/ultrafeedback.ts +0 -60
  295. package/src/evaldb.ts +0 -199
  296. package/src/expert-io.ts +0 -393
  297. package/src/expert-offload-build.ts +0 -77
  298. package/src/expert-offload.ts +0 -76
  299. package/src/expert-residency.ts +0 -1134
  300. package/src/expert-usage.ts +0 -415
  301. package/src/fill/echo-index.ts +0 -188
  302. package/src/fill/fill-session.ts +0 -452
  303. package/src/fill/proposal.ts +0 -68
  304. package/src/fill/schema-rows.ts +0 -506
  305. package/src/fill/tool-boundary.ts +0 -103
  306. package/src/fit.ts +0 -249
  307. package/src/generate.ts +0 -1346
  308. package/src/grammar.ts +0 -591
  309. package/src/harness-pi.ts +0 -184
  310. package/src/hf-push.ts +0 -628
  311. package/src/hub-rest.ts +0 -242
  312. package/src/index.ts +0 -63
  313. package/src/inference/denoising.ts +0 -17
  314. package/src/inference/draft-acceptance.ts +0 -41
  315. package/src/inference/draft-output.ts +0 -49
  316. package/src/inference/graph.ts +0 -33
  317. package/src/inference/prefill.ts +0 -39
  318. package/src/inference/rollback.ts +0 -7
  319. package/src/jobs/index.ts +0 -31
  320. package/src/jobs/job-entry.ts +0 -107
  321. package/src/jobs/task-client.ts +0 -19
  322. package/src/jobs/types.ts +0 -2
  323. package/src/kv-scheme.ts +0 -257
  324. package/src/kv-store.ts +0 -1099
  325. package/src/lab/README.md +0 -18
  326. package/src/lab/curve/curve-designer.html +0 -303
  327. package/src/lab/curve/curve-sampler.ts +0 -122
  328. package/src/lab/expert-trace/expert-trace.ts +0 -69
  329. package/src/lab/paged-kv/paged-kv-rows.ts +0 -83
  330. package/src/lab/paged-kv/paged-kv.ts +0 -270
  331. package/src/library.ts +0 -40
  332. package/src/lora.ts +0 -408
  333. package/src/md-modules.d.ts +0 -49
  334. package/src/media-fetch.ts +0 -442
  335. package/src/memory/article.ts +0 -558
  336. package/src/memory/chunk-validation.ts +0 -150
  337. package/src/memory/chunk.ts +0 -318
  338. package/src/memory/cluster.ts +0 -351
  339. package/src/memory/coverage.ts +0 -134
  340. package/src/memory/crosslink.ts +0 -0
  341. package/src/memory/db.ts +0 -253
  342. package/src/memory/entity.ts +0 -203
  343. package/src/memory/gate.ts +0 -187
  344. package/src/memory/history.ts +0 -152
  345. package/src/memory/ingest.ts +0 -335
  346. package/src/memory/model.ts +0 -183
  347. package/src/memory/normalize.ts +0 -671
  348. package/src/memory/parse.ts +0 -22
  349. package/src/memory/pipeline.ts +0 -275
  350. package/src/memory/prompts.ts +0 -65
  351. package/src/memory/query.ts +0 -334
  352. package/src/memory/reconcile.ts +0 -734
  353. package/src/memory/redirect.ts +0 -108
  354. package/src/memory/reindex.ts +0 -325
  355. package/src/memory/resolve.ts +0 -307
  356. package/src/memory/rest.ts +0 -245
  357. package/src/memory/route.ts +0 -272
  358. package/src/memory/schedule.ts +0 -165
  359. package/src/memory/stages.ts +0 -777
  360. package/src/memory/synthesize.ts +0 -1638
  361. package/src/memory/tools.ts +0 -749
  362. package/src/memory/vault.ts +0 -1194
  363. package/src/memory/wikify.ts +0 -1064
  364. package/src/mlx/array.ts +0 -388
  365. package/src/mlx/autograd.ts +0 -304
  366. package/src/mlx/checkpoint.ts +0 -149
  367. package/src/mlx/compile.ts +0 -168
  368. package/src/mlx/custom-vjp.ts +0 -188
  369. package/src/mlx/ffi.ts +0 -512
  370. package/src/mlx/materialize.ts +0 -23
  371. package/src/mlx/metal-kernel.ts +0 -159
  372. package/src/mlx/ops.ts +0 -1021
  373. package/src/mlx/quantized-rows.ts +0 -36
  374. package/src/mlx/token-bitmask.ts +0 -52
  375. package/src/mlx/turboquant-kv-decode.ts +0 -82
  376. package/src/mlx/turboquant-kv-inverse.ts +0 -122
  377. package/src/mlx/turboquant-ops.ts +0 -861
  378. package/src/mlx/turboquant-tables.ts +0 -237
  379. package/src/mmap.ts +0 -90
  380. package/src/model/artifact-identity.ts +0 -73
  381. package/src/model/batched-kv.ts +0 -235
  382. package/src/model/batched-mask.ts +0 -279
  383. package/src/model/batched-quant.ts +0 -212
  384. package/src/model/batched-quantized-kv.ts +0 -144
  385. package/src/model/batched-rotating-quant.ts +0 -353
  386. package/src/model/batched-rotating-state.ts +0 -149
  387. package/src/model/batched-rotating.ts +0 -338
  388. package/src/model/batched-row-storage.ts +0 -179
  389. package/src/model/batched-ssm.ts +0 -52
  390. package/src/model/batched-turboquant-kv.ts +0 -93
  391. package/src/model/compiled-decode.ts +0 -712
  392. package/src/model/decoded-kv-donor.ts +0 -12
  393. package/src/model/delayed-quantized-kv.ts +0 -72
  394. package/src/model/delayed-rotating-quantized-kv.ts +0 -93
  395. package/src/model/delayed-turboquant-kv.ts +0 -55
  396. package/src/model/diffusion-gemma.ts +0 -1026
  397. package/src/model/factory.ts +0 -189
  398. package/src/model/fingerprint.ts +0 -33
  399. package/src/model/flash-attention.ts +0 -694
  400. package/src/model/full-prefill-padding.ts +0 -56
  401. package/src/model/full-prefill-row.ts +0 -105
  402. package/src/model/gemma4-base.ts +0 -2351
  403. package/src/model/gemma4.ts +0 -1315
  404. package/src/model/generated/gemma4-12b.ts +0 -429
  405. package/src/model/generated/gemma4-26b.ts +0 -448
  406. package/src/model/generated/gemma4-e4b.ts +0 -717
  407. package/src/model/generated/index.ts +0 -18
  408. package/src/model/glm52-atlas.ts +0 -511
  409. package/src/model/glm52-cache.ts +0 -792
  410. package/src/model/glm52-config.ts +0 -262
  411. package/src/model/glm52-container.ts +0 -305
  412. package/src/model/glm52-coupling.ts +0 -388
  413. package/src/model/glm52-dsa.ts +0 -606
  414. package/src/model/glm52-expert-layout.ts +0 -253
  415. package/src/model/glm52-layout.ts +0 -152
  416. package/src/model/glm52-memory.ts +0 -500
  417. package/src/model/glm52-mla.ts +0 -792
  418. package/src/model/glm52-moe.ts +0 -223
  419. package/src/model/glm52-pilot.ts +0 -537
  420. package/src/model/glm52-reference.ts +0 -445
  421. package/src/model/glm52-residency.ts +0 -557
  422. package/src/model/glm52-resident-weights.ts +0 -135
  423. package/src/model/glm52-streamed-experts.ts +0 -453
  424. package/src/model/glm52-streamed-metal.ts +0 -610
  425. package/src/model/glm52-weights.ts +0 -503
  426. package/src/model/glm52.ts +0 -1132
  427. package/src/model/implementation.ts +0 -69
  428. package/src/model/kv-attention-view.ts +0 -67
  429. package/src/model/minicpm5.ts +0 -376
  430. package/src/model/profile.ts +0 -443
  431. package/src/model/qwen-append.ts +0 -12
  432. package/src/model/qwen-conv.ts +0 -52
  433. package/src/model/qwen3-delta-state.ts +0 -74
  434. package/src/model/qwen3-delta.ts +0 -451
  435. package/src/model/qwen3-moe.ts +0 -404
  436. package/src/model/qwen3-mrope.ts +0 -252
  437. package/src/model/qwen3.ts +0 -277
  438. package/src/model/qwen3_5-checkpoint.ts +0 -164
  439. package/src/model/qwen3_5.ts +0 -819
  440. package/src/model/rotating-kv-donor.ts +0 -35
  441. package/src/model/rotating-kv-layout.ts +0 -262
  442. package/src/model/rotating-row-transaction.ts +0 -35
  443. package/src/model/speculative-rotating-kv.ts +0 -101
  444. package/src/model/ssm-prefill-padding.ts +0 -39
  445. package/src/model/support.ts +0 -102
  446. package/src/model/trellis-balanced-scatter.ts +0 -51
  447. package/src/model/trellis-linear.ts +0 -672
  448. package/src/model/trellis-shared-m.ts +0 -151
  449. package/src/model/trellis-shared-scatter.ts +0 -144
  450. package/src/model/trellis-splitk-prefill.ts +0 -158
  451. package/src/model/trellis-tiled-prefill.ts +0 -136
  452. package/src/model/trellis-vector-expand.ts +0 -62
  453. package/src/model/trellis-wide-prefill.ts +0 -94
  454. package/src/model/turboquant-codec.ts +0 -83
  455. package/src/model/universal/archs.ts +0 -332
  456. package/src/model/universal/dense.ts +0 -612
  457. package/src/model/universal/modules.ts +0 -170
  458. package/src/model/universal/rope.ts +0 -381
  459. package/src/native/expert_io.c +0 -523
  460. package/src/native/frame_extract.swift +0 -140
  461. package/src/native-pack.ts +0 -162
  462. package/src/pi-provider.ts +0 -53
  463. package/src/pi-session.ts +0 -81
  464. package/src/pi-terminal.ts +0 -274
  465. package/src/pi-web.ts +0 -1932
  466. package/src/preflight.ts +0 -158
  467. package/src/prompt-cache.ts +0 -460
  468. package/src/quantize/allocator.ts +0 -627
  469. package/src/quantize/atomic-output.ts +0 -38
  470. package/src/quantize/calibration.ts +0 -181
  471. package/src/quantize/config-writer.ts +0 -145
  472. package/src/quantize/index.ts +0 -84
  473. package/src/quantize/job.ts +0 -162
  474. package/src/quantize/quantizer.ts +0 -666
  475. package/src/quantize/rotate.ts +0 -603
  476. package/src/quantize/safetensors-writer.ts +0 -249
  477. package/src/quantize/sensitivity.ts +0 -216
  478. package/src/quantize/trellis.ts +0 -586
  479. package/src/quantize/weight-transform.ts +0 -292
  480. package/src/registry.ts +0 -602
  481. package/src/runtime-config.ts +0 -81
  482. package/src/safetensors.ts +0 -158
  483. package/src/sampler.ts +0 -1011
  484. package/src/serve/admin-routes.ts +0 -184
  485. package/src/serve/assets/curve-terrain.html +0 -105
  486. package/src/serve/assets/training-inference-map.html +0 -575
  487. package/src/serve/aux-routes.ts +0 -132
  488. package/src/serve/batch-scheduler.ts +0 -8
  489. package/src/serve/checkpoint-identity.ts +0 -64
  490. package/src/serve/generation-gateway.ts +0 -496
  491. package/src/serve/isolate.ts +0 -609
  492. package/src/serve/kv-budget.ts +0 -14
  493. package/src/serve/lab-routes.ts +0 -341
  494. package/src/serve/lane-registry.ts +0 -45
  495. package/src/serve/model-admin-routes.ts +0 -155
  496. package/src/serve/model-binding.ts +0 -43
  497. package/src/serve/model-host.ts +0 -511
  498. package/src/serve/parent-application.ts +0 -56
  499. package/src/serve/prompt-response-trace.ts +0 -147
  500. package/src/serve/response-lease.ts +0 -24
  501. package/src/serve/session-search.ts +0 -326
  502. package/src/serve/static-routes.ts +0 -101
  503. package/src/server.ts +0 -1314
  504. package/src/spec/assistant-rows.ts +0 -92
  505. package/src/spec/assistant-source.ts +0 -126
  506. package/src/spec/cache-identity.ts +0 -22
  507. package/src/spec/deepspec-rows.ts +0 -29
  508. package/src/spec/deepspec-source.ts +0 -145
  509. package/src/spec/dflash-source.ts +0 -149
  510. package/src/spec/drafter.ts +0 -304
  511. package/src/spec/dspark/ab-stats.ts +0 -136
  512. package/src/spec/dspark/calibration.ts +0 -112
  513. package/src/spec/dspark/data-dflash.ts +0 -156
  514. package/src/spec/dspark/data.ts +0 -201
  515. package/src/spec/dspark/deepspec-module.ts +0 -776
  516. package/src/spec/dspark/generate-dflash.ts +0 -245
  517. package/src/spec/dspark/generate.ts +0 -290
  518. package/src/spec/dspark/loader.ts +0 -23
  519. package/src/spec/dspark/loss.ts +0 -213
  520. package/src/spec/dspark/module-dflash.ts +0 -560
  521. package/src/spec/dspark/module.ts +0 -540
  522. package/src/spec/dspark/quantize-drafter.ts +0 -91
  523. package/src/spec/dspark/sample.ts +0 -112
  524. package/src/spec/generate.ts +0 -276
  525. package/src/spec/glm52-mtp-graph.ts +0 -29
  526. package/src/spec/glm52-mtp-rows.ts +0 -132
  527. package/src/spec/glm52-mtp-source.ts +0 -317
  528. package/src/spec/glm52-mtp-state.ts +0 -33
  529. package/src/spec/ngram-source.ts +0 -204
  530. package/src/spec/projected-draft-rows.ts +0 -73
  531. package/src/spec/qwen-mtp-module.ts +0 -171
  532. package/src/spec/qwen-mtp-rows.ts +0 -219
  533. package/src/spec/qwen-mtp-source.ts +0 -383
  534. package/src/spec/qwen-mtp-state.ts +0 -30
  535. package/src/spec/serve-loop.ts +0 -585
  536. package/src/spec/source.ts +0 -215
  537. package/src/spec/two-model-rows.ts +0 -61
  538. package/src/spec/two-model.ts +0 -113
  539. package/src/ssd-cache.ts +0 -372
  540. package/src/ssd-durability.ts +0 -211
  541. package/src/tokenizer.ts +0 -335
  542. package/src/tool-approvals.ts +0 -131
  543. package/src/tool-call.ts +0 -695
  544. package/src/train/dataset.ts +0 -484
  545. package/src/train/diffusion-lora.ts +0 -216
  546. package/src/train/export.ts +0 -32
  547. package/src/train/flash-cce.ts +0 -1234
  548. package/src/train/forward.ts +0 -266
  549. package/src/train/fuse.ts +0 -243
  550. package/src/train/index.ts +0 -46
  551. package/src/train/job.ts +0 -205
  552. package/src/train/lora-params.ts +0 -254
  553. package/src/train/loss.ts +0 -1591
  554. package/src/train/merge.ts +0 -195
  555. package/src/train/optimizer.ts +0 -183
  556. package/src/train/prefix-shared.ts +0 -490
  557. package/src/train/rank.ts +0 -137
  558. package/src/train/segmented.ts +0 -1828
  559. package/src/train/steel-qmm-header.ts +0 -1461
  560. package/src/train/trainer.ts +0 -1221
  561. package/src/train/watch.ts +0 -542
  562. package/src/vision/diffusion-vision.ts +0 -620
  563. package/src/vision/embedder.ts +0 -168
  564. package/src/vision/preprocess.ts +0 -247
  565. package/src/vision/prompt.ts +0 -373
  566. package/src/vision/qwen3vl-preprocess.ts +0 -409
  567. package/src/vision/qwen3vl-prompt.ts +0 -196
  568. package/src/vision/qwen3vl-tower.ts +0 -555
  569. package/src/vision/siglip.ts +0 -599
  570. package/src/vision/video-frames.ts +0 -148
  571. package/src/web/app.html +0 -2143
  572. package/src/web/app.js +0 -5703
  573. package/src/web/skills/memory/SKILL.md +0 -120
  574. package/src/web/skills/web-research/SKILL.md +0 -46
  575. package/src/web/skills.ts +0 -97
  576. package/src/web/src/adapters-panel.ts +0 -288
  577. package/src/web/src/assistant.ts +0 -451
  578. package/src/web/src/chat.ts +0 -1057
  579. package/src/web/src/composer.ts +0 -959
  580. package/src/web/src/dataset.ts +0 -132
  581. package/src/web/src/finetune.ts +0 -267
  582. package/src/web/src/hub.ts +0 -428
  583. package/src/web/src/main.ts +0 -98
  584. package/src/web/src/markdown.ts +0 -0
  585. package/src/web/src/memory-panel.ts +0 -572
  586. package/src/web/src/model-picker.ts +0 -159
  587. package/src/web/src/palette.ts +0 -346
  588. package/src/web/src/protocol.ts +0 -26
  589. package/src/web/src/quantize.ts +0 -167
  590. package/src/web/src/rag.ts +0 -274
  591. package/src/web/src/sessions.ts +0 -469
  592. package/src/web/src/shell.ts +0 -804
  593. package/src/web/src/ui-catalog.ts +0 -78
  594. package/src/web/vendor/README.md +0 -61
  595. package/src/web-assets.ts +0 -47
  596. package/src/web-tools.ts +0 -478
  597. package/src/weights.ts +0 -255
  598. /package/src/{serve → server}/completion-sink.ts +0 -0
  599. /package/src/web/{icon.svg → public/icon.svg} +0 -0
  600. /package/src/web/{manifest.webmanifest → public/manifest.webmanifest} +0 -0
  601. /package/src/web/{vendor → public/vendor}/hljs-LICENSE +0 -0
@@ -1,804 +0,0 @@
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
- // App shell: DOM helpers, toast, HF settings modal, focus trap, theme,
5
- // keyboard shortcut sheet, mobile drawer, router, connection pill + model
6
- // identity polling, and the global keydown sweep. Behavior-identical port
7
- // of the original inline <script> in app.html. Owns the `controllers`
8
- // registry that the per-tab modules populate (see main.ts for the exact
9
- // registration order, which callers must preserve).
10
-
11
- import { api } from "./api";
12
- import type { ApiEnvelope } from "./protocol";
13
-
14
- /* ════════════════════════════════════════════════════════════════════
15
- SHARED HELPERS
16
- ════════════════════════════════════════════════════════════════════ */
17
- export const $ = (id: string): HTMLElement => document.getElementById(id) as HTMLElement;
18
- export const gb = (b: number | null | undefined): string => b == null ? "—" : (b / 2 ** 30).toFixed(2) + " GB";
19
- export const mb = (b: number | null | undefined): string => b == null ? "—" : (b / 2 ** 20).toFixed(1) + " MB";
20
- export const num = (n: number | null | undefined): string => n == null ? "—" : Math.round(n).toLocaleString();
21
-
22
- /** Build a DOM node quickly. */
23
- export function el<K extends keyof HTMLElementTagNameMap>(tag: K, cls?: string, parent?: Element | null): HTMLElementTagNameMap[K] {
24
- const e = document.createElement(tag);
25
- if (cls) e.className = cls;
26
- if (parent) parent.appendChild(e);
27
- return e;
28
- }
29
-
30
- /** Toast notification. kind: "" | "ok" | "err". */
31
- export function toast(msg: string, kind = "", ms = 4200): void {
32
- const t = el("div", "toast " + kind, $("toasts"));
33
- t.textContent = msg;
34
- setTimeout(() => { t.style.transition = "opacity .4s,transform .4s"; t.style.opacity = "0"; t.style.transform = "translateY(10px)"; setTimeout(() => t.remove(), 420); }, ms);
35
- }
36
-
37
- const injectedStyleIds = new Set<string>();
38
-
39
- /** Append a `<style>` block to `<head>` exactly once per `id`, keyed
40
- * independent of call order/count (idempotent — safe to call from a
41
- * module's own top-level init every boot). Used by modules that build
42
- * their DOM entirely via createElement (no app.html markup of their own)
43
- * and therefore have nowhere else to put CSS for their new classes — the
44
- * command palette (palette.ts) and the sidebar's full-text-search /
45
- * export-menu additions (sessions.ts) both use this instead of touching
46
- * app.html's <style> block, which is out of scope for the modules that
47
- * introduce them. All rules should reference the existing `:root` design
48
- * tokens (var(--card), var(--hairline), etc.) so injected chrome matches
49
- * the current theme (dark/light/auto) automatically — those custom
50
- * properties cascade regardless of where in the document this tag lands. */
51
- export function injectStyles(id: string, css: string): void {
52
- if (injectedStyleIds.has(id)) return;
53
- injectedStyleIds.add(id);
54
- const style = document.createElement("style");
55
- style.id = id;
56
- style.textContent = css;
57
- document.head.appendChild(style);
58
- }
59
-
60
- /* ════════════════════════════════════════════════════════════════════
61
- HUGGING FACE — shared token settings + push-to-hub flow
62
- ════════════════════════════════════════════════════════════════════ */
63
-
64
- /** Reflect saved-token state on the nav gear. */
65
- export async function refreshHfGear(): Promise<boolean> {
66
- try {
67
- const d = await api("/api/settings/hf-token");
68
- const saved = !!(d && d.ok && (d as { hasToken?: boolean }).hasToken);
69
- $("nav-hf").classList.toggle("saved", saved);
70
- $("nav-hf").title = saved ? "Hugging Face token saved · click to replace" : "Hugging Face token settings";
71
- return saved;
72
- } catch { return false; }
73
- }
74
-
75
- let hfOverlayTrap: FocusTrap | null = null; // set by initHfSettings() below
76
-
77
- function openHfSettings(): void {
78
- const ov = $("hf-overlay"); ov.classList.add("open");
79
- if (hfOverlayTrap) hfOverlayTrap.capture();
80
- ($("hf-token-input") as HTMLInputElement).value = ""; $("hf-settings-msg").innerHTML = "";
81
- $("hf-state").textContent = "Checking for a saved token…";
82
- api("/api/settings/hf-token").then((d) => {
83
- const saved = !!(d && d.ok && (d as { hasToken?: boolean }).hasToken);
84
- $("hf-state").innerHTML = saved
85
- ? "A write token is <strong>saved</strong>. Enter a new one below to replace it."
86
- : "No token saved yet. Add a write token to push models and datasets to the Hub.";
87
- }).catch(() => { $("hf-state").textContent = "Could not reach the server."; });
88
- setTimeout(() => $("hf-token-input").focus(), 50);
89
- }
90
- function closeHfSettings(): void {
91
- $("hf-overlay").classList.remove("open");
92
- if (hfOverlayTrap) hfOverlayTrap.restore();
93
- }
94
-
95
- async function saveHfToken(): Promise<void> {
96
- const tokenInput = $("hf-token-input") as HTMLInputElement;
97
- const token = tokenInput.value.trim();
98
- const msg = $("hf-settings-msg");
99
- if (!token) { msg.innerHTML = '<div class="flash err">Enter a token first.</div>'; return; }
100
- const btn = $("hf-save") as HTMLButtonElement; btn.disabled = true;
101
- const d = await api("/api/settings/hf-token", { method: "POST", body: { token } }).catch((): ApiEnvelope => ({ ok: false, error: "request failed" }));
102
- btn.disabled = false;
103
- if (!d.ok) { msg.innerHTML = '<div class="flash err">' + escHtml(d.error || "could not save token") + "</div>"; return; }
104
- msg.innerHTML = '<div class="flash ok">Token saved to <code>~/.mlx-bun/hf.json</code>.</div>';
105
- tokenInput.value = "";
106
- refreshHfGear();
107
- toast("Hugging Face token saved", "ok");
108
- }
109
-
110
- // Local esc() copy to avoid a circular import with markdown.ts (shell.ts
111
- // only needs escaping, not the full markdown surface) — identical logic to
112
- // markdown.ts's esc().
113
- function escHtml(s: unknown): string {
114
- return String(s ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" } as Record<string, string>)[c]!);
115
- }
116
-
117
- export interface PushToHubOpts {
118
- kind: "quantize" | "finetune" | "dataset";
119
- job_id?: string;
120
- source_path?: string;
121
- }
122
-
123
- /**
124
- * Shared push-to-hub flow, rendered inline inside a done-step card.
125
- * pushToHub(panelEl, { kind, job_id?, source_path? })
126
- * kind: "quantize" | "finetune" | "dataset". Posts to /api/{kind}/push.
127
- * Walks: ensure token -> repo_id + private -> POST -> success/error flash.
128
- */
129
- export async function pushToHub(panel: HTMLElement, opts: PushToHubOpts): Promise<void> {
130
- const { kind, job_id, source_path } = opts;
131
- panel.innerHTML = '<div class="pushpanel"><div class="flash"><span class="shimmer">checking Hugging Face token…</span></div></div>';
132
- const wrap = panel.firstChild as HTMLElement;
133
- let hasToken = false;
134
- try { const s = await api("/api/settings/hf-token"); hasToken = !!(s && s.ok && (s as { hasToken?: boolean }).hasToken); } catch { /* treat as no token */ }
135
-
136
- const renderForm = () => {
137
- wrap.innerHTML =
138
- (!hasToken
139
- ? '<div class="field"><label>Hugging Face write token</label>' +
140
- '<input type="password" class="p-token" placeholder="hf_…" autocomplete="off" spellcheck="false">' +
141
- '<div class="hint">A <strong>write</strong> token from huggingface.co/settings/tokens. Stored locally at <code>~/.mlx-bun/hf.json</code>.</div></div>'
142
- : "") +
143
- '<div class="field"><label>Repository id</label>' +
144
- '<input type="text" class="p-repo" placeholder="you/my-' + escHtml(kind === "dataset" ? "dataset" : "model") + '" autocomplete="off"></div>' +
145
- '<div class="field"><label class="chk"><input type="checkbox" class="p-priv">Private repository</label></div>' +
146
- '<div class="p-msg"></div>' +
147
- '<div class="btnrow" style="margin-top:6px"><button class="btn primary sm p-go">' +
148
- (hasToken ? "Push" : "Save token &amp; push") + "</button></div>";
149
- (wrap.querySelector(".p-go") as HTMLButtonElement).onclick = go;
150
- const focusEl = wrap.querySelector(hasToken ? ".p-repo" : ".p-token") as HTMLElement | null;
151
- if (focusEl) focusEl.focus();
152
- };
153
-
154
- async function go(): Promise<void> {
155
- const msg = wrap.querySelector(".p-msg") as HTMLElement;
156
- const repo = ((wrap.querySelector(".p-repo") as HTMLInputElement).value || "").trim();
157
- const priv = (wrap.querySelector(".p-priv") as HTMLInputElement).checked;
158
- if (!repo) { msg.innerHTML = '<div class="flash err">Enter a repository id (e.g. <code>you/my-model</code>).</div>'; return; }
159
- const btn = wrap.querySelector(".p-go") as HTMLButtonElement; btn.disabled = true;
160
-
161
- if (!hasToken) {
162
- const token = ((wrap.querySelector(".p-token") as HTMLInputElement).value || "").trim();
163
- if (!token) { msg.innerHTML = '<div class="flash err">Enter a write token first.</div>'; btn.disabled = false; return; }
164
- const sv = await api("/api/settings/hf-token", { method: "POST", body: { token } }).catch((): ApiEnvelope => ({ ok: false, error: "request failed" }));
165
- if (!sv.ok) { msg.innerHTML = '<div class="flash err">' + escHtml(sv.error || "could not save token") + "</div>"; btn.disabled = false; return; }
166
- hasToken = true; refreshHfGear();
167
- }
168
-
169
- msg.innerHTML = '<div class="flash"><span class="shimmer">pushing to ' + escHtml(repo) + "…</span></div>";
170
- const body: Record<string, unknown> = { repo_id: repo, private: priv };
171
- if (job_id != null) body.job_id = job_id;
172
- if (source_path != null) body.source_path = source_path;
173
- const d = await api("/api/" + kind + "/push", { method: "POST", body }).catch((): ApiEnvelope => ({ ok: false, error: "request failed" }));
174
- btn.disabled = false;
175
- if (!d.ok) { msg.innerHTML = '<div class="flash err">' + escHtml(d.error || "push failed") + "</div>"; return; }
176
- const url = (d as { url?: string }).url || ("https://huggingface.co/" + repo);
177
- wrap.innerHTML = '<div class="flash ok">Pushed to <a href="' + escHtml(url) + '" target="_blank" rel="noopener" style="text-decoration:underline">' + escHtml(url) + "</a></div>";
178
- toast("Pushed to Hugging Face", "ok");
179
- }
180
-
181
- renderForm();
182
- }
183
-
184
- /* wire the nav gear + modal once at boot. The modal now also hosts the
185
- * Agent tools section (plan §5.4/§6.5/§9 Phase 2) — see the block below;
186
- * kept in this same init function since it's one modal, one open/close
187
- * lifecycle, one focus trap. */
188
- export function initHfSettings(): void {
189
- $("nav-hf").onclick = openHfSettings;
190
- $("hf-close").onclick = closeHfSettings;
191
- $("hf-save").onclick = saveHfToken;
192
- $("hf-overlay").addEventListener("click", (e) => { if (e.target === $("hf-overlay")) closeHfSettings(); });
193
- $("hf-token-input").addEventListener("keydown", (e) => { if ((e as KeyboardEvent).key === "Enter") saveHfToken(); });
194
- // Escape is handled globally by closeTopOverlay (plan §7 item 5 — one
195
- // Escape mechanism for every popover/overlay, not a bespoke one per modal).
196
- hfOverlayTrap = trapFocus($("hf-overlay"), () => $("hf-overlay").classList.contains("open"));
197
- refreshHfGear();
198
- initCodingToolsToggle();
199
- }
200
-
201
- /* ════════════════════════════════════════════════════════════════════
202
- AGENT TOOLS SETTINGS (plan §5.4/§6.5/§9 Phase 2) — the codingTools
203
- opt-in toggle + the durable "always allow" list. State of record for
204
- BOTH is server-side (the toggle's enforcement, and the tool-approvals
205
- config file); this module only reflects what the server last reported
206
- (coding_tools/tool_approvals ServerMessages, relayed here by chat.ts —
207
- shell.ts owns no WebSocket) plus the localStorage mirror that lets the
208
- checkbox restore its last-requested state before the first `ready`
209
- frame arrives.
210
- ════════════════════════════════════════════════════════════════════ */
211
- const CODING_TOOLS_KEY = "mlxbun.codingTools";
212
-
213
- /** Last requested state (this browser). The server is the actual source of
214
- * truth for enforcement — this is only UI restoration so the checkbox
215
- * doesn't flash unchecked while waiting on the first `ready`/`coding_tools`
216
- * frame after a reload. */
217
- export function storedCodingToolsPreference(): boolean {
218
- return localStorage.getItem(CODING_TOOLS_KEY) === "1";
219
- }
220
-
221
- function setStoredCodingToolsPreference(on: boolean): void {
222
- localStorage.setItem(CODING_TOOLS_KEY, on ? "1" : "0");
223
- }
224
-
225
- /** Reflect the server's coding_tools frame {active, pending} into the
226
- * checkbox + the honest status note. `active` is what THIS session's tool
227
- * surface actually contains; `pending` is the last request, which only
228
- * takes effect on the next new/opened/forked chat — the note says so
229
- * explicitly rather than implying the toggle is live immediately. */
230
- export function renderCodingToolsState(active: boolean, pending: boolean): void {
231
- const cb = $("settings-coding-tools") as HTMLInputElement | null;
232
- if (cb) cb.checked = pending;
233
- const note = $("settings-coding-tools-note");
234
- if (!note) return;
235
- if (pending && !active) {
236
- note.style.display = "";
237
- note.textContent = "Will apply starting with your next new chat (this chat keeps its current tools).";
238
- } else if (!pending && active) {
239
- note.style.display = "";
240
- note.textContent = "Turning this off won't remove tools from the CURRENT chat — start a new chat to fully disable.";
241
- } else {
242
- note.style.display = "none";
243
- note.textContent = "";
244
- }
245
- }
246
-
247
- /** Reflect the durable always-allow set (tool_approvals frame) into the
248
- * settings list, each row with a "forget" button. `onForget` is supplied
249
- * by chat.ts (it owns the WebSocket) — this module never sends frames
250
- * itself, matching pushToHub/refreshHfGear's api()-only pattern above. */
251
- export function renderToolApprovals(tools: readonly string[], onForget: (tool: string) => void): void {
252
- const list = $("settings-approvals-list");
253
- if (!list) return;
254
- if (tools.length === 0) {
255
- list.innerHTML = '<div class="settings-approvals-empty">No tools are set to always-allow yet.</div>';
256
- return;
257
- }
258
- list.innerHTML = tools.map((t) =>
259
- '<div class="settings-approval-row"><span class="satool">' + escHtml(t) +
260
- '</span><button class="saforget" data-tool="' + escHtml(t) + '">Forget</button></div>'
261
- ).join("");
262
- list.querySelectorAll<HTMLButtonElement>(".saforget").forEach((btn) => {
263
- btn.onclick = () => onForget(btn.dataset.tool || "");
264
- });
265
- }
266
-
267
- /** Wire the checkbox; the actual set_coding_tools WS send is delegated to
268
- * controllers.chat.setCodingTools (registered by chat.ts's init — same
269
- * cross-controller-call pattern as newChat/copyLastResponse above), since
270
- * shell.ts owns no WebSocket. controllers.chat is always populated by the
271
- * time a user can reach this modal (main.ts's boot order registers it
272
- * before any UI is interactive); the guard below is defensive only — if
273
- * it's ever missing, localStorage still records the preference (it's
274
- * re-sent on the next `ready` frame regardless), it just silently skips
275
- * the immediate WS send rather than throwing. */
276
- function initCodingToolsToggle(): void {
277
- const cb = $("settings-coding-tools") as HTMLInputElement | null;
278
- if (!cb) return;
279
- cb.checked = storedCodingToolsPreference();
280
- cb.onchange = () => {
281
- const on = cb.checked;
282
- setStoredCodingToolsPreference(on);
283
- const setCodingTools = controllers.chat && controllers.chat.setCodingTools as ((enabled: boolean) => void) | undefined;
284
- if (setCodingTools) setCodingTools(on);
285
- };
286
- }
287
-
288
- /* ════════════════════════════════════════════════════════════════════
289
- POPOVER / OVERLAY A11Y SWEEP (plan §7 item 5)
290
- A single reusable focus-trap: Tab/Shift+Tab cycle within the container
291
- while `isOpen()` is true, and focus returns to whatever triggered the
292
- overlay when it closes. Applied uniformly to the HF modal, the
293
- shortcut sheet, the sampling popover, and the drawer — one mechanism,
294
- not four bespoke ones.
295
- ════════════════════════════════════════════════════════════════════ */
296
- export interface FocusTrap {
297
- /** Call right before opening: remembers the trigger to restore focus to. */
298
- capture(): void;
299
- /** Call right after closing. */
300
- restore(): void;
301
- }
302
-
303
- export function trapFocus(container: HTMLElement, isOpen: () => boolean): FocusTrap {
304
- let lastFocused: HTMLElement | null = null;
305
- const focusables = (): HTMLElement[] => [...container.querySelectorAll(
306
- 'a[href],button:not([disabled]),textarea,input:not([disabled]),select,[tabindex]:not([tabindex="-1"])'
307
- )].filter((e) => (e as HTMLElement).offsetParent !== null || e === document.activeElement) as HTMLElement[];
308
- container.addEventListener("keydown", (e) => {
309
- const ke = e as KeyboardEvent;
310
- if (ke.key !== "Tab" || !isOpen()) return;
311
- const items = focusables();
312
- if (!items.length) return;
313
- const first = items[0]!, last = items[items.length - 1]!;
314
- if (ke.shiftKey && document.activeElement === first) { ke.preventDefault(); last.focus(); }
315
- else if (!ke.shiftKey && document.activeElement === last) { ke.preventDefault(); first.focus(); }
316
- });
317
- return {
318
- capture() { lastFocused = document.activeElement as HTMLElement | null; },
319
- restore() { if (lastFocused && lastFocused.focus) lastFocused.focus(); lastFocused = null; },
320
- };
321
- }
322
-
323
- /* ════════════════════════════════════════════════════════════════════
324
- THEME (plan §7): auto/dark/light, persisted, honors prefers-color-scheme
325
- when the choice is "auto" (the default). Ambient bloom/shimmer already
326
- respect prefers-reduced-motion via CSS above — this only handles color.
327
- ════════════════════════════════════════════════════════════════════ */
328
- const THEME_KEY = "mlxbun.theme"; // "auto" | "dark" | "light"
329
- const themeMedia = window.matchMedia("(prefers-color-scheme: light)");
330
-
331
- function effectiveTheme(choice: string): "dark" | "light" {
332
- if (choice === "dark" || choice === "light") return choice;
333
- return themeMedia.matches ? "light" : "dark";
334
- }
335
- function applyTheme(choice: string): void {
336
- document.documentElement.setAttribute("data-theme", effectiveTheme(choice));
337
- document.querySelectorAll<HTMLButtonElement>("#theme-toggle button").forEach((b) =>
338
- b.classList.toggle("active", b.dataset.themeChoice === choice));
339
- }
340
- export function setTheme(choice: string): void {
341
- localStorage.setItem(THEME_KEY, choice);
342
- applyTheme(choice);
343
- }
344
- export function initTheme(): void {
345
- const saved = localStorage.getItem(THEME_KEY) || "auto";
346
- applyTheme(saved);
347
- document.querySelectorAll<HTMLButtonElement>("#theme-toggle button").forEach((b) =>
348
- b.addEventListener("click", () => setTheme(b.dataset.themeChoice || "auto")));
349
- // Live-follow the OS when the user's choice is "auto" (default).
350
- themeMedia.addEventListener("change", () => {
351
- if ((localStorage.getItem(THEME_KEY) || "auto") === "auto") applyTheme("auto");
352
- });
353
- }
354
-
355
- /* ════════════════════════════════════════════════════════════════════
356
- KEYBOARD SHORTCUT SHEET (Cmd/Ctrl+/) + global bindings (plan §7)
357
- ════════════════════════════════════════════════════════════════════ */
358
- let skTrap: FocusTrap;
359
-
360
- /** Exported (not just used internally) so palette.ts's "Open shortcut
361
- * sheet" action can call it directly — same registered-entry-point
362
- * posture as openMemPanel/openHubPanel below, just a plain export since
363
- * this one takes no arguments and lives in the same module as its own
364
- * close/trap state. */
365
- export function openShortcutSheet(): void {
366
- skTrap.capture();
367
- $("shortcut-overlay").classList.add("open");
368
- $("nav-shortcuts").setAttribute("aria-expanded", "true");
369
- setTimeout(() => $("sk-close").focus(), 30);
370
- }
371
- function closeShortcutSheet(): void {
372
- $("shortcut-overlay").classList.remove("open");
373
- $("nav-shortcuts").setAttribute("aria-expanded", "false");
374
- skTrap.restore();
375
- }
376
- export function initShortcutSheet(): void {
377
- skTrap = trapFocus($("shortcut-overlay"), () => $("shortcut-overlay").classList.contains("open"));
378
- const isMac = /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);
379
- document.querySelectorAll("#sk-mod-label, .sk-mod").forEach((e) => { e.textContent = isMac ? "⌘" : "Ctrl"; });
380
- $("nav-shortcuts").onclick = () => {
381
- $("shortcut-overlay").classList.contains("open") ? closeShortcutSheet() : openShortcutSheet();
382
- };
383
- $("sk-close").onclick = closeShortcutSheet;
384
- $("shortcut-overlay").addEventListener("click", (e) => { if (e.target === $("shortcut-overlay")) closeShortcutSheet(); });
385
- }
386
-
387
- /* ════════════════════════════════════════════════════════════════════
388
- MOBILE DRAWER (plan §5.7/§7/§9 Phase 1 item 1) — chat sidebar becomes a
389
- slide-over on narrow viewports instead of vanishing outright. Chat-route
390
- only: the hamburger button is CSS-hidden >=760px and JS-hidden off /chat
391
- (router() below), so open/close are only ever reachable when relevant.
392
- ════════════════════════════════════════════════════════════════════ */
393
- let drawerTrap: FocusTrap;
394
-
395
- export function openDrawer(): void {
396
- drawerTrap.capture();
397
- $("chat-sidebar").classList.add("drawer-open");
398
- $("chat-drawer-backdrop").classList.add("open");
399
- $("chat-hamburger").setAttribute("aria-expanded", "true");
400
- setTimeout(() => { const s = $("chat-sess-search"); if (s) s.focus(); }, 30);
401
- }
402
- export function closeDrawer(): void {
403
- $("chat-sidebar").classList.remove("drawer-open");
404
- $("chat-drawer-backdrop").classList.remove("open");
405
- $("chat-hamburger").setAttribute("aria-expanded", "false");
406
- drawerTrap.restore();
407
- }
408
- export function initDrawer(): void {
409
- drawerTrap = trapFocus($("chat-sidebar"), () => $("chat-sidebar").classList.contains("drawer-open"));
410
- $("chat-hamburger").onclick = () => {
411
- $("chat-sidebar").classList.contains("drawer-open") ? closeDrawer() : openDrawer();
412
- };
413
- $("chat-drawer-backdrop").addEventListener("click", closeDrawer);
414
- }
415
-
416
- /** Set by controllers.chat's initSampling() once the popover exists; let
417
- * closeTopOverlay's global Escape sweep close it without a second,
418
- * divergent Escape listener living inside the controller. */
419
- export let samplingPopoverClose: (() => void) | null = null;
420
- export function setSamplingPopoverClose(fn: (() => void) | null): void { samplingPopoverClose = fn; }
421
-
422
- /** Same registered-callback pattern as samplingPopoverClose above, used by
423
- * memory-panel.ts's initMemoryPanel() — avoids a circular import (shell.ts
424
- * is imported BY memory-panel.ts for $/trapFocus/etc., so shell.ts can't
425
- * import memory-panel.ts's closeMemPanel back). */
426
- export let memPanelClose: (() => void) | null = null;
427
- export function setMemPanelClose(fn: (() => void) | null): void { memPanelClose = fn; }
428
-
429
- /** Same pattern, for the adapter routing table overlay (adapters-panel.ts,
430
- * plan §5.6/§9 Phase 2) — same circular-import reason as memPanelClose. */
431
- export let adaptersPanelClose: (() => void) | null = null;
432
- export function setAdaptersPanelClose(fn: (() => void) | null): void { adaptersPanelClose = fn; }
433
-
434
- /** Same pattern, for the model picker popover (model-picker.ts). */
435
- export let modelPopClose: (() => void) | null = null;
436
- export function setModelPopClose(fn: (() => void) | null): void { modelPopClose = fn; }
437
-
438
- /** Same pattern, for the Model Hub panel (hub.ts, plan §9 Phase 3) —
439
- * opened from the model picker's "Browse models…" action. */
440
- export let hubPanelClose: (() => void) | null = null;
441
- export function setHubPanelClose(fn: (() => void) | null): void { hubPanelClose = fn; }
442
-
443
- /** Registered by hub.ts's initHubPanel(); called by model-picker.ts's
444
- * "Browse models…" button. Its own body is re-rendered via innerHTML on
445
- * every popover open (see refreshModelPop()), so the button can't hold a
446
- * handler bound once at boot the way the overlay-close callbacks above
447
- * do — model-picker.ts re-wires the click to this registered function
448
- * each time it re-renders instead. */
449
- export let openHubFromModelPicker: (() => void) | null = null;
450
- export function setOpenHubFromModelPicker(fn: (() => void) | null): void { openHubFromModelPicker = fn; }
451
-
452
- /** Same pattern, for the system-prompt popover (composer.ts's
453
- * initSystemPrompt() — presets v1, plan §9 Phase 2). */
454
- export let sysPromptPopoverClose: (() => void) | null = null;
455
- export function setSysPromptPopoverClose(fn: (() => void) | null): void { sysPromptPopoverClose = fn; }
456
-
457
- /** Same pattern, for the command palette (palette.ts, plan §9 Phase 3,
458
- * beat-matrix Axis 10 "Command palette" row). Its own module builds 100%
459
- * of its DOM via createElement (no app.html markup to look up), so
460
- * isPaletteOpen()/openPalette() are registered functions rather than
461
- * `$(id)`-based lookups like the overlays above. */
462
- export let paletteClose: (() => void) | null = null;
463
- export function setPaletteClose(fn: (() => void) | null): void { paletteClose = fn; }
464
- export let paletteIsOpen: (() => boolean) | null = null;
465
- export function setPaletteIsOpen(fn: (() => boolean) | null): void { paletteIsOpen = fn; }
466
- export let paletteOpen: (() => void) | null = null;
467
- export function setPaletteOpen(fn: (() => void) | null): void { paletteOpen = fn; }
468
-
469
- /** Any open popover/drawer/sheet this Escape binding knows how to close,
470
- * checked in a fixed priority order (most-recently-opened-ish first).
471
- * Returns true if it closed something, so callers can stop there. */
472
- function closeTopOverlay(): boolean {
473
- if ($("shortcut-overlay").classList.contains("open")) { closeShortcutSheet(); return true; }
474
- if ($("hf-overlay").classList.contains("open")) { closeHfSettings(); return true; }
475
- const samplePop = $("chat-sampling-pop");
476
- if (samplePop && samplePop.classList.contains("open") && samplingPopoverClose) { samplingPopoverClose(); return true; }
477
- const sysPop = $("chat-sysprompt-pop");
478
- if (sysPop && sysPop.classList.contains("open") && sysPromptPopoverClose) { sysPromptPopoverClose(); return true; }
479
- const memOverlay = $("mem-overlay");
480
- if (memOverlay && memOverlay.classList.contains("open") && memPanelClose) { memPanelClose(); return true; }
481
- const adaptersOverlay = $("adapters-overlay");
482
- if (adaptersOverlay && adaptersOverlay.classList.contains("open") && adaptersPanelClose) { adaptersPanelClose(); return true; }
483
- const modelPop = $("model-pop");
484
- if (modelPop && modelPop.classList.contains("open") && modelPopClose) { modelPopClose(); return true; }
485
- const hubOverlay = $("hub-overlay");
486
- if (hubOverlay && hubOverlay.classList.contains("open") && hubPanelClose) { hubPanelClose(); return true; }
487
- if (paletteIsOpen && paletteIsOpen() && paletteClose) { paletteClose(); return true; }
488
- if ($("chat-sidebar").classList.contains("drawer-open")) { closeDrawer(); return true; }
489
- return false;
490
- }
491
-
492
- export function initGlobalKeydown(): void {
493
- document.addEventListener("keydown", (e) => {
494
- const mod = e.metaKey || e.ctrlKey;
495
- // Cmd/Ctrl+K — command palette (beat-matrix Axis 10). Checked before
496
- // Cmd/Ctrl+/ below since they're different keys but both live here;
497
- // order doesn't matter functionally, kept first as the newer addition.
498
- if (mod && (e.key === "k" || e.key === "K")) {
499
- e.preventDefault();
500
- if (paletteIsOpen && paletteIsOpen()) { if (paletteClose) paletteClose(); }
501
- else if (paletteOpen) paletteOpen();
502
- return;
503
- }
504
- // Cmd/Ctrl+/ — shortcut sheet. Not a browser-reserved combo.
505
- if (mod && e.key === "/") { e.preventDefault(); $("shortcut-overlay").classList.contains("open") ? closeShortcutSheet() : openShortcutSheet(); return; }
506
- // Cmd/Ctrl+Shift+O — new chat. (Ctrl+Shift+O is free in every major
507
- // browser; Cmd+Shift+O has no macOS Safari/Chrome reservation either.)
508
- if (mod && e.shiftKey && (e.key === "O" || e.key === "o")) {
509
- if (currentRoute() !== "chat") return; // no-op off the chat route
510
- e.preventDefault();
511
- const newChat = controllers.chat && controllers.chat.newChat as (() => void) | undefined;
512
- newChat && newChat();
513
- return;
514
- }
515
- // Cmd/Ctrl+Shift+C — copy last response. Reuses the wave-3 copy action.
516
- if (mod && e.shiftKey && (e.key === "C" || e.key === "c")) {
517
- if (currentRoute() !== "chat") return;
518
- e.preventDefault();
519
- const copyLastResponse = controllers.chat && controllers.chat.copyLastResponse as (() => void) | undefined;
520
- copyLastResponse && copyLastResponse();
521
- return;
522
- }
523
- // Shift+Escape — focus the composer (skipped while already typing
524
- // somewhere else that isn't the composer, so it doesn't steal focus
525
- // from e.g. the HF token field — but Escape's own overlay-close below
526
- // still fires first via closeTopOverlay for the "any open thing" case).
527
- if (e.shiftKey && e.key === "Escape") {
528
- if (currentRoute() !== "chat") return;
529
- e.preventDefault();
530
- $("chat-box").focus();
531
- return;
532
- }
533
- // Escape alone — close whatever overlay is open, with focus restored to
534
- // its trigger. Never shadows a plain Escape when nothing is open (e.g.
535
- // the composer's own Escape-to-cancel-edit keeps working, since
536
- // closeTopOverlay simply returns false and this handler does nothing else).
537
- if (e.key === "Escape" && !e.shiftKey && !mod) {
538
- closeTopOverlay();
539
- }
540
- });
541
- }
542
-
543
- /* ════════════════════════════════════════════════════════════════════
544
- DEVELOPER TOGGLE (plan §8/§9 Phase 2) — Chat is the product; Quantize /
545
- Fine-tune / Build Dataset / Status / Curves / Routes are developer tools
546
- that collapse behind one nav switch. Persisted; default OFF for a fresh
547
- browser, but default ON (once) when ANY pre-existing mlxbun.* localStorage
548
- key is found — an existing user never has tabs yanked out from under
549
- them. Deep links still work: entering a dev route with the toggle off
550
- flips it on (capability is never unreachable, just not the default view).
551
- ════════════════════════════════════════════════════════════════════ */
552
- const DEV_KEY = "mlxbun.developer";
553
- /** Dev-only tabs, matching data-tab on the <a class="tab"> elements in nav
554
- * (routes' visibility is further gated by the /dag probe below). "curves"
555
- * is listed for applyDeveloperMode's DOM sweep even though it can never be
556
- * a `Route` — it's a full-page href, not hash-routed, so
557
- * ensureDeveloperModeFor(route: Route) below can never match it; a direct
558
- * /curves visit reaches a real page load, not the SPA router, so there's
559
- * no deep-link case to flip the toggle for. */
560
- const DEV_TABS = ["quantize", "finetune", "dataset", "status", "curves", "routes"] as const;
561
-
562
- /** True if any OTHER mlxbun.* key already exists — i.e. this is a returning
563
- * user of the app, not a fresh browser profile. Checked BEFORE writing the
564
- * developer key itself, so it can't self-detect on a later load. */
565
- function hasExistingMlxbunState(): boolean {
566
- for (let i = 0; i < localStorage.length; i++) {
567
- const k = localStorage.key(i);
568
- if (k && k.startsWith("mlxbun.") && k !== DEV_KEY) return true;
569
- }
570
- return false;
571
- }
572
-
573
- export function isDeveloperMode(): boolean {
574
- const saved = localStorage.getItem(DEV_KEY);
575
- if (saved != null) return saved === "1";
576
- // First-ever read: decide + persist the one-time default so it's stable
577
- // across reloads (a fresh browser stays OFF; an existing user's tabs
578
- // don't flicker between "on" and "off" from the detection re-running).
579
- const on = hasExistingMlxbunState();
580
- localStorage.setItem(DEV_KEY, on ? "1" : "0");
581
- return on;
582
- }
583
-
584
- /** Reflect developer mode into the DOM: dev-tab visibility + the toggle's
585
- * own pressed state. Safe to call before /dag has been probed — the
586
- * routes tab's extra hide (see initRoutesProbe) is independent and only
587
- * ever hides further, never re-shows a tab this function would show. */
588
- function applyDeveloperMode(on: boolean): void {
589
- document.querySelectorAll<HTMLElement>("nav .tab[data-dev]").forEach((t) => {
590
- // Once /dag has been probed and failed, routes stays hidden regardless
591
- // of developer mode (dataset routesUnavailable === "1" marks that).
592
- if (t.dataset.tab === "routes" && t.dataset.routesUnavailable === "1") { t.style.display = "none"; return; }
593
- t.style.display = on ? "" : "none";
594
- });
595
- const btn = $("nav-developer");
596
- if (btn) btn.setAttribute("aria-checked", on ? "true" : "false");
597
- const dot = document.getElementById("nav-developer-dot");
598
- if (dot) dot.classList.toggle("on", on);
599
- updateTabFades();
600
- }
601
-
602
- export function setDeveloperMode(on: boolean): void {
603
- localStorage.setItem(DEV_KEY, on ? "1" : "0");
604
- applyDeveloperMode(on);
605
- }
606
-
607
- /** Called by the router when a deep link (or brand-new hash) lands on a
608
- * dev-only route while developer mode is off — capability must never be
609
- * unreachable just because the toggle defaulted off (plan §8). Flips the
610
- * preference on (persisted, not a one-time peek) so the tab row itself
611
- * stops looking broken/mismatched with what's showing. */
612
- function ensureDeveloperModeFor(route: Route): void {
613
- if ((DEV_TABS as readonly string[]).includes(route) && !isDeveloperMode()) setDeveloperMode(true);
614
- }
615
-
616
- export function initDeveloperToggle(): void {
617
- applyDeveloperMode(isDeveloperMode());
618
- const btn = $("nav-developer");
619
- if (btn) btn.onclick = () => setDeveloperMode(!isDeveloperMode());
620
- }
621
-
622
- /* ── Routes tab feature-detection (web-ui-pass-plan.md #17) ──
623
- /dag readFileSync's a repo-relative doc that's absent from compiled
624
- binaries/npm installs. Probe once at boot with a HEAD request; on 404
625
- hide the Routes tab entirely (rather than leaving a dead link that
626
- iframes a raw 404) and, if the user is already sitting on #/routes, swap
627
- in a graceful in-app note instead of the broken iframe. Could instead
628
- embed the artifact server-side like app.html's /curves fallback does —
629
- left as server.ts territory, not touched here. */
630
- export async function initRoutesProbe(): Promise<void> {
631
- const tab = document.querySelector<HTMLElement>('nav .tab[data-tab="routes"]');
632
- let ok = true;
633
- try {
634
- const r = await fetch("/dag", { method: "HEAD" });
635
- ok = r.ok;
636
- } catch { ok = false; }
637
- if (ok) return;
638
- if (tab) { tab.dataset.routesUnavailable = "1"; tab.style.display = "none"; }
639
- const section = $("s-routes");
640
- if (section) {
641
- section.innerHTML =
642
- '<div class="wrap" style="max-width:640px;margin:60px auto;text-align:center;color:var(--dim)">' +
643
- "<h2>Routes map unavailable</h2>" +
644
- "<p>The training/inference route diagram ships alongside the repo checkout " +
645
- "and isn't bundled into this build.</p></div>";
646
- }
647
- // If a deep link landed here directly, bounce to chat rather than sit on
648
- // an orphaned nav state with no visible tab pointing at it.
649
- if (currentRoute() === "routes") location.replace("#/chat");
650
- }
651
-
652
- /* ════════════════════════════════════════════════════════════════════
653
- ROUTER — toggles section[data-route]; lazily inits each controller.
654
- ════════════════════════════════════════════════════════════════════ */
655
- export const ROUTES = ["chat", "quantize", "finetune", "dataset", "status", "routes"] as const;
656
- export type Route = typeof ROUTES[number];
657
-
658
- /** A tab controller: init() runs once (lazy, on first enter); enter()/leave()
659
- * run every time the route is (de)activated. Extra fields (refreshAdapters,
660
- * newChat, copyLastResponse, refreshLibrary) are controller-specific
661
- * cross-calls other controllers use — kept loose (unknown) here since the
662
- * registry is shared infrastructure, not a single controller's contract. */
663
- export interface Controller {
664
- init?(): void;
665
- enter?(): void;
666
- leave?(): void;
667
- [extra: string]: unknown;
668
- }
669
-
670
- const inited: Partial<Record<Route, boolean>> = {};
671
- /** name -> { init, enter, leave }. Populated by main.ts in the exact order
672
- * the original inline script declared controllers.chat/.quantize/.finetune/
673
- * .dataset/.status — load-bearing for the module-init side effects each
674
- * IIFE ran at declaration time (e.g. chat's samplingPopoverClose wiring). */
675
- export const controllers: Partial<Record<Route, Controller>> = {};
676
-
677
- export function currentRoute(): Route {
678
- const h = (location.hash || "").replace(/^#\/?/, "").split("?")[0] || "";
679
- return (ROUTES as readonly string[]).includes(h) ? (h as Route) : "chat";
680
- }
681
-
682
- export function router(): void {
683
- const route = currentRoute();
684
- ensureDeveloperModeFor(route); // deep link to a dev tab always flips the toggle on
685
- document.querySelectorAll<HTMLElement>("section[data-route]").forEach((s) => {
686
- const on = s.dataset.route === route;
687
- if (on && !s.classList.contains("active")) {
688
- s.classList.add("active");
689
- const c = controllers[route];
690
- if (c) { if (!inited[route]) { inited[route] = true; c.init && c.init(); } c.enter && c.enter(); }
691
- } else if (!on && s.classList.contains("active")) {
692
- s.classList.remove("active");
693
- const c = controllers[s.dataset.route as Route];
694
- if (c && c.leave) c.leave();
695
- }
696
- });
697
- document.querySelectorAll<HTMLElement>("nav .tab").forEach((t) => t.classList.toggle("active", t.dataset.tab === route));
698
- // dim the ambient bloom slightly in the chat workspace so text reads cleanly
699
- $("bloom").style.opacity = route === "chat" ? "0.55" : "1";
700
- // The mobile drawer hamburger only makes sense on /chat (it opens the
701
- // recent-chats sidebar, which only exists there) — CSS already hides it
702
- // >=760px; this hides it off-route regardless of viewport width.
703
- $("chat-hamburger").style.display = route === "chat" ? "" : "none";
704
- if (route !== "chat") closeDrawer();
705
- }
706
-
707
- /* Nav-tab scroll-edge fades (2026-07-06 jank fix): the tab row scrolls with
708
- its scrollbar hidden, so overflow read as clipped/broken text. Toggle
709
- .fade-r/.fade-l so the CSS mask signals "more this way" only when true. */
710
- export function updateTabFades(): void {
711
- const t = $("tabs");
712
- const over = t.scrollWidth - t.clientWidth > 1;
713
- t.classList.toggle("fade-r", over && t.scrollLeft + t.clientWidth < t.scrollWidth - 1);
714
- t.classList.toggle("fade-l", over && t.scrollLeft > 1);
715
- }
716
-
717
- export function initRouter(): void {
718
- window.addEventListener("hashchange", router);
719
- $("tabs").addEventListener("scroll", updateTabFades, { passive: true });
720
- window.addEventListener("resize", updateTabFades);
721
- updateTabFades();
722
- }
723
-
724
- /* ════════════════════════════════════════════════════════════════════
725
- PWA SERVICE WORKER REGISTRATION (plan §9 Phase 3, beat-matrix Axis 10)
726
- Shell-only cache-first worker (src/web/sw.js) — installability + instant
727
- shell paint, explicitly NOT offline chat (the app is useless without the
728
- local model server running, see sw.js's own header comment). Guarded so
729
- it never registers somewhere a service worker can't run at all
730
- (`'serviceWorker' in navigator`) or where the browser would refuse it
731
- anyway (secure-context requirement: https, or localhost/127.0.0.1 for
732
- local dev — a plain `http://<lan-ip>` load would silently no-op without
733
- this check, so failing the guard is preferable to a console error on
734
- every load in that case).
735
- ════════════════════════════════════════════════════════════════════ */
736
- export function initServiceWorker(): void {
737
- if (!("serviceWorker" in navigator)) return;
738
- const host = location.hostname;
739
- const isLocalhost = host === "localhost" || host === "127.0.0.1" || host === "::1";
740
- if (location.protocol !== "https:" && !isLocalhost) return;
741
- window.addEventListener("load", () => {
742
- navigator.serviceWorker.register("/sw.js").catch(() => {
743
- // Registration failure isn't user-actionable (browser policy, private
744
- // browsing, etc.) — the app works identically without it, so this is
745
- // silent rather than a toast.
746
- });
747
- });
748
- }
749
-
750
- /* ════════════════════════════════════════════════════════════════════
751
- GLOBAL CONNECTION PILL + MODEL ID (polled lightly, always on)
752
- ════════════════════════════════════════════════════════════════════ */
753
- export let activeModelId: string | null = null;
754
-
755
- export function setConn(state: string, text: string): void {
756
- const p = $("nav-conn");
757
- p.className = "pill " + (state || "");
758
- $("nav-conn-text").textContent = text;
759
- }
760
-
761
- let defaultHelloSub: string | null = null;
762
-
763
- export async function pollIdentity(): Promise<void> {
764
- try {
765
- const [models, dl] = await Promise.all([
766
- fetch("/v1/models").then((r) => r.json()),
767
- fetch("/downloads").then((r) => r.json()).catch(() => ({ downloads: [] })),
768
- ]);
769
- activeModelId = models.data && models.data[0] ? models.data[0].id : null;
770
- $("nav-model").textContent = activeModelId || "no model";
771
- setConn("ok", "live · localhost");
772
- updateDownloadIndicator((dl && dl.downloads) || []);
773
- } catch {
774
- $("nav-model").textContent = "server unreachable";
775
- setConn("bad", "unreachable — retrying");
776
- }
777
- }
778
-
779
- interface DownloadInfo {
780
- state: string;
781
- repoId: string;
782
- totalBytes?: number;
783
- receivedBytes?: number;
784
- }
785
-
786
- /** Surface a background model download — the "bigger model arriving while you
787
- * chat on the starter" case — as a live nav pill plus a download-aware
788
- * greeting in the empty chat state. Hidden when nothing else is downloading. */
789
- export function updateDownloadIndicator(downloads: DownloadInfo[]): void {
790
- const sub = $("chat-hello-sub");
791
- if (sub && defaultHelloSub === null) defaultHelloSub = sub.textContent;
792
- const incoming = downloads.find((d) => d.state === "active" && d.repoId !== activeModelId);
793
- const pill = $("nav-download");
794
- if (incoming) {
795
- const pct = incoming.totalBytes ? Math.floor(((incoming.receivedBytes || 0) / incoming.totalBytes) * 100) : 0;
796
- const name = incoming.repoId.split("/").pop();
797
- $("nav-download-text").textContent = "↓ " + name + " · " + pct + "%";
798
- pill.style.display = "";
799
- if (sub) sub.textContent = "You're on a small, fast starter model so you can chat right now — a more capable one (" + name + ") is downloading and takes over next launch. Ask me anything, or about mlx-bun itself.";
800
- } else {
801
- pill.style.display = "none";
802
- if (sub && defaultHelloSub !== null) sub.textContent = defaultHelloSub;
803
- }
804
- }