feat(site/src/pages/AgentsPage): redesign chat model selector dropdown#26355
Draft
tracyjohnsonux wants to merge 9 commits into
Draft
feat(site/src/pages/AgentsPage): redesign chat model selector dropdown#26355tracyjohnsonux wants to merge 9 commits into
tracyjohnsonux wants to merge 9 commits into
Conversation
Replace the Radix Select-based ModelSelector with a Popover + Command
(cmdk) implementation that matches the new agents-chatbox design:
- Search input at the top of the panel for fast model lookup with
strict, substring-based filtering.
- Provider names render as section headings ("Anthropic", "OpenAI").
- Each model row shows the display name with a compact context
window chip, for example "Opus 4.7 (1M)".
- The selected row gets a darker background and a trailing checkmark.
- The trigger is a subtle inline pill with chevron that keeps the
keyboard-focus ring and suppresses the mouse-focus ring.
- A read-only Effort row at the bottom renders the admin-configured
reasoning effort for the selected model. Per-chat effort selection
is a backend feature; this PR ships the layout without faking
interactivity. See the PR description for the open question.
ModelSelectorOption gains an optional `effort` field, and
getModelOptionsFromConfigs reads the reasoning effort from each
provider's options block so the new row shows real data.
Co-authored-by: Coder Agents <agents@coder.com>
- Loosen row spacing in the popover so each row's hover background has visible breathing room from its neighbours (gap-1 between items, h-10 row height, px-2.5 horizontal padding, bumped heading padding). - Left-align the dropdown with the trigger instead of centering it. Radix's collision detection nudges the popover back into view when the trigger is close to the viewport edge. - Turn the Effort row into an interactive slider, capped at the admin-configured value, so designers can demo lowering the model's reasoning effort. The selected value is local-only state and is not sent to the backend; this is a UI preview until the per-chat effort override is wired through the API. Tooltip text updated to spell that out. Co-authored-by: Coder Agents <agents@coder.com>
…icker scrollbar - Drop the `border-t-0` override on the model picker's `CommandList` so the theme-aware top border from `Command` draws a clean rule between the search input and the first model row. - Style the picker's scrollbar narrow: `scrollbar-width: thin` for Firefox plus an 8px webkit scrollbar with a rounded `surface-quaternary` thumb on a transparent track. 8px keeps a usable hit area for the thumb while shedding the visual weight of the default browser scrollbar. Co-authored-by: Coder Agents <agents@coder.com>
… every model - Hide the round slider thumb in the Effort row so the visual indicator is purely the end of the filled track. The thumb element stays in the DOM at full size, so pointer drag and keyboard interaction still work; only its paint is suppressed via arbitrary Tailwind selectors on [role=slider]. - Always render the Effort row when a model is selected, falling back to `xhigh` when the admin has not configured an effort. This is a demo-only fallback so every model in the picker tells the same story; remove it once the backend lands and the row should hide for models that do not support reasoning. Co-authored-by: Coder Agents <agents@coder.com>
- Lock the effort badge to a fixed width (`w-[4.5rem]`) with `justify-start` so the slider track keeps a constant length as the label flips between `Low`, `Medium`, `High`, etc. - Animate the slider's transform, position, and width over 150ms so stepping between effort levels glides instead of snapping. The duration is short enough that pointer drags still feel snappy at each step boundary. - Replace the hidden-thumb treatment with a small (10px) solid-white thumb, no border or shadow, with a grab/grabbing cursor so the handle reads as draggable without dominating the row. Co-authored-by: Coder Agents <agents@coder.com>
…n opened Pass the currently-selected model's cmdk value as the Command's `defaultValue` so cmdk highlights and scrolls the row into view on mount, instead of the dropdown opening at the top of the list with the user's choice hidden below the fold. Extract the cmdk-value shape into `getCmdkValue` so the same string is used both when rendering a row and when telling cmdk which row is active. Co-authored-by: Coder Agents <agents@coder.com>
Remove the border, background, rounded corners, and h-6 box around the effort label so it reads as plain text next to the slider. The fixed 4.5rem width and `justify-start` are preserved so the slider track length still stays constant as the label flips between values like "Low" and "Xhigh". Co-authored-by: Coder Agents <agents@coder.com>
… picker cmdk applies `data-selected=true` to its active row, which can be the hovered row, the keyboard cursor row, or whichever row cmdk auto-advanced to after a search. Painting that with the same `bg-surface-secondary` used for the persistent-selected row made the two read as identical, so typing into the search appeared to select a different model than the one with the checkmark. Drop the active state to a faint white overlay (`bg-content-primary/[0.08]`) so the cursor stays visible for keyboard users without competing with the real selection. Co-authored-by: Coder Agents <agents@coder.com>
Override the Slider primitive's default 8px track height down to 6px (`h-1.5`) for the effort slider in the model picker. The Track is the slider's only descendant with `bg-surface-secondary`, so the class selector targets it stably; the parent-scoped override wins on specificity over the primitive's own `h-2`. The 10px thumb stays the same size so the proportions read like a control dot riding a slim track. Co-authored-by: Coder Agents <agents@coder.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Redesign of the agents-chatbox model dropdown to match the new layout: a searchable list grouped by provider, a clear "selected" treatment with a trailing checkmark, and an Effort row at the bottom of the panel. The trigger is a subtle inline pill with a chevron that fits next to the existing chat-footer controls.
This is a draft opened on @tracyjohnsonux's behalf by the Coder Agents bot for design review. Backend was intentionally not touched.
Visual reference
Screenshots of the implementation will be added as a follow-up comment on this PR.
What changed (frontend only)
ModelSelector.tsx: replaced RadixSelectwithPopover+Command(cmdk).Opus 4.7 (1M).aria-labelon each row is the model display name, so existinggetByRole("option", { name: ... })lookups keep matching even though the visible label now includes the context chip.Effortrow at the bottom of the panel renders the admin-configured reasoning effort for the selected model as a read-only slider plus value chip. Hidden when the selected model has no effort configured. Important: see the open question below before merging.ModelSelectorOptiongains an optionaleffort?: stringfield.modelOptions.ts:getModelOptionsFromConfigsnow reads the reasoning effort from each provider's options block (provider_options.openai.reasoning_effort,provider_options.openaicompat.reasoning_effort,provider_options.anthropic.effort) and passes it through so the Effort row shows real configuration values.ModelSelector.stories.tsx: rewritten around the new design. New stories coverOpenSingleProvider,OpenMultipleProviders,OpenWithoutEffort,SearchFiltersOptions, plus trigger/disabled/empty states.Open question (decision needed before this can become real)
The design shows an interactive Effort slider with
low / medium / high / xhighvalues. Today, the backend stores reasoning effort only on the admin-managedChatModelConfig:CreateChatRequestand the message-send payloads have noreasoning_effortfield. So a user-controllable per-chat effort slider needs a backend change.This PR ships the row as a read-only display of the admin-configured value so the layout is real and not faking interactivity. Three options for what comes next:
CreateChatRequestand the chat-send websocket payload, plumbed throughChatModelConfig.model_config.provider_options.{openai,openaicompat,anthropic}at request time). Then make the slider here interactive.Need your call here, @tracyjohnsonux.
Tests
pnpm exec tsc --noEmitclean.pnpm exec biome check src/clean.vitest --project unit) for AgentsPage: 1370 passed.Tool.stories.tsx > MCP Tool Completedreproduces onmainwithout these changes; it is a pre-existing xterm flake.)make lint/emdashclean.Implementation notes for reviewers
Select, which forces a listbox-only popover and does not support the bottom Effort row. Switching toPopover+Commandgives us the search input "for free" via cmdk and lets us attach the Effort footer to the same popover surface.opuswould not fully hideSonnet). The picker overridesfilterwith a strictvalue.includes(needle)so typing narrows cleanly.data-[disabled]:opacity-40to its Track when disabled. The Effort row explicitly overrides that with[&_[data-disabled]]:opacity-100so the read-only display stays at full visual weight; it's communicating real configuration, not a disabled control.focus:ring-0suppresses the mouse-focus ring,focus-visible:ring-2 focus-visible:ring-content-linkkeeps the keyboard-focus ring. The existing trigger test (ModelSelector.test.tsx) still passes against the new markup.role="combobox"+aria-haspopup="listbox"+aria-expandedare set on the trigger so the test helpers inAgentSettingsAgentsPageView.stories.tsx(getByRole("combobox", { name: ... })) keep working.This pull request was prepared by the Coder Agents bot at @tracyjohnsonux's direction.