provider config UI changes and ExtensionStateContext loading state

This commit is contained in:
slytechnical 2025-05-30 11:04:10 -05:00
parent 605a00921b
commit 656bb9f4d0
8 changed files with 109 additions and 19 deletions

View file

@ -72,7 +72,7 @@ const ApiOptions = ({
setErrorMessage,
}: ApiOptionsProps) => {
const { t } = useAppTranslation()
const { organizationAllowList } = useExtensionState()
const { organizationAllowList, areProviderModelsLoading } = useExtensionState()
const refetchRouterModels = useCallback(() => {
vscode.postMessage({
@ -130,11 +130,16 @@ const ApiOptions = ({
}
}, [selectedModelId, setApiConfigurationField])
// Validation logic using the global loading state
useEffect(() => {
const apiValidationResult = validateApiConfiguration(apiConfiguration, organizationAllowList)
setErrorMessage(apiValidationResult)
}, [apiConfiguration, organizationAllowList, setErrorMessage])
if (!areProviderModelsLoading) {
// Only validate if provider models are not currently loading
const apiValidationResult = validateApiConfiguration(apiConfiguration, organizationAllowList)
setErrorMessage(apiValidationResult)
} else {
setErrorMessage(undefined)
}
}, [apiConfiguration, organizationAllowList, setErrorMessage, areProviderModelsLoading])
const selectedProviderModels = useMemo(() => {
const models = MODELS_BY_PROVIDER[selectedProvider]

View file

@ -1,4 +1,4 @@
import { useCallback } from "react"
import { useCallback, useMemo } from "react"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import type { ProviderSettings, OrganizationAllowList } from "@roo-code/types"
@ -22,7 +22,19 @@ type GlamaProps = {
export const Glama = ({ apiConfiguration, setApiConfigurationField, uriScheme, organizationAllowList }: GlamaProps) => {
const { t } = useAppTranslation()
const { models: glamaModelsData, isLoading: isLoadingModels, error: modelsError } = useProviderModels("glama")
const providerModelsOptions = useMemo(
() => ({
flushCacheFirst: true,
}),
[],
)
const {
models: glamaModelsData,
isLoading: isLoadingModels,
error: modelsError,
refetch: refetchGlamaModels,
} = useProviderModels("glama", providerModelsOptions)
const handleInputChange = useCallback(
<K extends keyof ProviderSettings, E>(
@ -70,6 +82,7 @@ export const Glama = ({ apiConfiguration, setApiConfigurationField, uriScheme, o
serviceName="Glama"
serviceUrl="https://glama.ai/models"
organizationAllowList={organizationAllowList}
onOpenRefetch={refetchGlamaModels}
/>
</>
)

View file

@ -1,4 +1,4 @@
import { useCallback } from "react"
import { useCallback, useMemo } from "react"
import { VSCodeTextField, VSCodeRadioGroup, VSCodeRadio } from "@vscode/webview-ui-toolkit/react"
import type { ProviderSettings } from "@roo-code/types"
@ -15,7 +15,20 @@ type OllamaProps = {
export const Ollama = ({ apiConfiguration, setApiConfigurationField }: OllamaProps) => {
const { t } = useAppTranslation()
const { models: ollamaModelsData, isLoading: isLoadingModels, error: modelsError } = useProviderModels("ollama")
const providerModelsOptions = useMemo(
() => ({
flushCacheFirst: true,
baseUrl: apiConfiguration?.ollamaBaseUrl,
}),
[apiConfiguration?.ollamaBaseUrl],
)
const {
models: ollamaModelsData,
isLoading: isLoadingModels,
error: modelsError,
// refetch is not used directly by this component for now
} = useProviderModels("ollama", providerModelsOptions)
const handleInputChange = useCallback(
<K extends keyof ProviderSettings, E>(

View file

@ -1,4 +1,4 @@
import { useState, useCallback, useEffect } from "react"
import { useState, useCallback, useEffect, useMemo } from "react"
import { Checkbox } from "vscrui"
import { VSCodeButton, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
@ -32,11 +32,24 @@ export const OpenAICompatible = ({
const [azureApiVersionSelected, setAzureApiVersionSelected] = useState(!!apiConfiguration?.azureApiVersion)
const [openAiLegacyFormatSelected, setOpenAiLegacyFormatSelected] = useState(!!apiConfiguration?.openAiLegacyFormat)
const providerModelsOptions = useMemo(() => {
if (!apiConfiguration?.openAiBaseUrl) {
return undefined
}
return {
flushCacheFirst: true,
baseUrl: apiConfiguration.openAiBaseUrl,
apiKey: apiConfiguration?.openAiApiKey,
openAiHeaders: apiConfiguration?.openAiHeaders,
}
}, [apiConfiguration?.openAiBaseUrl, apiConfiguration?.openAiApiKey, apiConfiguration?.openAiHeaders])
const {
models: openAiCompatibleModels,
isLoading: isLoadingOpenAiCompatibleModels,
error: openAiCompatibleModelsError,
} = useProviderModels("openai-compatible")
refetch: refetchOpenAiCompatibleModels,
} = useProviderModels("openai-compatible", providerModelsOptions)
const [customHeaders, setCustomHeaders] = useState<[string, string][]>(() => {
const headers = apiConfiguration?.openAiHeaders || {}
@ -131,6 +144,7 @@ export const OpenAICompatible = ({
serviceName="OpenAI"
serviceUrl="https://platform.openai.com"
organizationAllowList={organizationAllowList}
onOpenRefetch={refetchOpenAiCompatibleModels}
/>
<R1FormatSetting
onChange={handleInputChange("openAiR1FormatEnabled", noTransform)}

View file

@ -1,4 +1,4 @@
import { useCallback } from "react"
import { useCallback, useMemo } from "react"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import type { ProviderSettings, OrganizationAllowList } from "@roo-code/types"
@ -23,7 +23,20 @@ type RequestyProps = {
export const Requesty = ({ apiConfiguration, setApiConfigurationField, organizationAllowList }: RequestyProps) => {
const { t } = useAppTranslation()
const { models: requestyModelsData, isLoading: isLoadingModels, error: modelsError } = useProviderModels("requesty")
const providerModelsOptions = useMemo(
() => ({
flushCacheFirst: true,
requestyApiKey: apiConfiguration?.requestyApiKey,
}),
[apiConfiguration?.requestyApiKey],
)
const {
models: requestyModelsData,
isLoading: isLoadingModels,
error: modelsError,
refetch: refetchRequestyModels,
} = useProviderModels("requesty", providerModelsOptions)
const handleInputChange = useCallback(
<K extends keyof ProviderSettings, E>(
@ -83,6 +96,7 @@ export const Requesty = ({ apiConfiguration, setApiConfigurationField, organizat
serviceName="Requesty"
serviceUrl="https://requesty.ai"
organizationAllowList={organizationAllowList}
onOpenRefetch={refetchRequestyModels}
/>
</>
)

View file

@ -1,4 +1,4 @@
import { useCallback, useState, useEffect, useRef } from "react"
import { useCallback, useState, useEffect, useRef, useMemo } from "react"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import type { ProviderSettings, OrganizationAllowList } from "@roo-code/types"
@ -20,7 +20,20 @@ type UnboundProps = {
export const Unbound = ({ apiConfiguration, setApiConfigurationField, organizationAllowList }: UnboundProps) => {
const { t } = useAppTranslation()
const { models: unboundModelsData, isLoading: isLoadingModels, error: modelsError } = useProviderModels("unbound")
const providerModelsOptions = useMemo(
() => ({
flushCacheFirst: true,
unboundApiKey: apiConfiguration?.unboundApiKey,
}),
[apiConfiguration?.unboundApiKey],
)
const {
models: unboundModelsData,
isLoading: isLoadingModels,
error: modelsError,
refetch: refetchUnboundModels,
} = useProviderModels("unbound", providerModelsOptions)
const [isInvalidKeyFeedback, setIsInvalidKeyFeedback] = useState<boolean>(false)
const invalidKeyTimerRef = useRef<NodeJS.Timeout>()
@ -101,6 +114,7 @@ export const Unbound = ({ apiConfiguration, setApiConfigurationField, organizati
serviceUrl="https://api.getunbound.ai/models"
setApiConfigurationField={setApiConfigurationField}
organizationAllowList={organizationAllowList}
onOpenRefetch={refetchUnboundModels}
/>
</>
)

View file

@ -5,19 +5,20 @@ import { RouterName, ModelRecord } from "@roo/api"
import { ExtensionMessage } from "@roo/ExtensionMessage"
import { vscode } from "@src/utils/vscode"
import { useDebounceEffect } from "@src/utils/useDebounceEffect"
import { useExtensionState } from "@src/context/ExtensionStateContext"
// --- START: Type definitions for provider-specific params ---
// Inspired by GetModelsOptions from src/shared/api.ts
// These are the *additional* params a provider might need, sent from the UI.
export type ProviderSpecificParamsMap = {
openrouter: Record<string, never>
glama: Record<string, never>
openrouter: object
glama: object
requesty: { requestyApiKey?: string }
unbound: { unboundApiKey?: string }
litellm: { litellmApiKey?: string; litellmBaseUrl?: string }
ollama: { baseUrl?: string }
lmstudio: { baseUrl?: string }
vscodelm: Record<string, never>
vscodelm: object
"openai-compatible": {
baseUrl: string
apiKey?: string
@ -103,6 +104,7 @@ export const useProviderModels = <P extends RouterName>(
options?: UseProviderModelsOptions<P>,
): UseProviderModelsResult => {
const queryClient = useQueryClient()
const { setAreProviderModelsLoading } = useExtensionState()
// Track if we're currently debouncing
const debouncingRef = useRef(false)
@ -171,6 +173,13 @@ export const useProviderModels = <P extends RouterName>(
// Clear error when in loading state
const error = isLoading ? undefined : queryError?.message
// Update global loading state
useEffect(() => {
if (setAreProviderModelsLoading) {
setAreProviderModelsLoading(isLoading)
}
}, [isLoading, setAreProviderModelsLoading])
return {
models: data,
isLoading,

View file

@ -114,6 +114,8 @@ export interface ExtensionStateContextType extends ExtensionState {
setAutoCondenseContext: (value: boolean) => void
autoCondenseContextPercent: number
setAutoCondenseContextPercent: (value: number) => void
areProviderModelsLoading?: boolean
setAreProviderModelsLoading?: (isLoading: boolean) => void
}
export const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
@ -146,7 +148,9 @@ export const mergeExtensionState = (prevState: ExtensionState, newState: Extensi
}
export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [state, setState] = useState<ExtensionState & { organizationAllowList?: OrganizationAllowList }>({
const [state, setState] = useState<
ExtensionState & { organizationAllowList?: OrganizationAllowList; areProviderModelsLoading?: boolean }
>({
version: "",
clineMessages: [],
taskHistory: [],
@ -206,6 +210,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
codebaseIndexEmbedderModelId: "",
},
codebaseIndexModels: { ollama: {}, openai: {} },
areProviderModelsLoading: false,
})
const [didHydrateState, setDidHydrateState] = useState(false)
@ -391,6 +396,9 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
setCondensingApiConfigId: (value) => setState((prevState) => ({ ...prevState, condensingApiConfigId: value })),
setCustomCondensingPrompt: (value) =>
setState((prevState) => ({ ...prevState, customCondensingPrompt: value })),
areProviderModelsLoading: state.areProviderModelsLoading,
setAreProviderModelsLoading: (isLoading: boolean) =>
setState((prevState) => ({ ...prevState, areProviderModelsLoading: isLoading })),
}
return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>