fix(site/src/pages/AISettingsPage/ModelsPage): duplicate Add model dropdown in empty state (#26685)

Mirrors the providers page on `/ai/settings/providers`: when the models
table is empty on `/ai/settings/models`, the empty state now renders an
**Add model** dropdown alongside the description so users have an
obvious next step.

## Changes

- `AddModelDropdown` accepts an optional `align` prop (defaults to
`"end"`), so the existing header instance is unchanged.
- The empty state passes a second instance via `TableEmpty`'s `cta` prop
with `align="start"`, matching how `ProvidersPageView` duplicates
`AddProviderDropdown`.
- Updated the `Empty` Storybook story to assert two **Add model**
buttons render (header + empty state).

## Verification

- `pnpm --dir site exec biome check
src/pages/AISettingsPage/ModelsPage/`
- `pnpm --dir site exec tsc -p . --noEmit`
- `pnpm --dir site exec vitest run --project=storybook
src/pages/AISettingsPage/ModelsPage/` (20/20 stories pass, including the
updated `Empty` play)

<details>
<summary>Reference: providers page pattern</summary>

`ProvidersPageView.tsx` already does this with `AddProviderDropdown`:

```tsx
<TableEmpty
  message="No providers configured"
  cta={<AddProviderDropdown align="start" />}
/>
```

This PR brings the models page in line with that pattern.

</details>

---

> [!NOTE]
> Opened by Coder Agents on behalf of @tracyjohnsonux.
This commit is contained in:
TJ
2026-06-24 17:58:14 -07:00
committed by GitHub
parent 612b6d4e95
commit 99c0362b26
2 changed files with 14 additions and 4 deletions
@@ -65,6 +65,9 @@ export const Empty: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expect(canvas.getByText("No models configured")).toBeInTheDocument();
await expect(
canvas.getAllByRole("button", { name: /add model/i }).length,
).toBe(2);
},
};
@@ -35,9 +35,10 @@ import { ModelRow } from "./components/ModelRow";
const MODELS_PAGE_SIZE = 10;
const AddModelDropdown: FC<{ providerStates: readonly ProviderState[] }> = ({
providerStates,
}) => {
const AddModelDropdown: FC<{
providerStates: readonly ProviderState[];
align?: "start" | "end";
}> = ({ providerStates, align = "end" }) => {
const navigate = useNavigate();
const manageableProviderStates = providerStates.filter(
canManageProviderModels,
@@ -52,7 +53,7 @@ const AddModelDropdown: FC<{ providerStates: readonly ProviderState[] }> = ({
<ChevronDownIcon className="ml-1 size-icon-xs" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="min-w-56">
<DropdownMenuContent align={align} className="min-w-56">
<div className="px-2 py-1.5 text-xs font-medium text-content-secondary">
Select a provider
</div>
@@ -140,6 +141,12 @@ const ModelsPageView: FC<ModelsPageViewProps> = ({
<TableEmpty
message="No models configured"
description="Configured models will appear here."
cta={
<AddModelDropdown
providerStates={providerStates}
align="start"
/>
}
/>
) : (
pagedItems.map((model) => (