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.
- package/README.md +1453 -88
- package/THIRD_PARTY_NOTICES.md +882 -0
- package/bin/mlx-bun.convert.mjs +4 -0
- package/bin/mlx-bun.fuse.mjs +4 -0
- package/bin/mlx-bun.generate.mjs +4 -0
- package/bin/mlx-bun.lora.mjs +4 -0
- package/bin/mlx-bun.mjs +13 -21
- package/bin/mlx-bun.server.mjs +4 -0
- package/bin/mlx-bun.upload.mjs +4 -0
- package/dist/web/app.js +9584 -0
- package/package.json +50 -53
- package/src/cli/ane-install.ts +76 -0
- package/src/cli/args.ts +179 -0
- package/src/cli/engine-entry.ts +68 -0
- package/src/cli/inference.ts +214 -0
- package/src/cli/job-entry.ts +97 -0
- package/src/cli/library-host.ts +117 -0
- package/src/cli/main.ts +57 -0
- package/src/cli/memory-engine.ts +163 -0
- package/src/cli/memory.ts +36 -0
- package/src/cli/mlx-lm-aliases.ts +309 -0
- package/src/cli/model-choice.ts +33 -0
- package/src/cli/model-selection.ts +84 -0
- package/src/cli/module-host.ts +11 -0
- package/src/cli/module-verbs.ts +50 -0
- package/src/cli/numerical-policy.ts +12 -0
- package/src/cli/perplexity.ts +89 -0
- package/src/cli/serve-host.ts +423 -0
- package/src/cli/serve-isolated.ts +217 -0
- package/src/cli/serve-options.ts +106 -0
- package/src/cli/serve-state.ts +205 -0
- package/src/cli/serve.ts +500 -0
- package/src/cli/served-model-host.ts +102 -0
- package/src/cli/server-entry.ts +148 -0
- package/src/cli/serving-unit.ts +177 -0
- package/src/{tui.ts → cli/terminal.ts} +13 -8
- package/src/cli/worker-adapters.ts +34 -0
- package/src/cli/worker-entry.ts +272 -0
- package/src/cli/worker-unit.ts +179 -0
- package/src/engine/adapter-operation.ts +26 -0
- package/src/engine/cache-services.ts +114 -0
- package/src/engine/completion.ts +39 -0
- package/src/engine/generation-gateway.ts +358 -0
- package/src/engine/index.ts +59 -0
- package/src/engine/kv-budget.ts +120 -0
- package/src/engine/media-preparation.ts +232 -0
- package/src/engine/model-binding.ts +24 -0
- package/src/engine/model-host.ts +513 -0
- package/src/engine/model-serving.ts +89 -0
- package/src/{serve → engine}/preparation.ts +10 -8
- package/src/{serve → engine}/session-completion-engine.ts +6 -6
- package/src/engine/telemetry.ts +109 -0
- package/src/hub/downloads.ts +116 -0
- package/src/jobs/db.ts +16 -7
- package/src/jobs/events.ts +29 -0
- package/src/jobs/executable.ts +5 -0
- package/src/jobs/host.ts +51 -0
- package/src/jobs/in-process.ts +55 -0
- package/src/{contracts/jobs.ts → jobs/protocol.ts} +7 -9
- package/src/jobs/runner.ts +150 -231
- package/src/jobs/service.ts +75 -0
- package/src/jobs/sse.ts +59 -23
- package/src/jobs/worker-memory.ts +46 -0
- package/src/jobs/worker-process.ts +163 -0
- package/src/jobs/worker-supervisor.ts +258 -0
- package/src/modules.ts +58 -0
- package/src/publishing/catalog-hub.ts +18 -0
- package/src/publishing/credentials.ts +31 -0
- package/src/publishing/upload.ts +37 -0
- package/src/residency/leased-adapters.ts +21 -0
- package/src/residency/local-records.ts +16 -0
- package/src/residency/model-residency.ts +405 -0
- package/src/residency/record-index.ts +28 -0
- package/src/residency/resident-estimate.ts +50 -0
- package/src/{anthropic.ts → server/anthropic.ts} +3 -81
- package/src/server/cache-routes.ts +36 -0
- package/src/{serve → server}/chat-request.ts +12 -10
- package/src/{serve → server}/chat-stage.ts +27 -22
- package/src/{client.ts → server/client.ts} +28 -2
- package/src/{serve → server}/completion-executor.ts +42 -42
- package/src/server/decision-body.ts +55 -0
- package/src/server/decision-routes.ts +134 -0
- package/src/server/decision-wire.ts +228 -0
- package/src/{serve → server}/discovery-routes.ts +42 -83
- package/src/server/generated-token-history.ts +57 -0
- package/src/{serve → server}/http.ts +25 -13
- package/src/{serve → server}/inference-request.ts +5 -5
- package/src/server/job-routes.ts +20 -0
- package/src/server/media-prompt.ts +63 -0
- package/src/server/memory-completion-client.ts +46 -0
- package/src/server/model-routes.ts +192 -0
- package/src/{serve → server}/openai-wire.ts +3 -2
- package/src/{serve → server}/pipeline.ts +1 -1
- package/src/server/prompt-builder.ts +13 -0
- package/src/server/prompt-contracts.ts +17 -0
- package/src/server/proxy-routes.ts +187 -0
- package/src/server/publishing-routes.ts +41 -0
- package/src/{serve → server}/request-plan.ts +8 -8
- package/src/{serve → server}/request-prep.ts +82 -68
- package/src/{serve → server}/responses-client.ts +30 -13
- package/src/{responses.ts → server/responses.ts} +12 -93
- package/src/server/routes.ts +205 -0
- package/src/server/start.ts +72 -0
- package/src/server/status-routes.ts +108 -0
- package/src/{serve → server}/text-completion-stage.ts +25 -4
- package/src/server/text-prompt.ts +12 -0
- package/src/{serve → server}/token-streams.ts +36 -38
- package/src/server/worker-routes.ts +311 -0
- package/src/storage/paths.ts +61 -0
- package/src/web/assets.ts +37 -0
- package/src/web/{src → browser}/api.ts +5 -23
- package/src/web/browser/chat-host.ts +17 -0
- package/src/web/browser/installed-panels.ts +6 -0
- package/src/web/browser/main.ts +55 -0
- package/src/web/browser/markdown.ts +279 -0
- package/src/web/browser/memory-panel.ts +9 -0
- package/src/web/browser/palette.ts +64 -0
- package/src/web/browser/protocol.ts +13 -0
- package/src/web/browser/shell.ts +467 -0
- package/src/web/{src → browser}/status.ts +32 -36
- package/src/web/build.ts +51 -0
- package/src/web/public/app.html +1054 -0
- package/src/web/{sw.js → public/sw.js} +4 -3
- package/src/web/{vendor → public/vendor}/hljs-theme.css +2 -2
- package/src/web/{vendor → public/vendor}/hljs.js +2 -2
- package/THIRD_PARTY_LICENSES.md +0 -51
- package/docs/README.md +0 -94
- package/docs/archive/hlg-sampling.md +0 -350
- package/docs/archive/investigations/audio-input-plan.md +0 -356
- package/docs/archive/investigations/chunk-finetune-experiment.md +0 -84
- package/docs/archive/investigations/colibri-glm52-port.md +0 -1486
- package/docs/archive/investigations/colibri-oracle-pin.md +0 -1284
- package/docs/archive/investigations/curve-bisector-routing.md +0 -123
- package/docs/archive/investigations/decode-roofline-lookagain.md +0 -348
- package/docs/archive/investigations/diffusion-gemma-port.md +0 -602
- package/docs/archive/investigations/dspark-handoff.md +0 -224
- package/docs/archive/investigations/expert-offload-single-user-moe.md +0 -292
- package/docs/archive/investigations/faithful-l1-consolidation.md +0 -310
- package/docs/archive/investigations/hlg-sampling-investigation.md +0 -970
- package/docs/archive/investigations/indexshare-performance-spike.md +0 -357
- package/docs/archive/investigations/kernel-perf-review-2026-07.md +0 -171
- package/docs/archive/investigations/lab-build-journal.md +0 -157
- package/docs/archive/investigations/minicpm5-decode-megakernel.md +0 -489
- package/docs/archive/investigations/openjarvis-fit.md +0 -198
- package/docs/archive/investigations/openwiki-evaluation.md +0 -249
- package/docs/archive/investigations/optimization_plan.md +0 -346
- package/docs/archive/investigations/orpo-base-uf-experiment-and-directions.md +0 -120
- package/docs/archive/investigations/orpo-flash-cce-pin-leak.md +0 -167
- package/docs/archive/investigations/orpo-uf-testing-handoff.md +0 -126
- package/docs/archive/investigations/pareto-specialized-runtime-findings.md +0 -92
- package/docs/archive/investigations/pi-builtin-investigation.md +0 -308
- package/docs/archive/investigations/pre-colibri-stabilization.md +0 -329
- package/docs/archive/investigations/qwen38-27b-campaign-log.md +0 -1092
- package/docs/archive/investigations/segmented-backward-handoff.md +0 -148
- package/docs/archive/investigations/starter-model-port-handoff.md +0 -342
- package/docs/archive/investigations/steel-flash-cce-handoff.md +0 -489
- package/docs/archive/investigations/the-dreaming-handoff.md +0 -141
- package/docs/archive/investigations/trainer-validation-experiment.md +0 -173
- package/docs/archive/planning/chat-ui-vision-memory-first.md +0 -199
- package/docs/archive/planning/chat-ui-vision.md +0 -50
- package/docs/archive/planning/chunk-task-roadmap.md +0 -77
- package/docs/archive/planning/curve-sampler-research-plan.md +0 -331
- package/docs/archive/planning/journal.md +0 -316
- package/docs/archive/planning/memory-docs-and-dag-plan.md +0 -194
- package/docs/archive/planning/pi-integration-review.md +0 -178
- package/docs/archive/planning/release-notes-v0.0.10.md +0 -57
- package/docs/archive/planning/release-notes-v0.0.11.md +0 -239
- package/docs/archive/planning/release-notes-v0.0.12.md +0 -80
- package/docs/archive/planning/release-notes-v0.0.13.md +0 -77
- package/docs/archive/planning/release-notes-v0.0.9.md +0 -122
- package/docs/archive/planning/release-notes-v0.2.0.md +0 -49
- package/docs/archive/planning/release-notes-v0.3.0.md +0 -35
- package/docs/archive/planning/web-ui-pass-plan.md +0 -213
- package/docs/archive/planning/website-readme-pass-plan.md +0 -224
- package/docs/design/batching.md +0 -1841
- package/docs/design/decode-speed-program.md +0 -2349
- package/docs/design/dreaming-nightly-pipeline.md +0 -684
- package/docs/design/generic-model-support.md +0 -705
- package/docs/design/kv-cache.md +0 -824
- package/docs/design/orpo-training.md +0 -680
- package/docs/design/speculative-decoding.md +0 -1990
- package/docs/design/turboquant.md +0 -2212
- package/docs/design/unified-engine-frontier-plan.md +0 -1880
- package/docs/design/web-chat-redesign.md +0 -695
- package/docs/planning/PRODUCT_ROADMAP.md +0 -594
- package/docs/planning/ResearchTopics.md +0 -119
- package/docs/planning/release-notes-v0.4.0.md +0 -45
- package/docs/planning/video-series.md +0 -421
- package/docs/reference/benchmarks.md +0 -3613
- package/docs/reference/cli.md +0 -585
- package/docs/reference/distribution.md +0 -355
- package/docs/reference/environment.md +0 -398
- package/docs/reference/glossary.md +0 -39
- package/docs/reference/library-api.md +0 -674
- package/docs/reference/memory.md +0 -260
- package/docs/reference/models.md +0 -334
- package/docs/reference/server-api.md +0 -1775
- package/docs/reference/server-config.md +0 -846
- package/docs/reference/training.md +0 -462
- package/docs/reference/troubleshooting.md +0 -158
- package/src/admission-plan.ts +0 -18
- package/src/audio/conformer.ts +0 -647
- package/src/audio/decode.ts +0 -133
- package/src/audio/features.ts +0 -202
- package/src/audio/transcode.ts +0 -67
- package/src/backends/mlx/assistant-target.ts +0 -46
- package/src/backends/mlx/autoregressive-draft-rows.ts +0 -120
- package/src/backends/mlx/autoregressive.ts +0 -108
- package/src/backends/mlx/batch-group.ts +0 -1790
- package/src/backends/mlx/cache-attachment.ts +0 -67
- package/src/backends/mlx/cache-layout.ts +0 -31
- package/src/backends/mlx/checkpoint-state.ts +0 -53
- package/src/backends/mlx/continuation-persistence.ts +0 -106
- package/src/backends/mlx/continuation-request.ts +0 -43
- package/src/backends/mlx/continuation.ts +0 -77
- package/src/backends/mlx/diffusion.ts +0 -39
- package/src/backends/mlx/draft-target.ts +0 -32
- package/src/backends/mlx/evaluation.ts +0 -105
- package/src/backends/mlx/fill-append.ts +0 -45
- package/src/backends/mlx/full-kv-row-append.ts +0 -32
- package/src/backends/mlx/full-kv-row-donor.ts +0 -54
- package/src/backends/mlx/full-transitioning-kv-rows.ts +0 -65
- package/src/backends/mlx/gateway-binding.ts +0 -130
- package/src/backends/mlx/graph.ts +0 -38
- package/src/backends/mlx/kv-codec.ts +0 -9
- package/src/backends/mlx/kv-maintenance.ts +0 -133
- package/src/backends/mlx/kv-tensor-rows.ts +0 -212
- package/src/backends/mlx/memory-client.ts +0 -189
- package/src/backends/mlx/methods.ts +0 -90
- package/src/backends/mlx/model-prompt.ts +0 -164
- package/src/backends/mlx/model-serving.ts +0 -79
- package/src/backends/mlx/prefill-cohort.ts +0 -89
- package/src/backends/mlx/prefill-rows.ts +0 -200
- package/src/backends/mlx/prefill.ts +0 -33
- package/src/backends/mlx/projected-context-rows.ts +0 -72
- package/src/backends/mlx/request-policy.ts +0 -20
- package/src/backends/mlx/request-state-policy.ts +0 -41
- package/src/backends/mlx/rollback.ts +0 -89
- package/src/backends/mlx/row-sampling.ts +0 -49
- package/src/backends/mlx/serial-executor.ts +0 -242
- package/src/backends/mlx/speculative-group.ts +0 -359
- package/src/backends/mlx/speculative-round.ts +0 -161
- package/src/backends/mlx/speculative.ts +0 -114
- package/src/backends/mlx/state-rows.ts +0 -104
- package/src/backends/mlx/state-views.ts +0 -46
- package/src/backends/mlx/target-layout-capability.ts +0 -35
- package/src/backends/mlx/text-engine.ts +0 -73
- package/src/backends/mlx/transitioning-kv-rows.ts +0 -165
- package/src/chat-template.ts +0 -382
- package/src/cli.ts +0 -3168
- package/src/config.ts +0 -399
- package/src/contracts/completion.ts +0 -16
- package/src/contracts/execution.ts +0 -60
- package/src/contracts/generation.ts +0 -103
- package/src/contracts/host.ts +0 -7
- package/src/contracts/pi-provider.ts +0 -30
- package/src/contracts/pi.ts +0 -259
- package/src/contracts/prefix-cache.ts +0 -23
- package/src/contracts/resources.ts +0 -25
- package/src/contracts/sampling.ts +0 -23
- package/src/contracts/scheduling.ts +0 -45
- package/src/contracts/tsconfig.json +0 -14
- package/src/dataset/generators.ts +0 -1087
- package/src/dataset/index.ts +0 -12
- package/src/dataset/job.ts +0 -81
- package/src/dataset/llm.ts +0 -71
- package/src/dataset/registry.ts +0 -528
- package/src/diffusion/diffusion-generate.ts +0 -507
- package/src/download.ts +0 -505
- package/src/embed.ts +0 -77
- package/src/engine/admission.ts +0 -59
- package/src/engine/cancellation.ts +0 -38
- package/src/engine/engine.ts +0 -56
- package/src/engine/execution-coordinator.ts +0 -70
- package/src/engine/execution-plan.ts +0 -53
- package/src/engine/resources.ts +0 -53
- package/src/engine/scheduler.ts +0 -72
- package/src/engine/session.ts +0 -247
- package/src/eval/README.md +0 -130
- package/src/eval/ifeval.ts +0 -81
- package/src/eval/kl-prompts.ts +0 -29
- package/src/eval/kl.ts +0 -590
- package/src/eval/perplexity.ts +0 -164
- package/src/eval/quality-db.ts +0 -92
- package/src/eval/reference-logits.ts +0 -207
- package/src/eval/runner.ts +0 -124
- package/src/eval/score.ts +0 -49
- package/src/eval/tasks/bfcl.ts +0 -535
- package/src/eval/tasks/gsm8k.ts +0 -98
- package/src/eval/tasks/hashhop.ts +0 -139
- package/src/eval/tasks/humaneval.ts +0 -275
- package/src/eval/tasks/ifeval.ts +0 -525
- package/src/eval/tasks/mmlu.ts +0 -157
- package/src/eval/ultrafeedback.ts +0 -60
- package/src/evaldb.ts +0 -199
- package/src/expert-io.ts +0 -393
- package/src/expert-offload-build.ts +0 -77
- package/src/expert-offload.ts +0 -76
- package/src/expert-residency.ts +0 -1134
- package/src/expert-usage.ts +0 -415
- package/src/fill/echo-index.ts +0 -188
- package/src/fill/fill-session.ts +0 -452
- package/src/fill/proposal.ts +0 -68
- package/src/fill/schema-rows.ts +0 -506
- package/src/fill/tool-boundary.ts +0 -103
- package/src/fit.ts +0 -249
- package/src/generate.ts +0 -1346
- package/src/grammar.ts +0 -591
- package/src/harness-pi.ts +0 -184
- package/src/hf-push.ts +0 -628
- package/src/hub-rest.ts +0 -242
- package/src/index.ts +0 -63
- package/src/inference/denoising.ts +0 -17
- package/src/inference/draft-acceptance.ts +0 -41
- package/src/inference/draft-output.ts +0 -49
- package/src/inference/graph.ts +0 -33
- package/src/inference/prefill.ts +0 -39
- package/src/inference/rollback.ts +0 -7
- package/src/jobs/index.ts +0 -31
- package/src/jobs/job-entry.ts +0 -107
- package/src/jobs/task-client.ts +0 -19
- package/src/jobs/types.ts +0 -2
- package/src/kv-scheme.ts +0 -257
- package/src/kv-store.ts +0 -1099
- package/src/lab/README.md +0 -18
- package/src/lab/curve/curve-designer.html +0 -303
- package/src/lab/curve/curve-sampler.ts +0 -122
- package/src/lab/expert-trace/expert-trace.ts +0 -69
- package/src/lab/paged-kv/paged-kv-rows.ts +0 -83
- package/src/lab/paged-kv/paged-kv.ts +0 -270
- package/src/library.ts +0 -40
- package/src/lora.ts +0 -408
- package/src/md-modules.d.ts +0 -49
- package/src/media-fetch.ts +0 -442
- package/src/memory/article.ts +0 -558
- package/src/memory/chunk-validation.ts +0 -150
- package/src/memory/chunk.ts +0 -318
- package/src/memory/cluster.ts +0 -351
- package/src/memory/coverage.ts +0 -134
- package/src/memory/crosslink.ts +0 -0
- package/src/memory/db.ts +0 -253
- package/src/memory/entity.ts +0 -203
- package/src/memory/gate.ts +0 -187
- package/src/memory/history.ts +0 -152
- package/src/memory/ingest.ts +0 -335
- package/src/memory/model.ts +0 -183
- package/src/memory/normalize.ts +0 -671
- package/src/memory/parse.ts +0 -22
- package/src/memory/pipeline.ts +0 -275
- package/src/memory/prompts.ts +0 -65
- package/src/memory/query.ts +0 -334
- package/src/memory/reconcile.ts +0 -734
- package/src/memory/redirect.ts +0 -108
- package/src/memory/reindex.ts +0 -325
- package/src/memory/resolve.ts +0 -307
- package/src/memory/rest.ts +0 -245
- package/src/memory/route.ts +0 -272
- package/src/memory/schedule.ts +0 -165
- package/src/memory/stages.ts +0 -777
- package/src/memory/synthesize.ts +0 -1638
- package/src/memory/tools.ts +0 -749
- package/src/memory/vault.ts +0 -1194
- package/src/memory/wikify.ts +0 -1064
- package/src/mlx/array.ts +0 -388
- package/src/mlx/autograd.ts +0 -304
- package/src/mlx/checkpoint.ts +0 -149
- package/src/mlx/compile.ts +0 -168
- package/src/mlx/custom-vjp.ts +0 -188
- package/src/mlx/ffi.ts +0 -512
- package/src/mlx/materialize.ts +0 -23
- package/src/mlx/metal-kernel.ts +0 -159
- package/src/mlx/ops.ts +0 -1021
- package/src/mlx/quantized-rows.ts +0 -36
- package/src/mlx/token-bitmask.ts +0 -52
- package/src/mlx/turboquant-kv-decode.ts +0 -82
- package/src/mlx/turboquant-kv-inverse.ts +0 -122
- package/src/mlx/turboquant-ops.ts +0 -861
- package/src/mlx/turboquant-tables.ts +0 -237
- package/src/mmap.ts +0 -90
- package/src/model/artifact-identity.ts +0 -73
- package/src/model/batched-kv.ts +0 -235
- package/src/model/batched-mask.ts +0 -279
- package/src/model/batched-quant.ts +0 -212
- package/src/model/batched-quantized-kv.ts +0 -144
- package/src/model/batched-rotating-quant.ts +0 -353
- package/src/model/batched-rotating-state.ts +0 -149
- package/src/model/batched-rotating.ts +0 -338
- package/src/model/batched-row-storage.ts +0 -179
- package/src/model/batched-ssm.ts +0 -52
- package/src/model/batched-turboquant-kv.ts +0 -93
- package/src/model/compiled-decode.ts +0 -712
- package/src/model/decoded-kv-donor.ts +0 -12
- package/src/model/delayed-quantized-kv.ts +0 -72
- package/src/model/delayed-rotating-quantized-kv.ts +0 -93
- package/src/model/delayed-turboquant-kv.ts +0 -55
- package/src/model/diffusion-gemma.ts +0 -1026
- package/src/model/factory.ts +0 -189
- package/src/model/fingerprint.ts +0 -33
- package/src/model/flash-attention.ts +0 -694
- package/src/model/full-prefill-padding.ts +0 -56
- package/src/model/full-prefill-row.ts +0 -105
- package/src/model/gemma4-base.ts +0 -2351
- package/src/model/gemma4.ts +0 -1315
- package/src/model/generated/gemma4-12b.ts +0 -429
- package/src/model/generated/gemma4-26b.ts +0 -448
- package/src/model/generated/gemma4-e4b.ts +0 -717
- package/src/model/generated/index.ts +0 -18
- package/src/model/glm52-atlas.ts +0 -511
- package/src/model/glm52-cache.ts +0 -792
- package/src/model/glm52-config.ts +0 -262
- package/src/model/glm52-container.ts +0 -305
- package/src/model/glm52-coupling.ts +0 -388
- package/src/model/glm52-dsa.ts +0 -606
- package/src/model/glm52-expert-layout.ts +0 -253
- package/src/model/glm52-layout.ts +0 -152
- package/src/model/glm52-memory.ts +0 -500
- package/src/model/glm52-mla.ts +0 -792
- package/src/model/glm52-moe.ts +0 -223
- package/src/model/glm52-pilot.ts +0 -537
- package/src/model/glm52-reference.ts +0 -445
- package/src/model/glm52-residency.ts +0 -557
- package/src/model/glm52-resident-weights.ts +0 -135
- package/src/model/glm52-streamed-experts.ts +0 -453
- package/src/model/glm52-streamed-metal.ts +0 -610
- package/src/model/glm52-weights.ts +0 -503
- package/src/model/glm52.ts +0 -1132
- package/src/model/implementation.ts +0 -69
- package/src/model/kv-attention-view.ts +0 -67
- package/src/model/minicpm5.ts +0 -376
- package/src/model/profile.ts +0 -443
- package/src/model/qwen-append.ts +0 -12
- package/src/model/qwen-conv.ts +0 -52
- package/src/model/qwen3-delta-state.ts +0 -74
- package/src/model/qwen3-delta.ts +0 -451
- package/src/model/qwen3-moe.ts +0 -404
- package/src/model/qwen3-mrope.ts +0 -252
- package/src/model/qwen3.ts +0 -277
- package/src/model/qwen3_5-checkpoint.ts +0 -164
- package/src/model/qwen3_5.ts +0 -819
- package/src/model/rotating-kv-donor.ts +0 -35
- package/src/model/rotating-kv-layout.ts +0 -262
- package/src/model/rotating-row-transaction.ts +0 -35
- package/src/model/speculative-rotating-kv.ts +0 -101
- package/src/model/ssm-prefill-padding.ts +0 -39
- package/src/model/support.ts +0 -102
- package/src/model/trellis-balanced-scatter.ts +0 -51
- package/src/model/trellis-linear.ts +0 -672
- package/src/model/trellis-shared-m.ts +0 -151
- package/src/model/trellis-shared-scatter.ts +0 -144
- package/src/model/trellis-splitk-prefill.ts +0 -158
- package/src/model/trellis-tiled-prefill.ts +0 -136
- package/src/model/trellis-vector-expand.ts +0 -62
- package/src/model/trellis-wide-prefill.ts +0 -94
- package/src/model/turboquant-codec.ts +0 -83
- package/src/model/universal/archs.ts +0 -332
- package/src/model/universal/dense.ts +0 -612
- package/src/model/universal/modules.ts +0 -170
- package/src/model/universal/rope.ts +0 -381
- package/src/native/expert_io.c +0 -523
- package/src/native/frame_extract.swift +0 -140
- package/src/native-pack.ts +0 -162
- package/src/pi-provider.ts +0 -53
- package/src/pi-session.ts +0 -81
- package/src/pi-terminal.ts +0 -274
- package/src/pi-web.ts +0 -1932
- package/src/preflight.ts +0 -158
- package/src/prompt-cache.ts +0 -460
- package/src/quantize/allocator.ts +0 -627
- package/src/quantize/atomic-output.ts +0 -38
- package/src/quantize/calibration.ts +0 -181
- package/src/quantize/config-writer.ts +0 -145
- package/src/quantize/index.ts +0 -84
- package/src/quantize/job.ts +0 -162
- package/src/quantize/quantizer.ts +0 -666
- package/src/quantize/rotate.ts +0 -603
- package/src/quantize/safetensors-writer.ts +0 -249
- package/src/quantize/sensitivity.ts +0 -216
- package/src/quantize/trellis.ts +0 -586
- package/src/quantize/weight-transform.ts +0 -292
- package/src/registry.ts +0 -602
- package/src/runtime-config.ts +0 -81
- package/src/safetensors.ts +0 -158
- package/src/sampler.ts +0 -1011
- package/src/serve/admin-routes.ts +0 -184
- package/src/serve/assets/curve-terrain.html +0 -105
- package/src/serve/assets/training-inference-map.html +0 -575
- package/src/serve/aux-routes.ts +0 -132
- package/src/serve/batch-scheduler.ts +0 -8
- package/src/serve/checkpoint-identity.ts +0 -64
- package/src/serve/generation-gateway.ts +0 -496
- package/src/serve/isolate.ts +0 -609
- package/src/serve/kv-budget.ts +0 -14
- package/src/serve/lab-routes.ts +0 -341
- package/src/serve/lane-registry.ts +0 -45
- package/src/serve/model-admin-routes.ts +0 -155
- package/src/serve/model-binding.ts +0 -43
- package/src/serve/model-host.ts +0 -511
- package/src/serve/parent-application.ts +0 -56
- package/src/serve/prompt-response-trace.ts +0 -147
- package/src/serve/response-lease.ts +0 -24
- package/src/serve/session-search.ts +0 -326
- package/src/serve/static-routes.ts +0 -101
- package/src/server.ts +0 -1314
- package/src/spec/assistant-rows.ts +0 -92
- package/src/spec/assistant-source.ts +0 -126
- package/src/spec/cache-identity.ts +0 -22
- package/src/spec/deepspec-rows.ts +0 -29
- package/src/spec/deepspec-source.ts +0 -145
- package/src/spec/dflash-source.ts +0 -149
- package/src/spec/drafter.ts +0 -304
- package/src/spec/dspark/ab-stats.ts +0 -136
- package/src/spec/dspark/calibration.ts +0 -112
- package/src/spec/dspark/data-dflash.ts +0 -156
- package/src/spec/dspark/data.ts +0 -201
- package/src/spec/dspark/deepspec-module.ts +0 -776
- package/src/spec/dspark/generate-dflash.ts +0 -245
- package/src/spec/dspark/generate.ts +0 -290
- package/src/spec/dspark/loader.ts +0 -23
- package/src/spec/dspark/loss.ts +0 -213
- package/src/spec/dspark/module-dflash.ts +0 -560
- package/src/spec/dspark/module.ts +0 -540
- package/src/spec/dspark/quantize-drafter.ts +0 -91
- package/src/spec/dspark/sample.ts +0 -112
- package/src/spec/generate.ts +0 -276
- package/src/spec/glm52-mtp-graph.ts +0 -29
- package/src/spec/glm52-mtp-rows.ts +0 -132
- package/src/spec/glm52-mtp-source.ts +0 -317
- package/src/spec/glm52-mtp-state.ts +0 -33
- package/src/spec/ngram-source.ts +0 -204
- package/src/spec/projected-draft-rows.ts +0 -73
- package/src/spec/qwen-mtp-module.ts +0 -171
- package/src/spec/qwen-mtp-rows.ts +0 -219
- package/src/spec/qwen-mtp-source.ts +0 -383
- package/src/spec/qwen-mtp-state.ts +0 -30
- package/src/spec/serve-loop.ts +0 -585
- package/src/spec/source.ts +0 -215
- package/src/spec/two-model-rows.ts +0 -61
- package/src/spec/two-model.ts +0 -113
- package/src/ssd-cache.ts +0 -372
- package/src/ssd-durability.ts +0 -211
- package/src/tokenizer.ts +0 -335
- package/src/tool-approvals.ts +0 -131
- package/src/tool-call.ts +0 -695
- package/src/train/dataset.ts +0 -484
- package/src/train/diffusion-lora.ts +0 -216
- package/src/train/export.ts +0 -32
- package/src/train/flash-cce.ts +0 -1234
- package/src/train/forward.ts +0 -266
- package/src/train/fuse.ts +0 -243
- package/src/train/index.ts +0 -46
- package/src/train/job.ts +0 -205
- package/src/train/lora-params.ts +0 -254
- package/src/train/loss.ts +0 -1591
- package/src/train/merge.ts +0 -195
- package/src/train/optimizer.ts +0 -183
- package/src/train/prefix-shared.ts +0 -490
- package/src/train/rank.ts +0 -137
- package/src/train/segmented.ts +0 -1828
- package/src/train/steel-qmm-header.ts +0 -1461
- package/src/train/trainer.ts +0 -1221
- package/src/train/watch.ts +0 -542
- package/src/vision/diffusion-vision.ts +0 -620
- package/src/vision/embedder.ts +0 -168
- package/src/vision/preprocess.ts +0 -247
- package/src/vision/prompt.ts +0 -373
- package/src/vision/qwen3vl-preprocess.ts +0 -409
- package/src/vision/qwen3vl-prompt.ts +0 -196
- package/src/vision/qwen3vl-tower.ts +0 -555
- package/src/vision/siglip.ts +0 -599
- package/src/vision/video-frames.ts +0 -148
- package/src/web/app.html +0 -2143
- package/src/web/app.js +0 -5703
- package/src/web/skills/memory/SKILL.md +0 -120
- package/src/web/skills/web-research/SKILL.md +0 -46
- package/src/web/skills.ts +0 -97
- package/src/web/src/adapters-panel.ts +0 -288
- package/src/web/src/assistant.ts +0 -451
- package/src/web/src/chat.ts +0 -1057
- package/src/web/src/composer.ts +0 -959
- package/src/web/src/dataset.ts +0 -132
- package/src/web/src/finetune.ts +0 -267
- package/src/web/src/hub.ts +0 -428
- package/src/web/src/main.ts +0 -98
- package/src/web/src/markdown.ts +0 -0
- package/src/web/src/memory-panel.ts +0 -572
- package/src/web/src/model-picker.ts +0 -159
- package/src/web/src/palette.ts +0 -346
- package/src/web/src/protocol.ts +0 -26
- package/src/web/src/quantize.ts +0 -167
- package/src/web/src/rag.ts +0 -274
- package/src/web/src/sessions.ts +0 -469
- package/src/web/src/shell.ts +0 -804
- package/src/web/src/ui-catalog.ts +0 -78
- package/src/web/vendor/README.md +0 -61
- package/src/web-assets.ts +0 -47
- package/src/web-tools.ts +0 -478
- package/src/weights.ts +0 -255
- /package/src/{serve → server}/completion-sink.ts +0 -0
- /package/src/web/{icon.svg → public/icon.svg} +0 -0
- /package/src/web/{manifest.webmanifest → public/manifest.webmanifest} +0 -0
- /package/src/web/{vendor → public/vendor}/hljs-LICENSE +0 -0
package/src/web/src/shell.ts
DELETED
|
@@ -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) => ({ "&": "&", "<": "<", ">": ">", '"': """ } 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 & 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
|
-
}
|