Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit 31be701

Browse files
committed
Revert "Improve API config picker layout"
This reverts commit 14f10d5.
1 parent 14f10d5 commit 31be701

3 files changed

Lines changed: 102 additions & 234 deletions

File tree

.changeset/two-column-api-config-selector.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

webview-ui/src/components/chat/ApiConfigSelector.tsx

Lines changed: 42 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useMemo, useCallback, useEffect } from "react"
1+
import { useState, useMemo, useCallback } from "react"
22
import { Fzf } from "fzf"
33

44
import { cn } from "@/lib/utils"
@@ -10,68 +10,14 @@ import { Button } from "@/components/ui"
1010

1111
import { IconButton } from "./IconButton"
1212

13-
type ApiConfigMeta = {
14-
id: string
15-
name: string
16-
apiProvider?: string
17-
modelId?: string
18-
}
19-
20-
type ProviderGroup = {
21-
key: string
22-
label: string
23-
configs: ApiConfigMeta[]
24-
}
25-
26-
const PROVIDER_LABELS: Record<string, string> = {
27-
anthropic: "Anthropic",
28-
bedrock: "Amazon Bedrock",
29-
deepseek: "DeepSeek",
30-
gemini: "Google Gemini",
31-
"gemini-cli": "Gemini CLI",
32-
vertex: "Vertex AI",
33-
openai: "OpenAI Compatible",
34-
"openai-native": "OpenAI",
35-
"openai-codex": "OpenAI Codex",
36-
openrouter: "OpenRouter",
37-
ollama: "Ollama",
38-
lmstudio: "LM Studio",
39-
mistral: "Mistral",
40-
moonshot: "Moonshot",
41-
minimax: "MiniMax",
42-
requesty: "Requesty",
43-
unbound: "Unbound",
44-
poe: "Poe",
45-
xai: "xAI",
46-
baseten: "Baseten",
47-
litellm: "LiteLLM",
48-
sambanova: "SambaNova",
49-
zai: "Z.ai",
50-
fireworks: "Fireworks AI",
51-
"qwen-code": "Qwen Code",
52-
roo: "Roo",
53-
"vscode-lm": "VS Code LM",
54-
"vercel-ai-gateway": "Vercel AI Gateway",
55-
}
56-
57-
const getProviderKey = (config?: ApiConfigMeta) => config?.apiProvider ?? "unknown"
58-
59-
const getProviderLabel = (apiProvider?: string) => {
60-
if (!apiProvider) {
61-
return "Other"
62-
}
63-
64-
return PROVIDER_LABELS[apiProvider] ?? apiProvider
65-
}
66-
6713
interface ApiConfigSelectorProps {
6814
value: string
6915
displayName: string
7016
disabled?: boolean
7117
title: string
7218
onChange: (value: string) => void
7319
triggerClassName?: string
74-
listApiConfigMeta: ApiConfigMeta[]
20+
listApiConfigMeta: Array<{ id: string; name: string; modelId?: string }>
7521
pinnedApiConfigs?: Record<string, boolean>
7622
togglePinnedApiConfig: (id: string) => void
7723
lockApiConfigAcrossModes: boolean
@@ -94,17 +40,14 @@ export const ApiConfigSelector = ({
9440
const { t } = useAppTranslation()
9541
const [open, setOpen] = useState(false)
9642
const [searchValue, setSearchValue] = useState("")
97-
const [activeProviderKey, setActiveProviderKey] = useState<string>("")
9843
const portalContainer = useRooPortal("roo-portal")
9944

10045
// Create searchable items for fuzzy search.
10146
const searchableItems = useMemo(
10247
() =>
10348
listApiConfigMeta.map((config) => ({
10449
original: config,
105-
searchStr: [config.name, config.modelId, config.apiProvider, getProviderLabel(config.apiProvider)]
106-
.filter(Boolean)
107-
.join(" "),
50+
searchStr: config.name,
10851
})),
10952
[listApiConfigMeta],
11053
)
@@ -125,129 +68,54 @@ export const ApiConfigSelector = ({
12568
return matchingItems
12669
}, [listApiConfigMeta, searchValue, fzfInstance])
12770

128-
const providerGroups = useMemo<ProviderGroup[]>(() => {
129-
const groups = new Map<string, ProviderGroup>()
130-
131-
for (const config of filteredConfigs) {
132-
const key = getProviderKey(config)
133-
134-
if (!groups.has(key)) {
135-
groups.set(key, {
136-
key,
137-
label: getProviderLabel(config.apiProvider),
138-
configs: [],
139-
})
140-
}
141-
142-
groups.get(key)!.configs.push(config)
143-
}
144-
145-
return Array.from(groups.values()).map((group) => ({
146-
...group,
147-
configs: [...group.configs].sort((a, b) => {
148-
const pinnedDelta = Number(!!pinnedApiConfigs?.[b.id]) - Number(!!pinnedApiConfigs?.[a.id])
149-
150-
return pinnedDelta
151-
}),
152-
}))
71+
// Separate pinned and unpinned configs.
72+
const { pinnedConfigs, unpinnedConfigs } = useMemo(() => {
73+
const pinned = filteredConfigs.filter((config) => pinnedApiConfigs?.[config.id])
74+
const unpinned = filteredConfigs.filter((config) => !pinnedApiConfigs?.[config.id])
75+
return { pinnedConfigs: pinned, unpinnedConfigs: unpinned }
15376
}, [filteredConfigs, pinnedApiConfigs])
15477

155-
const currentConfig = useMemo(
156-
() => listApiConfigMeta.find((config) => config.id === value),
157-
[listApiConfigMeta, value],
158-
)
159-
160-
const currentProviderKey = getProviderKey(currentConfig)
161-
const preferredProviderKey = providerGroups.some((group) => group.key === currentProviderKey)
162-
? currentProviderKey
163-
: (providerGroups[0]?.key ?? "")
164-
165-
useEffect(() => {
166-
if (!providerGroups.length) {
167-
setActiveProviderKey("")
168-
return
169-
}
170-
171-
if (!providerGroups.some((group) => group.key === activeProviderKey)) {
172-
setActiveProviderKey(preferredProviderKey)
173-
}
174-
}, [activeProviderKey, preferredProviderKey, providerGroups])
175-
176-
const activeProviderGroup = providerGroups.find((group) => group.key === activeProviderKey) ?? providerGroups[0]
177-
17878
const handleSelect = useCallback(
17979
(configId: string) => {
180-
const selectedConfig = listApiConfigMeta.find((config) => config.id === configId)
181-
182-
if (selectedConfig) {
183-
setActiveProviderKey(getProviderKey(selectedConfig))
184-
}
185-
18680
onChange(configId)
18781
setOpen(false)
18882
setSearchValue("")
18983
},
190-
[listApiConfigMeta, onChange],
84+
[onChange],
19185
)
19286

19387
const handleEditClick = useCallback(() => {
19488
vscode.postMessage({ type: "switchTab", tab: "settings" })
19589
setOpen(false)
19690
}, [])
19791

198-
const renderProviderItem = useCallback(
199-
(group: ProviderGroup) => {
200-
const isActive = group.key === activeProviderGroup?.key
201-
const currentCount = group.configs.filter((config) => config.id === value).length
202-
const pinnedCount = group.configs.filter((config) => pinnedApiConfigs?.[config.id]).length
203-
204-
return (
205-
<button
206-
key={group.key}
207-
type="button"
208-
onClick={() => setActiveProviderKey(group.key)}
209-
className={cn(
210-
"w-full px-2 py-1.5 text-left text-sm cursor-pointer flex items-center gap-2",
211-
"hover:bg-vscode-list-hoverBackground",
212-
isActive &&
213-
"bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground",
214-
)}>
215-
<span className="truncate flex-1 min-w-0">{group.label}</span>
216-
<div className="flex items-center gap-1 flex-shrink-0">
217-
{pinnedCount > 0 && <span className="codicon codicon-pin text-[10px] opacity-60" />}
218-
{currentCount > 0 && <span className="codicon codicon-check text-xs" />}
219-
<span className="text-[10px] opacity-70">{group.configs.length}</span>
220-
</div>
221-
</button>
222-
)
223-
},
224-
[activeProviderGroup?.key, pinnedApiConfigs, value],
225-
)
226-
227-
const renderModelItem = useCallback(
228-
(config: ApiConfigMeta) => {
92+
const renderConfigItem = useCallback(
93+
(config: { id: string; name: string; modelId?: string }, isPinned: boolean) => {
22994
const isCurrentConfig = config.id === value
230-
const isPinned = !!pinnedApiConfigs?.[config.id]
23195

23296
return (
23397
<div
23498
key={config.id}
23599
onClick={() => handleSelect(config.id)}
236100
className={cn(
237-
"px-3 py-1.5 text-sm cursor-pointer flex items-center group gap-2",
101+
"px-3 py-1.5 text-sm cursor-pointer flex items-center group",
238102
"hover:bg-vscode-list-hoverBackground",
239103
isCurrentConfig &&
240104
"bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground",
241105
)}>
242-
<div className="flex-1 min-w-0 flex flex-col overflow-hidden leading-tight">
243-
<span className="truncate">{config.modelId || config.name}</span>
106+
<div className="flex-1 min-w-0 flex items-center gap-1 overflow-hidden">
107+
<span className="flex-shrink-0">{config.name}</span>
244108
{config.modelId && (
245-
<span className="text-xs text-vscode-descriptionForeground opacity-80 truncate">
246-
{config.name}
247-
</span>
109+
<>
110+
<span
111+
className="text-vscode-descriptionForeground opacity-70 min-w-0 overflow-hidden"
112+
style={{ direction: "rtl", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
113+
{config.modelId}
114+
</span>
115+
</>
248116
)}
249117
</div>
250-
<div className="flex items-center gap-1 flex-shrink-0">
118+
<div className="flex items-center gap-1">
251119
{isCurrentConfig && (
252120
<div className="size-5 p-1 flex items-center justify-center">
253121
<span className="codicon codicon-check text-xs" />
@@ -274,7 +142,7 @@ export const ApiConfigSelector = ({
274142
</div>
275143
)
276144
},
277-
[value, pinnedApiConfigs, handleSelect, t, togglePinnedApiConfig],
145+
[value, handleSelect, t, togglePinnedApiConfig],
278146
)
279147

280148
return (
@@ -299,7 +167,7 @@ export const ApiConfigSelector = ({
299167
align="start"
300168
sideOffset={4}
301169
container={portalContainer}
302-
className="p-0 overflow-hidden w-[520px] max-w-[calc(100vw-24px)]">
170+
className="p-0 overflow-hidden w-[300px]">
303171
<div className="flex flex-col w-full">
304172
{/* Search input or info blurb */}
305173
{listApiConfigMeta.length > 6 ? (
@@ -329,29 +197,29 @@ export const ApiConfigSelector = ({
329197
</div>
330198
)}
331199

332-
{/* Provider/model picker */}
200+
{/* Config list - single scroll container */}
333201
{filteredConfigs.length === 0 && searchValue ? (
334202
<div className="py-2 px-3 text-sm text-vscode-foreground/70">{t("common:ui.no_results")}</div>
335203
) : (
336-
<div className="grid grid-cols-[170px_minmax(0,1fr)] max-h-[320px] overflow-hidden">
337-
<div
338-
className="border-r border-vscode-dropdown-border overflow-y-auto"
339-
data-testid="api-provider-column"
340-
aria-label={t("settings:providers.apiProvider")}>
341-
<div className="sticky top-0 z-10 bg-vscode-dropdown-background px-2 py-1 text-[10px] uppercase tracking-wide text-vscode-descriptionForeground border-b border-vscode-dropdown-border">
342-
{t("settings:providers.apiProvider")}
204+
<div className="max-h-[300px] overflow-y-auto">
205+
{/* Pinned configs - sticky header */}
206+
{pinnedConfigs.length > 0 && (
207+
<div
208+
className={cn(
209+
"sticky top-0 z-10 bg-vscode-dropdown-background py-1",
210+
unpinnedConfigs.length > 0 && "border-b border-vscode-dropdown-foreground/10",
211+
)}
212+
aria-label="Pinned configurations">
213+
{pinnedConfigs.map((config) => renderConfigItem(config, true))}
343214
</div>
344-
<div className="py-1">{providerGroups.map(renderProviderItem)}</div>
345-
</div>
346-
<div
347-
className="overflow-y-auto"
348-
data-testid="api-model-column"
349-
aria-label={t("settings:providers.model")}>
350-
<div className="sticky top-0 z-10 bg-vscode-dropdown-background px-3 py-1 text-[10px] uppercase tracking-wide text-vscode-descriptionForeground border-b border-vscode-dropdown-border">
351-
{activeProviderGroup?.label ?? t("settings:providers.model")}
215+
)}
216+
217+
{/* Unpinned configs */}
218+
{unpinnedConfigs.length > 0 && (
219+
<div className="py-1" aria-label="All configurations">
220+
{unpinnedConfigs.map((config) => renderConfigItem(config, false))}
352221
</div>
353-
<div className="py-1">{activeProviderGroup?.configs.map(renderModelItem)}</div>
354-
</div>
222+
)}
355223
</div>
356224
)}
357225

0 commit comments

Comments
 (0)