Skip to content

Commit 3e03fc3

Browse files
committed
Group chat models by provider and clarify install settings
- Replace provider submenus with grouped model sections in the picker - Update settings copy for auth and custom model provider scope - Adjust picker tests for the new grouped layout
1 parent 5b42730 commit 3e03fc3

3 files changed

Lines changed: 38 additions & 32 deletions

File tree

apps/web/src/components/chat/ProviderModelPicker.browser.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ describe("ProviderModelPicker", () => {
5050
document.body.innerHTML = "";
5151
});
5252

53-
it("shows provider submenus when provider switching is allowed", async () => {
53+
it("shows both Codex and Anthropic model groups when provider switching is allowed", async () => {
5454
const mounted = await mountPicker({
5555
provider: "claudeAgent",
5656
model: "claude-opus-4-6",
@@ -64,7 +64,9 @@ describe("ProviderModelPicker", () => {
6464
const text = document.body.textContent ?? "";
6565
expect(text).toContain("Codex");
6666
expect(text).toContain("Anthropic");
67-
expect(text).not.toContain("Claude Sonnet 4.6");
67+
expect(text).toContain("GPT-5 Codex");
68+
expect(text).toContain("Claude Sonnet 4.6");
69+
expect(text).toContain("Claude Haiku 4.5");
6870
});
6971
} finally {
7072
await mounted.cleanup();

apps/web/src/components/chat/ProviderModelPicker.tsx

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,12 @@ import { Button } from "../ui/button";
77
import {
88
Menu,
99
MenuGroup,
10+
MenuGroupLabel,
1011
MenuItem,
1112
MenuPopup,
1213
MenuRadioGroup,
1314
MenuRadioItem,
1415
MenuSeparator as MenuDivider,
15-
MenuSub,
16-
MenuSubPopup,
17-
MenuSubTrigger,
1816
MenuTrigger,
1917
} from "../ui/menu";
2018
import { ClaudeAI, CursorIcon, Gemini, Icon, OpenAI, OpenCodeIcon } from "../Icons";
@@ -48,6 +46,10 @@ function providerIconClassName(
4846
return provider === "claudeAgent" ? "text-[#d97757]" : fallbackClassName;
4947
}
5048

49+
function getProviderLabel(provider: ProviderKind): string {
50+
return AVAILABLE_PROVIDER_OPTIONS.find((option) => option.value === provider)?.label ?? provider;
51+
}
52+
5153
export const ProviderModelPicker = memo(function ProviderModelPicker(props: {
5254
provider: ProviderKind;
5355
model: ModelSlug;
@@ -122,6 +124,9 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: {
122124
<MenuPopup align="start">
123125
{props.lockedProvider !== null ? (
124126
<MenuGroup>
127+
<MenuGroupLabel className="px-2 pb-1 pt-2 text-[11px] uppercase tracking-[0.08em]">
128+
{getProviderLabel(props.lockedProvider)} · locked for this thread
129+
</MenuGroupLabel>
125130
<MenuRadioGroup
126131
value={props.model}
127132
onValueChange={(value) => handleModelChange(props.lockedProvider!, value)}
@@ -139,39 +144,36 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: {
139144
</MenuGroup>
140145
) : (
141146
<>
142-
{AVAILABLE_PROVIDER_OPTIONS.map((option) => {
147+
{AVAILABLE_PROVIDER_OPTIONS.map((option, index) => {
143148
const OptionIcon = PROVIDER_ICON_BY_PROVIDER[option.value];
144149
return (
145-
<MenuSub key={option.value}>
146-
<MenuSubTrigger>
150+
<MenuGroup key={option.value}>
151+
{index > 0 ? <MenuDivider /> : null}
152+
<MenuGroupLabel className="flex items-center gap-2 px-2 pb-1 pt-2 text-[11px] uppercase tracking-[0.08em]">
147153
<OptionIcon
148154
aria-hidden="true"
149155
className={cn(
150156
"size-4 shrink-0",
151157
providerIconClassName(option.value, "text-muted-foreground/85"),
152158
)}
153159
/>
154-
{option.label}
155-
</MenuSubTrigger>
156-
<MenuSubPopup className="[--available-height:min(24rem,70vh)]">
157-
<MenuGroup>
158-
<MenuRadioGroup
159-
value={props.provider === option.value ? props.model : ""}
160-
onValueChange={(value) => handleModelChange(option.value, value)}
160+
<span>{option.label}</span>
161+
</MenuGroupLabel>
162+
<MenuRadioGroup
163+
value={props.provider === option.value ? props.model : ""}
164+
onValueChange={(value) => handleModelChange(option.value, value)}
165+
>
166+
{props.modelOptionsByProvider[option.value].map((modelOption) => (
167+
<MenuRadioItem
168+
key={`${option.value}:${modelOption.slug}`}
169+
value={modelOption.slug}
170+
onClick={() => setIsMenuOpen(false)}
161171
>
162-
{props.modelOptionsByProvider[option.value].map((modelOption) => (
163-
<MenuRadioItem
164-
key={`${option.value}:${modelOption.slug}`}
165-
value={modelOption.slug}
166-
onClick={() => setIsMenuOpen(false)}
167-
>
168-
{modelOption.name}
169-
</MenuRadioItem>
170-
))}
171-
</MenuRadioGroup>
172-
</MenuGroup>
173-
</MenuSubPopup>
174-
</MenuSub>
172+
{modelOption.name}
173+
</MenuRadioItem>
174+
))}
175+
</MenuRadioGroup>
176+
</MenuGroup>
175177
);
176178
})}
177179
{UNAVAILABLE_PROVIDER_OPTIONS.length > 0 && <MenuDivider />}

apps/web/src/routes/_chat.settings.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ const INSTALL_PROVIDER_SETTINGS: readonly InstallProviderSettings[] = [
7878
binaryPlaceholder: "Codex binary path",
7979
binaryDescription: (
8080
<>
81-
Leave blank to use <code>codex</code> from your PATH.
81+
Leave blank to use <code>codex</code> from your PATH. Authentication normally uses{" "}
82+
<code>codex login</code> unless your Codex config points at a custom model provider.
8283
</>
8384
),
8485
homePathKey: "codexHomePath",
@@ -92,7 +93,8 @@ const INSTALL_PROVIDER_SETTINGS: readonly InstallProviderSettings[] = [
9293
binaryPlaceholder: "Claude binary path",
9394
binaryDescription: (
9495
<>
95-
Leave blank to use <code>claude</code> from your PATH.
96+
Leave blank to use <code>claude</code> from your PATH. Authentication uses{" "}
97+
<code>claude auth login</code>.
9698
</>
9799
),
98100
},
@@ -673,7 +675,7 @@ function SettingsRouteView() {
673675

674676
<SettingsRow
675677
title="Custom models"
676-
description="Add custom model slugs for supported providers."
678+
description="Add custom model slugs for Codex or Anthropic. The chat picker groups models by provider."
677679
resetAction={
678680
totalCustomModels > 0 ? (
679681
<SettingResetButton
@@ -805,7 +807,7 @@ function SettingsRouteView() {
805807
<SettingsSection title="Advanced">
806808
<SettingsRow
807809
title="Provider installs"
808-
description="Override the CLI used for new sessions."
810+
description="Override the CLI binaries and auth homes used for new sessions."
809811
resetAction={
810812
isInstallSettingsDirty ? (
811813
<SettingResetButton

0 commit comments

Comments
 (0)