Skip to content

Commit 7ebf3fa

Browse files
authored
Merge pull request RooCodeInc#923 from samhvw8/fix/fail-to-get-model-list
refactor: unify model picker components
2 parents 1f07a77 + 1653263 commit 7ebf3fa

File tree

6 files changed

+152
-236
lines changed

6 files changed

+152
-236
lines changed

src/core/webview/ClineProvider.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1834,6 +1834,12 @@ export class ClineProvider implements vscode.WebviewViewProvider {
18341834
if (!apiKey) {
18351835
apiKey = (await this.getSecret("requestyApiKey")) as string
18361836
}
1837+
1838+
if (!apiKey) {
1839+
this.outputChannel.appendLine("No Requesty API key found")
1840+
return models
1841+
}
1842+
18371843
if (apiKey) {
18381844
config["headers"] = { Authorization: `Bearer ${apiKey}` }
18391845
}
@@ -1884,7 +1890,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
18841890
this.outputChannel.appendLine("Invalid response from Requesty API")
18851891
}
18861892
await fs.writeFile(requestyModelsFilePath, JSON.stringify(models))
1887-
this.outputChannel.appendLine(`Requesty models fetched and saved: ${JSON.stringify(models, null, 2)}`)
18881893
} catch (error) {
18891894
this.outputChannel.appendLine(
18901895
`Error fetching Requesty models: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,

webview-ui/src/components/settings/ApiOptions.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage, fromWelcomeView }: A
252252
value={apiConfiguration?.requestyApiKey || ""}
253253
style={{ width: "100%" }}
254254
type="password"
255-
onInput={handleInputChange("requestyApiKey")}
255+
onBlur={handleInputChange("requestyApiKey")}
256256
placeholder="Enter API Key...">
257257
<span style={{ fontWeight: 500 }}>Requesty API Key</span>
258258
</VSCodeTextField>

webview-ui/src/components/settings/ModelPicker.tsx

Lines changed: 107 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
22
import debounce from "debounce"
3-
import { useMemo, useState, useCallback, useEffect } from "react"
3+
import { useMemo, useState, useCallback, useEffect, useRef } from "react"
44
import { useMount } from "react-use"
55
import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons"
66

@@ -23,15 +23,24 @@ import { vscode } from "../../utils/vscode"
2323
import { normalizeApiConfiguration } from "./ApiOptions"
2424
import { ModelInfoView } from "./ModelInfoView"
2525

26-
interface ModelPickerProps {
26+
type ModelProvider = "glama" | "openRouter" | "unbound" | "requesty" | "openAi"
27+
28+
type ModelKeys<T extends ModelProvider> = `${T}Models`
29+
type ConfigKeys<T extends ModelProvider> = `${T}ModelId`
30+
type InfoKeys<T extends ModelProvider> = `${T}ModelInfo`
31+
type RefreshMessageType<T extends ModelProvider> = `refresh${Capitalize<T>}Models`
32+
33+
interface ModelPickerProps<T extends ModelProvider = ModelProvider> {
2734
defaultModelId: string
28-
modelsKey: "glamaModels" | "openRouterModels" | "unboundModels" | "requestyModels"
29-
configKey: "glamaModelId" | "openRouterModelId" | "unboundModelId" | "requestyModelId"
30-
infoKey: "glamaModelInfo" | "openRouterModelInfo" | "unboundModelInfo" | "requestyModelInfo"
31-
refreshMessageType: "refreshGlamaModels" | "refreshOpenRouterModels" | "refreshUnboundModels" | "refreshRequestyModels"
35+
modelsKey: ModelKeys<T>
36+
configKey: ConfigKeys<T>
37+
infoKey: InfoKeys<T>
38+
refreshMessageType: RefreshMessageType<T>
39+
refreshValues?: Record<string, any>
3240
serviceName: string
3341
serviceUrl: string
3442
recommendedModel: string
43+
allowCustomModel?: boolean
3544
}
3645

3746
export const ModelPicker = ({
@@ -40,25 +49,51 @@ export const ModelPicker = ({
4049
configKey,
4150
infoKey,
4251
refreshMessageType,
52+
refreshValues,
4353
serviceName,
4454
serviceUrl,
4555
recommendedModel,
56+
allowCustomModel = false,
4657
}: ModelPickerProps) => {
58+
const [customModelId, setCustomModelId] = useState("")
59+
const [isCustomModel, setIsCustomModel] = useState(false)
4760
const [open, setOpen] = useState(false)
4861
const [value, setValue] = useState(defaultModelId)
4962
const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false)
63+
const prevRefreshValuesRef = useRef<Record<string, any> | undefined>()
5064

51-
const { apiConfiguration, setApiConfiguration, [modelsKey]: models, onUpdateApiConfig } = useExtensionState()
52-
const modelIds = useMemo(() => Object.keys(models).sort((a, b) => a.localeCompare(b)), [models])
65+
const { apiConfiguration, [modelsKey]: models, onUpdateApiConfig, setApiConfiguration } = useExtensionState()
66+
67+
const modelIds = useMemo(
68+
() => (Array.isArray(models) ? models : Object.keys(models)).sort((a, b) => a.localeCompare(b)),
69+
[models],
70+
)
5371

5472
const { selectedModelId, selectedModelInfo } = useMemo(
5573
() => normalizeApiConfiguration(apiConfiguration),
5674
[apiConfiguration],
5775
)
5876

77+
const onSelectCustomModel = useCallback(
78+
(modelId: string) => {
79+
setCustomModelId(modelId)
80+
const modelInfo = { id: modelId }
81+
const apiConfig = { ...apiConfiguration, [configKey]: modelId, [infoKey]: modelInfo }
82+
setApiConfiguration(apiConfig)
83+
onUpdateApiConfig(apiConfig)
84+
setValue(modelId)
85+
setOpen(false)
86+
setIsCustomModel(false)
87+
},
88+
[apiConfiguration, configKey, infoKey, onUpdateApiConfig, setApiConfiguration],
89+
)
90+
5991
const onSelect = useCallback(
6092
(modelId: string) => {
61-
const apiConfig = { ...apiConfiguration, [configKey]: modelId, [infoKey]: models[modelId] }
93+
const modelInfo = Array.isArray(models)
94+
? { id: modelId } // For OpenAI models which are just strings
95+
: models[modelId] // For other models that have full info objects
96+
const apiConfig = { ...apiConfiguration, [configKey]: modelId, [infoKey]: modelInfo }
6297
setApiConfiguration(apiConfig)
6398
onUpdateApiConfig(apiConfig)
6499
setValue(modelId)
@@ -67,16 +102,42 @@ export const ModelPicker = ({
67102
[apiConfiguration, configKey, infoKey, models, onUpdateApiConfig, setApiConfiguration],
68103
)
69104

70-
const debouncedRefreshModels = useMemo(
71-
() => debounce(() => vscode.postMessage({ type: refreshMessageType }), 50),
72-
[refreshMessageType],
73-
)
105+
const debouncedRefreshModels = useMemo(() => {
106+
return debounce(() => {
107+
const message = refreshValues
108+
? { type: refreshMessageType, values: refreshValues }
109+
: { type: refreshMessageType }
110+
vscode.postMessage(message)
111+
}, 100)
112+
}, [refreshMessageType, refreshValues])
74113

75114
useMount(() => {
76115
debouncedRefreshModels()
77116
return () => debouncedRefreshModels.clear()
78117
})
79118

119+
useEffect(() => {
120+
if (!refreshValues) {
121+
prevRefreshValuesRef.current = undefined
122+
return
123+
}
124+
125+
// Check if all values in refreshValues are truthy
126+
if (Object.values(refreshValues).some((value) => !value)) {
127+
prevRefreshValuesRef.current = undefined
128+
return
129+
}
130+
131+
// Compare with previous values
132+
const prevValues = prevRefreshValuesRef.current
133+
if (prevValues && JSON.stringify(prevValues) === JSON.stringify(refreshValues)) {
134+
return
135+
}
136+
137+
prevRefreshValuesRef.current = refreshValues
138+
debouncedRefreshModels()
139+
}, [debouncedRefreshModels, refreshValues])
140+
80141
useEffect(() => setValue(selectedModelId), [selectedModelId])
81142

82143
return (
@@ -104,6 +165,17 @@ export const ModelPicker = ({
104165
</CommandItem>
105166
))}
106167
</CommandGroup>
168+
{allowCustomModel && (
169+
<CommandGroup heading="Custom">
170+
<CommandItem
171+
onSelect={() => {
172+
setIsCustomModel(true)
173+
setOpen(false)
174+
}}>
175+
+ Add custom model
176+
</CommandItem>
177+
</CommandGroup>
178+
)}
107179
</CommandList>
108180
</Command>
109181
</PopoverContent>
@@ -125,6 +197,28 @@ export const ModelPicker = ({
125197
<VSCodeLink onClick={() => onSelect(recommendedModel)}>{recommendedModel}.</VSCodeLink>
126198
You can also try searching "free" for no-cost options currently available.
127199
</p>
200+
{allowCustomModel && isCustomModel && (
201+
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
202+
<div className="bg-[var(--vscode-editor-background)] p-6 rounded-lg w-96">
203+
<h3 className="text-lg font-semibold mb-4">Add Custom Model</h3>
204+
<input
205+
type="text"
206+
className="w-full p-2 mb-4 bg-[var(--vscode-input-background)] text-[var(--vscode-input-foreground)] border border-[var(--vscode-input-border)] rounded"
207+
placeholder="Enter model ID"
208+
value={customModelId}
209+
onChange={(e) => setCustomModelId(e.target.value)}
210+
/>
211+
<div className="flex justify-end gap-2">
212+
<Button variant="secondary" onClick={() => setIsCustomModel(false)}>
213+
Cancel
214+
</Button>
215+
<Button onClick={() => onSelectCustomModel(customModelId)} disabled={!customModelId.trim()}>
216+
Add
217+
</Button>
218+
</div>
219+
</div>
220+
</div>
221+
)}
128222
</>
129223
)
130224
}

0 commit comments

Comments
 (0)