UI integration plan.
Browse files
plans/2025-08-01-model-filtering/5-ui-worker-model-integration.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## Worker Model Listing β UI Integration Plan
|
| 2 |
+
|
| 3 |
+
Purpose: document a complete, actionable plan to replace UI-side HF model fetching and caching with the worker-based `listChatModels` flow, remove UI caching, and defer slash-command creation until the worker returns the final model list.
|
| 4 |
+
|
| 5 |
+
Requirements checklist
|
| 6 |
+
- Use the `listChatModels` action name everywhere (do not introduce `loadChatModels`).
|
| 7 |
+
- Remove UI-side HF network logic and localStorage caching.
|
| 8 |
+
- Defer Crepe/Milkdown BlockEdit population until the worker returns the final models list.
|
| 9 |
+
- Keep the implementation simple, minimal, and easy to iterate on.
|
| 10 |
+
- Expose cancellation for the listing operation.
|
| 11 |
+
- Provide a small hard-coded fallback list if the worker fails.
|
| 12 |
+
- Make changes recoverable (do not permanently obliterate legacy code without a backup or relying on git).
|
| 13 |
+
|
| 14 |
+
## One-line summary
|
| 15 |
+
Replace `fetchBrowserModels()` in the UI with a thin worker-backed proxy that calls `listChatModels`, expose a cancellable wrapper in `src/app/worker-connection.js`, and update `src/app/init-milkdown.js` to wait for the final list before adding the BlockEdit `models` group.
|
| 16 |
+
|
| 17 |
+
## Deliverables (files to change)
|
| 18 |
+
- Edit: `src/app/worker-connection.js` β add a small wrapper that returns `{ id, promise, cancel }` for `listChatModels` requests.
|
| 19 |
+
- Edit: `src/app/model-list.js` β replace the heavy `fetchBrowserModels()` implementation with a worker-proxy (no caching/localStorage).
|
| 20 |
+
- Edit: `src/app/init-milkdown.js` β defer BlockEdit creation until the worker returns the final list; show a simple placeholder while waiting.
|
| 21 |
+
- Add (optional): `src/app/_legacy/fetchBrowserModels.removed.js` β move old implementation here for safe disposal (or rely on git history).
|
| 22 |
+
- Add (optional): `test/app/model-list.integration.test.js` β a fast smoke test that mocks `listChatModels` and verifies BlockEdit population.
|
| 23 |
+
|
| 24 |
+
## API contract (UI-side wrapper)
|
| 25 |
+
- Function: `listChatModels(params = {}, onProgress?)` β keep this name and behavior.
|
| 26 |
+
- Wrapper return: `{ id, promise, cancel }` where:
|
| 27 |
+
- `id` (string) is the request id posted to the worker;
|
| 28 |
+
- `promise` resolves to the final `ModelEntry[]` (use the shape from the plan) or rejects on fatal error;
|
| 29 |
+
- `cancel()` posts `{ type: 'cancelListChatModels', id }` and cleans up local pending state.
|
| 30 |
+
- `onProgress(delta)` will be called with small delta objects emitted by the worker. Keep deltas as-is (no heavy transformation).
|
| 31 |
+
|
| 32 |
+
Keep the `fetchBrowserModels()` export stable: it should return a Promise resolving to the model array so existing callers need no changes.
|
| 33 |
+
|
| 34 |
+
## Implementation steps (detailed, exact)
|
| 35 |
+
|
| 36 |
+
1) Worker client wrapper (`src/app/worker-connection.js`)
|
| 37 |
+
|
| 38 |
+
- Replace the current `listChatModels` helper with a thin wrapper that returns `{ id, promise, cancel }`:
|
| 39 |
+
- Generate an `id` (e.g., `String(Math.random()).slice(2)`).
|
| 40 |
+
- Create a pending entry in the connection's `pending` map containing `resolve`, `reject`, and `onProgress`.
|
| 41 |
+
- `worker.postMessage({ type: 'listChatModels', id, params })`.
|
| 42 |
+
- `promise` is a new Promise that resolves/rejects via the pending entry.
|
| 43 |
+
- `cancel()` posts `{ type: 'cancelListChatModels', id }` and removes the pending entry.
|
| 44 |
+
|
| 45 |
+
- Keep naming strictly `listChatModels` (do not introduce `loadChatModels`).
|
| 46 |
+
|
| 47 |
+
2) UI proxy in `src/app/model-list.js`
|
| 48 |
+
|
| 49 |
+
- Replace the heavy implementation of `fetchBrowserModels()` with a worker-proxy implementation. Keep the same exported function name/signature to preserve callers.
|
| 50 |
+
|
| 51 |
+
- Minimal behavior for `fetchBrowserModels()`:
|
| 52 |
+
- Call `const { id, promise, cancel } = workerConnection().listChatModels(params, onProgress)`.
|
| 53 |
+
- Optionally handle simple timeout (e.g., `Promise.race([promise, timeout(30_000)])`). If timed out, call `cancel()` and fall back.
|
| 54 |
+
- Map the `ModelEntry[]` returned by the worker to the previous UI shape expected by callers (keep only the fields UI needs: `id`, `name`, `size`, `slashCommand`, `pipeline_tag`, etc.). Use concise mapping.
|
| 55 |
+
- No localStorage, no caching, no persisted TTL logic.
|
| 56 |
+
- Maintain an in-memory transient map for in-progress deltas if you want to display progress β but keep it small; for this iteration final result suffices.
|
| 57 |
+
|
| 58 |
+
- Provide a small hard-coded fallback array of 3β5 known models to return on worker failure.
|
| 59 |
+
|
| 60 |
+
3) Defer BlockEdit creation (`src/app/init-milkdown.js`)
|
| 61 |
+
|
| 62 |
+
- Start Crepe without the BlockEdit `models` group (as the code already does in the repository).
|
| 63 |
+
- After mounting creat the Crepe editor, call `fetchBrowserModels()` (the new worker-backed function) and await the final array.
|
| 64 |
+
- Once the worker returns the final models list, call `crepeInput.addFeature(blockEdit, { buildMenu })` and populate the `models` group there.
|
| 65 |
+
- buildMenu: loop final models and call `group.addItem(model.slashCommand, { label, icon, onRun })`.
|
| 66 |
+
- While waiting, show a simple spinner or placeholder. Keep the UX minimal: BlockEdit appears only after the final list resolves.
|
| 67 |
+
|
| 68 |
+
4) Remove caching + cleanup
|
| 69 |
+
|
| 70 |
+
- Remove any `localStorage` persistence logic and in-memory long-lived caches from `src/app/model-list.js`.
|
| 71 |
+
- Do not delete the legacy implementation irreversibly in the first pass. Move the old code into `src/app/_legacy/fetchBrowserModels.removed.js` or rely on git to recover it. Add a short header comment in `src/app/model-list.js` explaining the replacement.
|
| 72 |
+
|
| 73 |
+
5) Add fallback & feature flag
|
| 74 |
+
|
| 75 |
+
- Add a small `FALLBACK_MODELS` constant inside `src/app/model-list.js` or a small `fallback-models.js` file. Return this when the worker fails.
|
| 76 |
+
- Add `const FEATURE_LIST_CHAT_MODELS_WORKER = true` (or read from an ENV/config object) near top of `init-milkdown.js` so you can toggle behavior quickly.
|
| 77 |
+
|
| 78 |
+
6) Tests & smoke
|
| 79 |
+
|
| 80 |
+
- Integration test: `test/app/model-list.integration.test.js` β mock `workerConnection().listChatModels` to return a resolved promise; assert that `init-milkdown()` ends up adding BlockEdit items.
|
| 81 |
+
- Unit test: small test for `worker-connection` wrapper to assert returned `{ id, promise, cancel }` shape and that cancel posts a cancel message (stub worker).
|
| 82 |
+
|
| 83 |
+
## Mapping worker ModelEntry -> UI model shape
|
| 84 |
+
|
| 85 |
+
Worker returns ModelEntry with these fields (plan):
|
| 86 |
+
|
| 87 |
+
```
|
| 88 |
+
{
|
| 89 |
+
id,
|
| 90 |
+
name?,
|
| 91 |
+
pipeline_tag?,
|
| 92 |
+
siblings?,
|
| 93 |
+
tags?,
|
| 94 |
+
model_type?,
|
| 95 |
+
architectures?,
|
| 96 |
+
classification: 'gen'|'encoder'|'unknown'|'auth-protected',
|
| 97 |
+
confidence: 'high'|'medium'|'low',
|
| 98 |
+
fetchStatus: 'ok'|'404'|'401'|'403'|'429'|'error',
|
| 99 |
+
fetchError?
|
| 100 |
+
}
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
Map into the minimal UI `ModelInfo` shape used by `init-milkdown` (example):
|
| 104 |
+
|
| 105 |
+
- `id`: same
|
| 106 |
+
- `name`: entry.name || humanized(id)
|
| 107 |
+
- `vendor`: extract from id (reuse `extractVendor` helper)
|
| 108 |
+
- `size`: keep conservative default or empty
|
| 109 |
+
- `slashCommand`: keep `generateSlashCommand(id)` helper usage
|
| 110 |
+
- `pipeline_tag`: pass through
|
| 111 |
+
- `requiresAuth`: classification === 'auth-protected'
|
| 112 |
+
|
| 113 |
+
Keep the mapping concise with a single `map()` call.
|
| 114 |
+
|
| 115 |
+
## Edge cases & caveats
|
| 116 |
+
|
| 117 |
+
- Cold start (no caching): expect slower initial load; mitigate by returning `FALLBACK_MODELS` on error.
|
| 118 |
+
- Many progress events: the worker may stream many small deltas; for the first iteration, only use the final `done` response to build BlockEdit. Optionally show a spinner while progress is in-flight.
|
| 119 |
+
- Rate limiting and auth-protected repos: worker classifies these; UI should honor `classification` and `confidence` but can ignore nuance for this initial pass.
|
| 120 |
+
- Cancellation: callers must keep the `id` or `cancel` handle and call it when unmounting.
|
| 121 |
+
- API name strictness: keep `listChatModels` everywhere; do not create `loadChatModels`.
|
| 122 |
+
|
| 123 |
+
## Rollout and rollback
|
| 124 |
+
|
| 125 |
+
- Implement in a feature branch and commit in small steps: 1) worker wrapper, 2) `fetchBrowserModels` replacement, 3) `init-milkdown` change, 4) tests + fallback.
|
| 126 |
+
- Test locally: open the editor, confirm the BlockEdit `models` group appears after the worker finishes and slash commands work.
|
| 127 |
+
- If anything breaks, toggle `FEATURE_LIST_CHAT_MODELS_WORKER` to `false` or revert via git.
|
| 128 |
+
|
| 129 |
+
## Time estimates
|
| 130 |
+
- `worker-connection` wrapper: 15β30 minutes
|
| 131 |
+
- `model-list` replacement: 30β60 minutes
|
| 132 |
+
- `init-milkdown` deferment: 30β60 minutes
|
| 133 |
+
- Tests & verification: 30β90 minutes
|
| 134 |
+
Total: ~1.5β3.5 hours.
|
| 135 |
+
|
| 136 |
+
## Minimal code style guidance (brevity & elegance)
|
| 137 |
+
- Use concise arrow functions and destructuring.
|
| 138 |
+
- Favor `map()`/`filter()` over manual loops.
|
| 139 |
+
- Keep try/catch blocks narrowly scoped.
|
| 140 |
+
- One-liner helpers for timeouts and small utilities: `const wait = ms => new Promise(r => setTimeout(r, ms));`
|
| 141 |
+
- Keep file-level exports small and stable; prefer preserving function names to avoid cascade edits.
|
| 142 |
+
|
| 143 |
+
## Next step (recommended)
|
| 144 |
+
- Implement the `listChatModels` wrapper in `src/app/worker-connection.js` and replace `fetchBrowserModels()` body in `src/app/model-list.js` with the worker-backed proxy. Then update `src/app/init-milkdown.js` to await the final list before adding BlockEdit. Add `FALLBACK_MODELS` for safety.
|
| 145 |
+
|
| 146 |
+
---
|
| 147 |
+
|
| 148 |
+
This document is intentionally prescriptive and conservative: small, reversible edits; stable export names; and minimal UI changes for the first iteration. Once this is merged and verified, we can iterate to add caching, incremental UI updates, and telemetry.
|