Skip to content

feat(models): manage model visibility from the chat selector - #849

Open
AKAZIK-py wants to merge 1 commit into
agegr:mainfrom
AKAZIK-py:feat/model-visibility-popup
Open

AKAZIK-py wants to merge 1 commit into
agegr:mainfrom
AKAZIK-py:feat/model-visibility-popup

Conversation

@AKAZIK-py

Copy link
Copy Markdown
Contributor

Problem

Two long-standing gaps around the model selector:

  1. Deleting a model in Settings → Models doesn't remove it from the chat selector. models.json is an upsert/override layer on top of pi's built-in provider catalogs, so a deleted entry just falls back to the built-in definition for any configured provider (Model dialog doesn't clean up enabledModels in settings.json when a connector/models are removed → persistent 'No models match pattern' warnings #560, After model deletion, the pi-web UI still displays "Model scope warnings," and the newly added Kimi K3 is not in the model dropdown list. #640). PR fix: prune removed models from enabled scope #561's implicit prune was rejected as by-design, and the supported lever — the enabledModels scope in ~/.pi/agent/settings.json — had no UI in pi-web at all.
  2. The selector shows every available model with no way to hide any (feat: support hiding/pinning/filtering models in UI model selector #241, 供应商添加和模型选择问题 #324 — the filter-only half was already shipped). pi's own answer to this is the global enabledModels scope, manageable in the TUI via /scoped-models but not from the web UI.

Solution

A "Manage visible models" entry at the bottom of the chat model selector dropdown opens a dialog that edits pi's native enabledModels scope explicitly:

  • Every available model listed, grouped by provider, with filter, per-provider tri-state toggles, and a global select-all
  • Checkbox state starts from the models the selector currently shows (the server-resolved scope — no client-side reimplementation of pi's matching rules)
  • Saving writes exact provider/modelId patterns; checking every model clears the scope back to the show-all default, so newly added models keep appearing automatically
  • Scopes using globs, bare ids, or :thinkingLevel pins are flagged in the dialog before saving replaces them with the explicit list

API

Safety rails

  • Empty selection rejected client-side and server-side: pi falls back to showing every model when a scope matches nothing, so saving an empty scope would silently undo the edit
  • The PUT route resolves the scope before writing and refuses zero-match patterns (a stale/typo'd list can never wipe the selector)

Relation to #727

Complementary rather than overlapping: #727 adds per-provider catalog cards inside Settings → Models with segment-preserving rewrites; this PR is a lighter, in-flow entry point that manages the whole scope in one view. Both edit the same pi-native setting, so they compose. Happy to rework if you'd rather land only one approach — the API safety rails here (zero-match refusal) and the dialog component are reusable either way.

Verification

  • npm test — 1032 pass (incl. new lib/model-visibility.test.mjs and components/ModelVisibilityDialog.test.mjs)
  • tsc --noEmit, eslint, next build clean
  • Manual end-to-end against next dev: GET/PUT round-trip writes enabledModels to settings.json, /api/models reflects the scope immediately (cache invalidation), zero-match and empty-array PUTs return 400, null restores the file byte-identically

Fixes the hiding half of #241 / #324; gives #560 / #640 users an in-app escape hatch instead of hand-editing settings.json.

Deleting a model under Settings > Models never removes it from the chat
selector when the provider is built into pi's catalog: models.json is an
upsert/override layer, so the built-in definition keeps serving the model
(agegr#560, agegr#640). The supported lever is the enabledModels scope in
~/.pi/agent/settings.json, which pi-web read but offered no UI to edit —
the selector showed every available model with no way to hide any.

This adds a "Manage visible models" entry to the model selector dropdown:

- ModelSelector grows an onManage hook that renders the entry in the
  panel footer; ChatInput forwards it from ChatWindow
- ModelVisibilityDialog lists every available model (grouped by provider,
  with filter, per-provider and global tri-state selection) starting from
  the models the selector currently shows
- Saving writes exact provider/modelId patterns via
  PUT /api/models-visibility; checking every model clears the scope back
  to pi's show-all default so newly added models keep appearing
- GET /api/models-visibility returns the raw patterns plus the full
  unfiltered catalog; writes invalidate the models cache and bump
  modelsRefreshKey so the selector reloads immediately (agegr#822)
- an empty selection is rejected client- and server-side: pi falls back
  to showing every model when a scope matches nothing, so saving one
  would silently undo the edit; the PUT route resolves the scope first
  and refuses zero-match patterns
- scopes that use globs, bare ids, or :thinkingLevel pins are flagged in
  the dialog before saving replaces them with the explicit list

Complements agegr#727 (settings-panel catalog cards) with a lighter, in-flow
entry point; both edit the same pi-native setting. Addresses the hiding
half of agegr#241 / agegr#324.

Tests: lib/model-visibility.test.mjs (pattern computation and
validation), components/ModelVisibilityDialog.test.mjs (dialog shell,
selector entry wiring). npm test 1032 pass; tsc --noEmit, eslint, and
next build clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants