feat(custom-model): promote the currently-loaded/last-used model in the Run-menu picker - #459
opticon454 wants to merge 3 commits into
Conversation
…he Run-menu picker Custom Model Endpoint Profiles' "which model" picker (session-ui.js's _openCustomModelPickModal) always listed models in their raw discovery order, so on a host with several downloaded GGUFs the user had to remember (or eyeball the "Default" tag) which one llama-swap actually had hot before picking — the whole point of the picker being fast is undone if it makes you think first. The picker now promotes exactly one model to the top of the list: - If llama-swap reports a model from this host's own list `ready` right now (via the existing GET /api/model-endpoints/:id/running-status route), that model is promoted and tagged "Currently loaded" — it's what a launch attaches to with zero wait. - Otherwise, the last model actually launched on this exact (harness, endpoint) pair is promoted and tagged "Last used", read from a new per-device localStorage key (codeman:customModelLastUsed:<mode>:<endpointId>), written by runCustomModelEntry on every launch attempt regardless of outcome. - A plain (non-llama-swap) OpenAI-compatible server, an unreachable endpoint, or a loaded-but-not-yet-ready model never promotes anything — the rest of the list keeps its discovery order. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N6eadpRyqpA9PD3i139cSD
…perseded probe Code review (high effort) on the previous commit found a real race: making _openCustomModelPickModal async (it now awaits the currently-loaded-model probe before rendering) meant a second, faster call for a different endpoint could render first, only for the first call's slower probe to resolve afterwards and overwrite the modal with the wrong endpoint's model list — while _pendingCustomModelPick (set synchronously, before either await) still named the second, correct endpoint. Picking a model in that state would launch/apply the wrong model on the wrong endpoint. Fixed with the same mutable-generation-counter guard _watchLlamaSwapLoading already uses for an identical async-superseded-by- newer-call shape: every DOM write, including _pendingCustomModelPick itself, is deferred until after the awaited probe, and a call that finds its generation already superseded bails out untouched instead of clobbering whatever a newer call already rendered. Added a regression test driving two overlapping opens with a controlled promise so the earlier, slower probe resolves after the later, faster one renders, asserting the late response is a no-op. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N6eadpRyqpA9PD3i139cSD
|
Thanks for this, and for the care in the commit messages. Promoting the model llama-swap already has hot, with a remembered "last used" as the fallback, is the right instinct for this picker: the zero-wait choice should be the one under your thumb. The race you found in your own first commit is real, and I checked it: the new regression test fails on 458ca57 (host A's rows render over host B's modal) and passes on the fix. Four things, then it goes in.
Two smaller things I will take at merge rather than send back: the promoted row loses its "Default" marking, since the tag slot is exclusive (so when the default is also the loaded model, nothing says it is the default), and Everything else is clean here: typecheck, lint, format:check, check:frontend-syntax and the test suites all pass, CI is green on the head commit, and the six new tests are real tests rather than assertions about strings. Push 1 and 2 and I will merge, or say the word and I will fold them in myself. |
…out, defer "last used" past confirmation, docs, zh-CN Four things from the maintainer's review on PR Ark0N#459, all fixed: 1. Bound _getCustomModelCurrentlyLoaded's probe client-side (~800ms via Promise.race, on top of — never instead of — the route's own 5s server-side timeout). Without it, an asleep/firewalled endpoint behind a saved model list left the picker completely invisible for up to 5s after the Run menu had already closed, with no spinner or toast. `timeoutMs` is an optional param (default 800, real callers never pass it) so a test can drive it in milliseconds, same pattern as `_watchLlamaSwapLoading`'s own `pollIntervalMs` — this code runs in a JSDOM window's own realm, whose setTimeout vi.useFakeTimers() cannot patch. 2. "Last used" is now written only once a launch actually applies, never on the mere click. It moved out of runCustomModelEntry (unconditional) and into each path's own success point: _quickStartWithCustomModelConfirm after the final post succeeds, and _runCustomModelEntryViaRestart right after the apply's success check. A context-window-warning decline means this exact model cannot work with this CLI at all, so the old unconditional write would promote, next time the picker opened, the one model guaranteed to fail again. 3. Documented the promotion/tag precedence and the new codeman:customModelLastUsed:<mode>:<endpointId> localStorage key in both CLAUDE.md's Custom Model Endpoint Profiles section and docs/custom-model-endpoints.md's Run-menu picker section. 4. Added zh-CN entries for "Currently loaded" and "Last used" in i18n.js, next to this modal's existing "Choose a model"/"Custom Endpoints" pair. New tests: the client-side timeout (endpoint that never answers, one that answers within the bound, and a rejected-after-timeout probe settling quietly), and "last used" recording on success vs. NOT recording on either confirmation's decline, for both the restart and one-shot paths. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N6eadpRyqpA9PD3i139cSD
|
Thanks for the careful read — all four fixed in 88e5b7b:
Left the two "I'll take at merge" items (Default-marking on the promoted row, typecheck/lint/format:check/check:frontend-syntax all clean; new + existing custom-model suites green (only the pre-existing, unrelated Windows-sandbox failures noted before: no tmux, a POSIX-chmod test that doesn't apply on this box). 🤖 Generated with Claude Code |
Summary
readyright now (via the existingGET /api/model-endpoints/:id/running-statusroute). This is what a launch attaches to with zero wait.localStoragekey (codeman:customModelLastUsed:<mode>:<endpointId>), written byrunCustomModelEntryon every launch attempt.readymodel never promotes anything — the rest of the list keeps its discovery order, and the existingDefaulttag still shows where neither applies.async(to await the currently-loaded probe) meant a slower probe for an earlier click could resolve after a faster click for a different endpoint and overwrite the modal with the wrong endpoint's models. Guarded with the same generation-counter pattern_watchLlamaSwapLoadingalready uses.Test plan
npm run typecheck— cleannpm run lint— cleannpm run check:frontend-syntax— cleannpx prettier --checkon both changed files — cleantest/custom-model-run-menu-ui.test.tscovering: currently-loaded promotion, last-used fallback, currently-loaded taking precedence over a stale last-used, ignoring a loaded-but-not-ready/no-longer-discovered model,runCustomModelEntryrecording last-used, and the async-race regression (58/58 in the file, all green)npm test(7723 tests) run for regressions — only pre-existing, unrelated environment failures on this Windows sandbox (no tmux,spawn npx ENOENT, port conflicts — the same class of failure CLAUDE.md already documents as sandbox-only)/code-review highrun on the full diff; its one finding (the async race above) fixed and covered by a regression test🤖 Generated with Claude Code
https://claude.ai/code/session_01N6eadpRyqpA9PD3i139cSD