mlx-bun 0.5.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 (640) hide show
  1. package/README.md +1449 -109
  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 +7 -5
  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 +10 -10
  78. package/src/{serve → server}/chat-stage.ts +14 -14
  79. package/src/{client.ts → server/client.ts} +28 -2
  80. package/src/{serve → server}/completion-executor.ts +33 -41
  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 +25 -82
  85. package/src/{serve → server}/http.ts +25 -13
  86. package/src/{serve → server}/inference-request.ts +5 -5
  87. package/src/server/job-routes.ts +20 -0
  88. package/src/server/media-prompt.ts +63 -0
  89. package/src/server/memory-completion-client.ts +46 -0
  90. package/src/server/model-routes.ts +192 -0
  91. package/src/{serve → server}/openai-wire.ts +3 -2
  92. package/src/{serve → server}/pipeline.ts +1 -1
  93. package/src/server/prompt-builder.ts +13 -0
  94. package/src/server/prompt-contracts.ts +17 -0
  95. package/src/server/proxy-routes.ts +187 -0
  96. package/src/server/publishing-routes.ts +41 -0
  97. package/src/{serve → server}/request-plan.ts +6 -8
  98. package/src/{serve → server}/request-prep.ts +47 -50
  99. package/src/{serve → server}/responses-client.ts +30 -13
  100. package/src/{responses.ts → server/responses.ts} +12 -93
  101. package/src/server/routes.ts +205 -0
  102. package/src/server/start.ts +72 -0
  103. package/src/server/status-routes.ts +108 -0
  104. package/src/{serve → server}/text-completion-stage.ts +25 -4
  105. package/src/server/text-prompt.ts +12 -0
  106. package/src/{serve → server}/token-streams.ts +36 -38
  107. package/src/server/worker-routes.ts +311 -0
  108. package/src/storage/paths.ts +61 -0
  109. package/src/web/assets.ts +37 -0
  110. package/src/web/{src → browser}/api.ts +5 -23
  111. package/src/web/browser/chat-host.ts +17 -0
  112. package/src/web/browser/installed-panels.ts +6 -0
  113. package/src/web/browser/main.ts +55 -0
  114. package/src/web/browser/markdown.ts +279 -0
  115. package/src/web/browser/memory-panel.ts +9 -0
  116. package/src/web/browser/palette.ts +64 -0
  117. package/src/web/browser/protocol.ts +13 -0
  118. package/src/web/browser/shell.ts +467 -0
  119. package/src/web/{src → browser}/status.ts +32 -36
  120. package/src/web/build.ts +51 -0
  121. package/src/web/public/app.html +1054 -0
  122. package/src/web/{sw.js → public/sw.js} +4 -3
  123. package/src/web/{vendor → public/vendor}/hljs-theme.css +2 -2
  124. package/src/web/{vendor → public/vendor}/hljs.js +2 -2
  125. package/THIRD_PARTY_LICENSES.md +0 -51
  126. package/docs/README.md +0 -107
  127. package/docs/archive/hlg-sampling.md +0 -350
  128. package/docs/archive/investigations/audio-input-plan.md +0 -356
  129. package/docs/archive/investigations/benchmark-ledger-through-v0.4.0.md +0 -3617
  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/release-notes-v0.4.0.md +0 -45
  174. package/docs/archive/planning/release-notes-v0.5.0.md +0 -37
  175. package/docs/archive/planning/web-ui-pass-plan.md +0 -213
  176. package/docs/archive/planning/website-readme-pass-plan.md +0 -224
  177. package/docs/design/batching.md +0 -2054
  178. package/docs/design/decode-speed-program.md +0 -2404
  179. package/docs/design/dreaming-nightly-pipeline.md +0 -684
  180. package/docs/design/generic-model-support.md +0 -821
  181. package/docs/design/kv-cache.md +0 -993
  182. package/docs/design/orpo-training.md +0 -680
  183. package/docs/design/speculative-decoding.md +0 -2129
  184. package/docs/design/turboquant.md +0 -2225
  185. package/docs/design/unified-engine-frontier-plan.md +0 -1987
  186. package/docs/design/web-chat-redesign.md +0 -695
  187. package/docs/planning/PRODUCT_ROADMAP.md +0 -593
  188. package/docs/planning/ResearchTopics.md +0 -119
  189. package/docs/planning/public-readiness-handoff.md +0 -244
  190. package/docs/planning/public-readiness.md +0 -149
  191. package/docs/planning/video-series.md +0 -421
  192. package/docs/reference/benchmarks.md +0 -3587
  193. package/docs/reference/cli.md +0 -646
  194. package/docs/reference/distribution.md +0 -380
  195. package/docs/reference/environment.md +0 -494
  196. package/docs/reference/glossary.md +0 -39
  197. package/docs/reference/library-api.md +0 -688
  198. package/docs/reference/memory.md +0 -260
  199. package/docs/reference/models.md +0 -346
  200. package/docs/reference/server-api.md +0 -1945
  201. package/docs/reference/server-config.md +0 -973
  202. package/docs/reference/training.md +0 -462
  203. package/docs/reference/troubleshooting.md +0 -158
  204. package/src/admission-plan.ts +0 -18
  205. package/src/audio/audiotoolbox.ts +0 -112
  206. package/src/audio/conformer.ts +0 -655
  207. package/src/audio/decode.ts +0 -133
  208. package/src/audio/features.ts +0 -202
  209. package/src/audio/mic-capture.ts +0 -98
  210. package/src/audio/silero-vad.ts +0 -379
  211. package/src/audio/transcode.ts +0 -67
  212. package/src/audio/whisper-decode.ts +0 -774
  213. package/src/audio/whisper-fast.ts +0 -732
  214. package/src/audio/whisper-format.ts +0 -73
  215. package/src/audio/whisper-mel.ts +0 -185
  216. package/src/audio/whisper-timing.ts +0 -274
  217. package/src/audio/whisper-tokenizer.ts +0 -239
  218. package/src/audio/whisper-transcribe.ts +0 -410
  219. package/src/backends/mlx/assistant-target.ts +0 -46
  220. package/src/backends/mlx/autoregressive-draft-rows.ts +0 -120
  221. package/src/backends/mlx/autoregressive.ts +0 -129
  222. package/src/backends/mlx/batch-group.ts +0 -1874
  223. package/src/backends/mlx/cache-attachment.ts +0 -67
  224. package/src/backends/mlx/cache-layout.ts +0 -31
  225. package/src/backends/mlx/checkpoint-state.ts +0 -53
  226. package/src/backends/mlx/continuation-persistence.ts +0 -107
  227. package/src/backends/mlx/continuation-request.ts +0 -43
  228. package/src/backends/mlx/continuation.ts +0 -77
  229. package/src/backends/mlx/diffusion.ts +0 -39
  230. package/src/backends/mlx/draft-target.ts +0 -32
  231. package/src/backends/mlx/encoder-cache.ts +0 -25
  232. package/src/backends/mlx/evaluation.ts +0 -105
  233. package/src/backends/mlx/fill-append.ts +0 -58
  234. package/src/backends/mlx/fill-group.ts +0 -497
  235. package/src/backends/mlx/full-kv-row-append.ts +0 -32
  236. package/src/backends/mlx/full-kv-row-donor.ts +0 -54
  237. package/src/backends/mlx/full-transitioning-kv-rows.ts +0 -65
  238. package/src/backends/mlx/gateway-binding.ts +0 -174
  239. package/src/backends/mlx/graph.ts +0 -38
  240. package/src/backends/mlx/kv-codec.ts +0 -9
  241. package/src/backends/mlx/kv-maintenance.ts +0 -161
  242. package/src/backends/mlx/kv-tensor-rows.ts +0 -212
  243. package/src/backends/mlx/memory-client.ts +0 -189
  244. package/src/backends/mlx/methods.ts +0 -90
  245. package/src/backends/mlx/mixed-iteration.ts +0 -62
  246. package/src/backends/mlx/model-prompt.ts +0 -178
  247. package/src/backends/mlx/model-serving.ts +0 -80
  248. package/src/backends/mlx/paged-cache-codec.ts +0 -27
  249. package/src/backends/mlx/prefill-cohort.ts +0 -100
  250. package/src/backends/mlx/prefill-policy.ts +0 -26
  251. package/src/backends/mlx/prefill-rows.ts +0 -244
  252. package/src/backends/mlx/prefill.ts +0 -33
  253. package/src/backends/mlx/projected-context-rows.ts +0 -72
  254. package/src/backends/mlx/prompt-input.ts +0 -29
  255. package/src/backends/mlx/qwen-prompt-input.ts +0 -40
  256. package/src/backends/mlx/request-policy.ts +0 -20
  257. package/src/backends/mlx/request-state-policy.ts +0 -54
  258. package/src/backends/mlx/rollback.ts +0 -92
  259. package/src/backends/mlx/row-sampling.ts +0 -49
  260. package/src/backends/mlx/serial-executor.ts +0 -251
  261. package/src/backends/mlx/speculative-group.ts +0 -438
  262. package/src/backends/mlx/speculative-round.ts +0 -161
  263. package/src/backends/mlx/speculative.ts +0 -131
  264. package/src/backends/mlx/state-rows.ts +0 -104
  265. package/src/backends/mlx/state-views.ts +0 -46
  266. package/src/backends/mlx/target-layout-capability.ts +0 -35
  267. package/src/backends/mlx/text-engine.ts +0 -73
  268. package/src/backends/mlx/transitioning-kv-rows.ts +0 -173
  269. package/src/chat-template.ts +0 -382
  270. package/src/cli.ts +0 -3490
  271. package/src/config.ts +0 -399
  272. package/src/contracts/completion.ts +0 -16
  273. package/src/contracts/execution.ts +0 -70
  274. package/src/contracts/generation.ts +0 -103
  275. package/src/contracts/host.ts +0 -7
  276. package/src/contracts/object-cache.ts +0 -8
  277. package/src/contracts/pi-provider.ts +0 -30
  278. package/src/contracts/pi.ts +0 -261
  279. package/src/contracts/prefix-cache.ts +0 -33
  280. package/src/contracts/resources.ts +0 -25
  281. package/src/contracts/sampling.ts +0 -23
  282. package/src/contracts/scheduling.ts +0 -54
  283. package/src/contracts/tsconfig.json +0 -14
  284. package/src/dataset/generators.ts +0 -1087
  285. package/src/dataset/index.ts +0 -12
  286. package/src/dataset/job.ts +0 -81
  287. package/src/dataset/llm.ts +0 -71
  288. package/src/dataset/registry.ts +0 -528
  289. package/src/diffusion/diffusion-generate.ts +0 -507
  290. package/src/download.ts +0 -505
  291. package/src/embed.ts +0 -77
  292. package/src/engine/admission.ts +0 -59
  293. package/src/engine/cancellation.ts +0 -38
  294. package/src/engine/engine.ts +0 -56
  295. package/src/engine/execution-coordinator.ts +0 -70
  296. package/src/engine/execution-plan.ts +0 -64
  297. package/src/engine/execution-tasks.ts +0 -44
  298. package/src/engine/namespaced-cache.ts +0 -13
  299. package/src/engine/resources.ts +0 -53
  300. package/src/engine/scheduler.ts +0 -92
  301. package/src/engine/session.ts +0 -247
  302. package/src/eval/README.md +0 -130
  303. package/src/eval/ifeval.ts +0 -81
  304. package/src/eval/kl-prompts.ts +0 -29
  305. package/src/eval/kl.ts +0 -590
  306. package/src/eval/perplexity.ts +0 -164
  307. package/src/eval/quality-db.ts +0 -92
  308. package/src/eval/reference-logits.ts +0 -207
  309. package/src/eval/runner.ts +0 -124
  310. package/src/eval/score.ts +0 -49
  311. package/src/eval/tasks/bfcl.ts +0 -535
  312. package/src/eval/tasks/gsm8k.ts +0 -98
  313. package/src/eval/tasks/hashhop.ts +0 -139
  314. package/src/eval/tasks/humaneval.ts +0 -275
  315. package/src/eval/tasks/ifeval.ts +0 -525
  316. package/src/eval/tasks/mmlu.ts +0 -157
  317. package/src/eval/ultrafeedback.ts +0 -60
  318. package/src/evaldb.ts +0 -199
  319. package/src/expert-io.ts +0 -393
  320. package/src/expert-offload-build.ts +0 -77
  321. package/src/expert-offload.ts +0 -76
  322. package/src/expert-residency.ts +0 -1134
  323. package/src/expert-usage.ts +0 -415
  324. package/src/fill/echo-index.ts +0 -188
  325. package/src/fill/fill-session.ts +0 -463
  326. package/src/fill/proposal.ts +0 -68
  327. package/src/fill/schema-rows.ts +0 -506
  328. package/src/fill/tool-boundary.ts +0 -103
  329. package/src/fit.ts +0 -249
  330. package/src/generate.ts +0 -1352
  331. package/src/grammar.ts +0 -604
  332. package/src/harness-pi.ts +0 -184
  333. package/src/hf-push.ts +0 -628
  334. package/src/hub-rest.ts +0 -242
  335. package/src/index.ts +0 -63
  336. package/src/inference/denoising.ts +0 -17
  337. package/src/inference/draft-acceptance.ts +0 -41
  338. package/src/inference/draft-output.ts +0 -49
  339. package/src/inference/graph.ts +0 -33
  340. package/src/inference/prefill.ts +0 -45
  341. package/src/inference/rollback.ts +0 -7
  342. package/src/jobs/index.ts +0 -31
  343. package/src/jobs/job-entry.ts +0 -107
  344. package/src/jobs/task-client.ts +0 -19
  345. package/src/jobs/types.ts +0 -2
  346. package/src/kv-scheme.ts +0 -279
  347. package/src/kv-store.ts +0 -1135
  348. package/src/lab/README.md +0 -22
  349. package/src/lab/curve/curve-designer.html +0 -303
  350. package/src/lab/curve/curve-sampler.ts +0 -122
  351. package/src/lab/expert-trace/expert-trace.ts +0 -69
  352. package/src/lab/paged-kv/paged-attention.ts +0 -117
  353. package/src/lab/paged-kv/paged-kv-rows.ts +0 -111
  354. package/src/lab/paged-kv/paged-kv.ts +0 -336
  355. package/src/library.ts +0 -40
  356. package/src/lora.ts +0 -408
  357. package/src/md-modules.d.ts +0 -54
  358. package/src/media-fetch.ts +0 -442
  359. package/src/memory/article.ts +0 -558
  360. package/src/memory/chunk-validation.ts +0 -150
  361. package/src/memory/chunk.ts +0 -318
  362. package/src/memory/cluster.ts +0 -351
  363. package/src/memory/coverage.ts +0 -134
  364. package/src/memory/crosslink.ts +0 -0
  365. package/src/memory/db.ts +0 -253
  366. package/src/memory/entity.ts +0 -203
  367. package/src/memory/gate.ts +0 -187
  368. package/src/memory/history.ts +0 -152
  369. package/src/memory/ingest.ts +0 -335
  370. package/src/memory/model.ts +0 -183
  371. package/src/memory/normalize.ts +0 -671
  372. package/src/memory/parse.ts +0 -22
  373. package/src/memory/pipeline.ts +0 -275
  374. package/src/memory/prompts.ts +0 -65
  375. package/src/memory/query.ts +0 -334
  376. package/src/memory/reconcile.ts +0 -734
  377. package/src/memory/redirect.ts +0 -108
  378. package/src/memory/reindex.ts +0 -325
  379. package/src/memory/resolve.ts +0 -307
  380. package/src/memory/rest.ts +0 -245
  381. package/src/memory/route.ts +0 -272
  382. package/src/memory/schedule.ts +0 -165
  383. package/src/memory/stages.ts +0 -777
  384. package/src/memory/synthesize.ts +0 -1638
  385. package/src/memory/tools.ts +0 -749
  386. package/src/memory/vault.ts +0 -1194
  387. package/src/memory/wikify.ts +0 -1064
  388. package/src/mlx/array.ts +0 -425
  389. package/src/mlx/autograd.ts +0 -304
  390. package/src/mlx/checkpoint.ts +0 -149
  391. package/src/mlx/compile.ts +0 -168
  392. package/src/mlx/custom-vjp.ts +0 -188
  393. package/src/mlx/ffi.ts +0 -531
  394. package/src/mlx/materialize.ts +0 -23
  395. package/src/mlx/metal-kernel.ts +0 -159
  396. package/src/mlx/normalized-argmax.ts +0 -116
  397. package/src/mlx/ops.ts +0 -1108
  398. package/src/mlx/quantized-rows.ts +0 -36
  399. package/src/mlx/token-bitmask.ts +0 -52
  400. package/src/mlx/turboquant-kv-decode.ts +0 -82
  401. package/src/mlx/turboquant-kv-inverse.ts +0 -122
  402. package/src/mlx/turboquant-ops.ts +0 -861
  403. package/src/mlx/turboquant-tables.ts +0 -237
  404. package/src/mmap.ts +0 -90
  405. package/src/model/artifact-identity.ts +0 -73
  406. package/src/model/batched-kv.ts +0 -235
  407. package/src/model/batched-mask.ts +0 -279
  408. package/src/model/batched-quant.ts +0 -212
  409. package/src/model/batched-quantized-kv.ts +0 -144
  410. package/src/model/batched-rotating-quant.ts +0 -353
  411. package/src/model/batched-rotating-state.ts +0 -149
  412. package/src/model/batched-rotating.ts +0 -338
  413. package/src/model/batched-row-storage.ts +0 -179
  414. package/src/model/batched-ssm.ts +0 -52
  415. package/src/model/batched-turboquant-kv.ts +0 -94
  416. package/src/model/compiled-decode.ts +0 -712
  417. package/src/model/decoded-kv-donor.ts +0 -12
  418. package/src/model/delayed-quantized-kv.ts +0 -72
  419. package/src/model/delayed-rotating-quantized-kv.ts +0 -93
  420. package/src/model/delayed-turboquant-kv.ts +0 -57
  421. package/src/model/diffusion-gemma.ts +0 -1026
  422. package/src/model/factory.ts +0 -215
  423. package/src/model/fingerprint.ts +0 -45
  424. package/src/model/flash-attention.ts +0 -694
  425. package/src/model/full-prefill-padding.ts +0 -56
  426. package/src/model/full-prefill-row.ts +0 -105
  427. package/src/model/gemma4-base.ts +0 -2370
  428. package/src/model/gemma4.ts +0 -1395
  429. package/src/model/generated/gemma4-12b.ts +0 -429
  430. package/src/model/generated/gemma4-26b.ts +0 -448
  431. package/src/model/generated/gemma4-e4b.ts +0 -717
  432. package/src/model/generated/index.ts +0 -18
  433. package/src/model/glm52-atlas.ts +0 -511
  434. package/src/model/glm52-cache.ts +0 -792
  435. package/src/model/glm52-config.ts +0 -262
  436. package/src/model/glm52-container.ts +0 -305
  437. package/src/model/glm52-coupling.ts +0 -388
  438. package/src/model/glm52-dsa.ts +0 -606
  439. package/src/model/glm52-expert-layout.ts +0 -253
  440. package/src/model/glm52-layout.ts +0 -152
  441. package/src/model/glm52-memory.ts +0 -500
  442. package/src/model/glm52-mla.ts +0 -792
  443. package/src/model/glm52-moe.ts +0 -223
  444. package/src/model/glm52-pilot.ts +0 -537
  445. package/src/model/glm52-reference.ts +0 -445
  446. package/src/model/glm52-residency.ts +0 -557
  447. package/src/model/glm52-resident-weights.ts +0 -135
  448. package/src/model/glm52-streamed-experts.ts +0 -453
  449. package/src/model/glm52-streamed-metal.ts +0 -610
  450. package/src/model/glm52-weights.ts +0 -503
  451. package/src/model/glm52.ts +0 -1132
  452. package/src/model/implementation.ts +0 -69
  453. package/src/model/kv-attention-view.ts +0 -70
  454. package/src/model/minicpm5.ts +0 -376
  455. package/src/model/profile.ts +0 -468
  456. package/src/model/quantized-append-attention.ts +0 -31
  457. package/src/model/qwen-append.ts +0 -12
  458. package/src/model/qwen-conv.ts +0 -52
  459. package/src/model/qwen3-delta-state.ts +0 -74
  460. package/src/model/qwen3-delta.ts +0 -451
  461. package/src/model/qwen3-moe.ts +0 -404
  462. package/src/model/qwen3-mrope.ts +0 -250
  463. package/src/model/qwen3.ts +0 -277
  464. package/src/model/qwen3_5-checkpoint.ts +0 -164
  465. package/src/model/qwen3_5.ts +0 -881
  466. package/src/model/rotating-kv-donor.ts +0 -35
  467. package/src/model/rotating-kv-layout.ts +0 -262
  468. package/src/model/rotating-row-transaction.ts +0 -35
  469. package/src/model/speculative-rotating-kv.ts +0 -101
  470. package/src/model/ssm-prefill-padding.ts +0 -39
  471. package/src/model/support.ts +0 -110
  472. package/src/model/token-groups.ts +0 -66
  473. package/src/model/trellis-balanced-scatter.ts +0 -51
  474. package/src/model/trellis-linear.ts +0 -672
  475. package/src/model/trellis-shared-m.ts +0 -151
  476. package/src/model/trellis-shared-scatter.ts +0 -144
  477. package/src/model/trellis-splitk-prefill.ts +0 -158
  478. package/src/model/trellis-tiled-prefill.ts +0 -136
  479. package/src/model/trellis-vector-expand.ts +0 -62
  480. package/src/model/trellis-wide-prefill.ts +0 -94
  481. package/src/model/turboquant-codec.ts +0 -89
  482. package/src/model/universal/archs.ts +0 -332
  483. package/src/model/universal/dense.ts +0 -612
  484. package/src/model/universal/modules.ts +0 -170
  485. package/src/model/universal/rope.ts +0 -381
  486. package/src/model/whisper.ts +0 -435
  487. package/src/native/expert_io.c +0 -523
  488. package/src/native/frame_extract.swift +0 -140
  489. package/src/native/mic_capture.swift +0 -92
  490. package/src/native-pack.ts +0 -163
  491. package/src/pi-provider.ts +0 -53
  492. package/src/pi-session.ts +0 -81
  493. package/src/pi-terminal.ts +0 -274
  494. package/src/pi-web.ts +0 -1940
  495. package/src/preflight.ts +0 -158
  496. package/src/prompt-cache.ts +0 -682
  497. package/src/quantize/allocator.ts +0 -627
  498. package/src/quantize/atomic-output.ts +0 -38
  499. package/src/quantize/calibration.ts +0 -181
  500. package/src/quantize/config-writer.ts +0 -145
  501. package/src/quantize/index.ts +0 -84
  502. package/src/quantize/job.ts +0 -162
  503. package/src/quantize/quantizer.ts +0 -666
  504. package/src/quantize/rotate.ts +0 -603
  505. package/src/quantize/safetensors-writer.ts +0 -249
  506. package/src/quantize/sensitivity.ts +0 -216
  507. package/src/quantize/trellis.ts +0 -586
  508. package/src/quantize/weight-transform.ts +0 -292
  509. package/src/registry.ts +0 -602
  510. package/src/runtime-config.ts +0 -77
  511. package/src/safetensors.ts +0 -165
  512. package/src/sampler.ts +0 -1012
  513. package/src/serve/admin-routes.ts +0 -184
  514. package/src/serve/assets/curve-terrain.html +0 -105
  515. package/src/serve/assets/training-inference-map.html +0 -575
  516. package/src/serve/audio-routes.ts +0 -323
  517. package/src/serve/aux-routes.ts +0 -132
  518. package/src/serve/batch-scheduler.ts +0 -8
  519. package/src/serve/checkpoint-identity.ts +0 -64
  520. package/src/serve/generation-gateway.ts +0 -537
  521. package/src/serve/isolate.ts +0 -609
  522. package/src/serve/kv-budget.ts +0 -14
  523. package/src/serve/lab-routes.ts +0 -341
  524. package/src/serve/lane-registry.ts +0 -45
  525. package/src/serve/model-admin-routes.ts +0 -155
  526. package/src/serve/model-binding.ts +0 -49
  527. package/src/serve/model-host.ts +0 -511
  528. package/src/serve/parent-application.ts +0 -56
  529. package/src/serve/prompt-response-trace.ts +0 -161
  530. package/src/serve/response-lease.ts +0 -24
  531. package/src/serve/session-search.ts +0 -326
  532. package/src/serve/static-routes.ts +0 -101
  533. package/src/serve/transcription-server.ts +0 -64
  534. package/src/serve/transcription-service.ts +0 -502
  535. package/src/server.ts +0 -1282
  536. package/src/spec/assistant-rows.ts +0 -93
  537. package/src/spec/assistant-source.ts +0 -126
  538. package/src/spec/cache-identity.ts +0 -22
  539. package/src/spec/deepspec-rows.ts +0 -29
  540. package/src/spec/deepspec-source.ts +0 -145
  541. package/src/spec/dflash-source.ts +0 -149
  542. package/src/spec/drafter.ts +0 -304
  543. package/src/spec/dspark/ab-stats.ts +0 -136
  544. package/src/spec/dspark/calibration.ts +0 -112
  545. package/src/spec/dspark/data-dflash.ts +0 -156
  546. package/src/spec/dspark/data.ts +0 -201
  547. package/src/spec/dspark/deepspec-module.ts +0 -776
  548. package/src/spec/dspark/generate-dflash.ts +0 -245
  549. package/src/spec/dspark/generate.ts +0 -290
  550. package/src/spec/dspark/loader.ts +0 -23
  551. package/src/spec/dspark/loss.ts +0 -213
  552. package/src/spec/dspark/module-dflash.ts +0 -560
  553. package/src/spec/dspark/module.ts +0 -540
  554. package/src/spec/dspark/quantize-drafter.ts +0 -91
  555. package/src/spec/dspark/sample.ts +0 -112
  556. package/src/spec/generate.ts +0 -276
  557. package/src/spec/glm52-mtp-graph.ts +0 -29
  558. package/src/spec/glm52-mtp-rows.ts +0 -132
  559. package/src/spec/glm52-mtp-source.ts +0 -318
  560. package/src/spec/glm52-mtp-state.ts +0 -33
  561. package/src/spec/ngram-source.ts +0 -226
  562. package/src/spec/projected-draft-rows.ts +0 -73
  563. package/src/spec/qwen-mtp-module.ts +0 -171
  564. package/src/spec/qwen-mtp-rows.ts +0 -230
  565. package/src/spec/qwen-mtp-source.ts +0 -386
  566. package/src/spec/qwen-mtp-state.ts +0 -30
  567. package/src/spec/serve-loop.ts +0 -587
  568. package/src/spec/source.ts +0 -228
  569. package/src/spec/two-model-rows.ts +0 -62
  570. package/src/spec/two-model.ts +0 -113
  571. package/src/ssd-cache.ts +0 -431
  572. package/src/ssd-durability.ts +0 -209
  573. package/src/storage/host-buffer.ts +0 -24
  574. package/src/storage/kv-writer.ts +0 -83
  575. package/src/storage/kv-writer.worker.js +0 -264
  576. package/src/storage/retention-policy.ts +0 -37
  577. package/src/tiered-prompt-cache.ts +0 -45
  578. package/src/tokenizer.ts +0 -335
  579. package/src/tool-approvals.ts +0 -131
  580. package/src/tool-call.ts +0 -695
  581. package/src/train/dataset.ts +0 -484
  582. package/src/train/diffusion-lora.ts +0 -216
  583. package/src/train/export.ts +0 -32
  584. package/src/train/flash-cce.ts +0 -1234
  585. package/src/train/forward.ts +0 -266
  586. package/src/train/fuse.ts +0 -243
  587. package/src/train/index.ts +0 -46
  588. package/src/train/job.ts +0 -205
  589. package/src/train/lora-params.ts +0 -254
  590. package/src/train/loss.ts +0 -1591
  591. package/src/train/merge.ts +0 -195
  592. package/src/train/optimizer.ts +0 -183
  593. package/src/train/prefix-shared.ts +0 -490
  594. package/src/train/rank.ts +0 -137
  595. package/src/train/segmented.ts +0 -1828
  596. package/src/train/steel-qmm-header.ts +0 -1461
  597. package/src/train/trainer.ts +0 -1221
  598. package/src/train/watch.ts +0 -542
  599. package/src/vision/diffusion-vision.ts +0 -620
  600. package/src/vision/embedder.ts +0 -176
  601. package/src/vision/preprocess.ts +0 -247
  602. package/src/vision/prompt.ts +0 -409
  603. package/src/vision/qwen3vl-preprocess.ts +0 -409
  604. package/src/vision/qwen3vl-prompt.ts +0 -233
  605. package/src/vision/qwen3vl-tower.ts +0 -566
  606. package/src/vision/siglip.ts +0 -607
  607. package/src/vision/video-frames.ts +0 -148
  608. package/src/web/app.html +0 -2155
  609. package/src/web/app.js +0 -5863
  610. package/src/web/skills/memory/SKILL.md +0 -120
  611. package/src/web/skills/web-research/SKILL.md +0 -46
  612. package/src/web/skills.ts +0 -97
  613. package/src/web/src/adapters-panel.ts +0 -288
  614. package/src/web/src/assistant.ts +0 -451
  615. package/src/web/src/chat.ts +0 -1060
  616. package/src/web/src/composer.ts +0 -962
  617. package/src/web/src/dataset.ts +0 -132
  618. package/src/web/src/finetune.ts +0 -267
  619. package/src/web/src/hub.ts +0 -428
  620. package/src/web/src/main.ts +0 -98
  621. package/src/web/src/markdown.ts +0 -0
  622. package/src/web/src/memory-panel.ts +0 -572
  623. package/src/web/src/model-picker.ts +0 -159
  624. package/src/web/src/palette.ts +0 -346
  625. package/src/web/src/protocol.ts +0 -26
  626. package/src/web/src/quantize.ts +0 -167
  627. package/src/web/src/rag.ts +0 -274
  628. package/src/web/src/sessions.ts +0 -469
  629. package/src/web/src/shell.ts +0 -804
  630. package/src/web/src/ui-catalog.ts +0 -78
  631. package/src/web/src/voice.ts +0 -141
  632. package/src/web/vendor/README.md +0 -61
  633. package/src/web-assets.ts +0 -47
  634. package/src/web-tools.ts +0 -478
  635. package/src/weights.ts +0 -255
  636. /package/src/{serve → server}/completion-sink.ts +0 -0
  637. /package/src/{serve → server}/generated-token-history.ts +0 -0
  638. /package/src/web/{icon.svg → public/icon.svg} +0 -0
  639. /package/src/web/{manifest.webmanifest → public/manifest.webmanifest} +0 -0
  640. /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
- }