fix: change provider not update without done, update chatbox change provider from MrUbens

This commit is contained in:
sam hoang
2025-01-07 20:16:44 +07:00
committed by Matt Rubens
parent c3fa10b367
commit 3346844584
7 changed files with 121 additions and 33 deletions

View File

@@ -125,6 +125,18 @@ export class ConfigManager {
} }
} }
/**
* Check if a config exists by name
*/
async HasConfig(name: string): Promise<boolean> {
try {
const config = await this.readConfig()
return name in config.apiConfigs
} catch (error) {
throw new Error(`Failed to check config existence: ${error}`)
}
}
private async readConfig(): Promise<ApiConfigData> { private async readConfig(): Promise<ApiConfigData> {
try { try {
const configKey = `${this.SCOPE_PREFIX}api_config` const configKey = `${this.SCOPE_PREFIX}api_config`

View File

@@ -1,7 +1,6 @@
import { ExtensionContext } from 'vscode' import { ExtensionContext } from 'vscode'
import { ConfigManager } from '../ConfigManager' import { ConfigManager, ApiConfigData } from '../ConfigManager'
import { ApiConfiguration } from '../../../shared/api' import { ApiConfiguration } from '../../../shared/api'
import { ApiConfigData } from '../ConfigManager'
// Mock VSCode ExtensionContext // Mock VSCode ExtensionContext
const mockSecrets = { const mockSecrets = {
@@ -345,4 +344,41 @@ describe('ConfigManager', () => {
) )
}) })
}) })
describe('HasConfig', () => {
it('should return true for existing config', async () => {
const existingConfig: ApiConfigData = {
currentApiConfigName: 'default',
apiConfigs: {
default: {},
test: {
apiProvider: 'anthropic'
}
}
}
mockSecrets.get.mockResolvedValue(JSON.stringify(existingConfig))
const hasConfig = await configManager.HasConfig('test')
expect(hasConfig).toBe(true)
})
it('should return false for non-existent config', async () => {
mockSecrets.get.mockResolvedValue(JSON.stringify({
currentApiConfigName: 'default',
apiConfigs: { default: {} }
}))
const hasConfig = await configManager.HasConfig('nonexistent')
expect(hasConfig).toBe(false)
})
it('should throw error if secrets storage fails', async () => {
mockSecrets.get.mockRejectedValue(new Error('Storage failed'))
await expect(configManager.HasConfig('test')).rejects.toThrow(
'Failed to check config existence: Error: Failed to read config from secrets: Error: Storage failed'
)
})
})
}) })

View File

@@ -45,7 +45,6 @@ type SecretKey =
| "geminiApiKey" | "geminiApiKey"
| "openAiNativeApiKey" | "openAiNativeApiKey"
| "deepSeekApiKey" | "deepSeekApiKey"
| "apiConfigPassword"
type GlobalStateKey = type GlobalStateKey =
| "apiProvider" | "apiProvider"
| "apiModelId" | "apiModelId"
@@ -428,15 +427,37 @@ export class ClineProvider implements vscode.WebviewViewProvider {
if (listApiConfig.length === 1) { if (listApiConfig.length === 1) {
// check if first time init then sync with exist config // check if first time init then sync with exist config
if (!checkExistKey(listApiConfig[0]) && listApiConfig[0].name === "default") { if (!checkExistKey(listApiConfig[0])) {
const { const {
apiConfiguration, apiConfiguration,
} = await this.getState() } = await this.getState()
await this.configManager.SaveConfig("default", apiConfiguration) await this.configManager.SaveConfig(listApiConfig[0].name ?? "default", apiConfiguration)
listApiConfig[0].apiProvider = apiConfiguration.apiProvider listApiConfig[0].apiProvider = apiConfiguration.apiProvider
} }
} }
let currentConfigName = await this.getGlobalState("currentApiConfigName") as string
if (currentConfigName) {
if (!await this.configManager.HasConfig(currentConfigName)) {
// current config name not valid, get first config in list
await this.updateGlobalState("currentApiConfigName", listApiConfig?.[0]?.name)
if (listApiConfig?.[0]?.name) {
const apiConfig = await this.configManager.LoadConfig(listApiConfig?.[0]?.name);
await Promise.all([
this.updateGlobalState("listApiConfigMeta", listApiConfig),
this.postMessageToWebview({ type: "listApiConfig", listApiConfig }),
this.updateApiConfiguration(apiConfig),
])
await this.postStateToWebview()
return
}
}
}
await Promise.all( await Promise.all(
[ [
await this.updateGlobalState("listApiConfigMeta", listApiConfig), await this.updateGlobalState("listApiConfigMeta", listApiConfig),
@@ -785,6 +806,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
let listApiConfig = await this.configManager.ListConfig(); let listApiConfig = await this.configManager.ListConfig();
await Promise.all([ await Promise.all([
this.updateApiConfiguration(message.apiConfiguration),
this.updateGlobalState("currentApiConfigName", message.text), this.updateGlobalState("currentApiConfigName", message.text),
this.updateGlobalState("listApiConfigMeta", listApiConfig), this.updateGlobalState("listApiConfigMeta", listApiConfig),
]) ])
@@ -800,7 +822,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
if (message.values && message.apiConfiguration) { if (message.values && message.apiConfiguration) {
try { try {
const {oldName, newName} = message.values const { oldName, newName } = message.values
await this.configManager.SaveConfig(newName, message.apiConfiguration); await this.configManager.SaveConfig(newName, message.apiConfiguration);
@@ -839,17 +861,37 @@ export class ClineProvider implements vscode.WebviewViewProvider {
break break
case "deleteApiConfiguration": case "deleteApiConfiguration":
if (message.text) { if (message.text) {
try {
await this.configManager.DeleteConfig(message.text);
let currentApiConfigName = (await this.getGlobalState("currentApiConfigName") as string) ?? "default"
if (message.text === currentApiConfigName) { const answer = await vscode.window.showInformationMessage(
await this.updateGlobalState("currentApiConfigName", "default") "What would you like to delete this api config?",
{ modal: true },
"Yes",
"No",
)
if (answer === "No" || answer === undefined) {
break
} }
let listApiConfig = await this.configManager.ListConfig(); try {
await this.updateGlobalState("listApiConfigMeta", listApiConfig) await this.configManager.DeleteConfig(message.text);
this.postMessageToWebview({ type: "listApiConfig", listApiConfig }) let listApiConfig = await this.configManager.ListConfig()
let currentApiConfigName = await this.getGlobalState("currentApiConfigName")
if (message.text === currentApiConfigName) {
await this.updateGlobalState("currentApiConfigName", listApiConfig?.[0]?.name)
if (listApiConfig?.[0]?.name) {
const apiConfig = await this.configManager.LoadConfig(listApiConfig?.[0]?.name);
await Promise.all([
this.updateGlobalState("listApiConfigMeta", listApiConfig),
this.updateApiConfiguration(apiConfig),
])
await this.postStateToWebview()
}
}
// this.postMessageToWebview({ type: "listApiConfig", listApiConfig })
} catch (error) { } catch (error) {
console.error("Error delete api configuration:", error) console.error("Error delete api configuration:", error)
@@ -867,16 +909,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
vscode.window.showErrorMessage("Failed to get list api configuration") vscode.window.showErrorMessage("Failed to get list api configuration")
} }
break break
case "setApiConfigPassword":
if (message.text) {
try {
await this.storeSecret("apiConfigPassword", message.text !== "" ? message.text : undefined)
} catch (error) {
console.error("Error set apiKey password:", error)
vscode.window.showErrorMessage("Failed to set apiKey password")
}
}
break
} }
}, },
null, null,
@@ -1398,7 +1430,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
requestDelaySeconds, requestDelaySeconds,
currentApiConfigName, currentApiConfigName,
listApiConfigMeta, listApiConfigMeta,
apiKeyPassword
} = await this.getState() } = await this.getState()
const allowedCommands = vscode.workspace const allowedCommands = vscode.workspace
@@ -1435,7 +1466,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
requestDelaySeconds: requestDelaySeconds ?? 5, requestDelaySeconds: requestDelaySeconds ?? 5,
currentApiConfigName: currentApiConfigName ?? "default", currentApiConfigName: currentApiConfigName ?? "default",
listApiConfigMeta: listApiConfigMeta ?? [], listApiConfigMeta: listApiConfigMeta ?? [],
apiKeyPassword: apiKeyPassword ?? ""
} }
} }
@@ -1545,7 +1575,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
requestDelaySeconds, requestDelaySeconds,
currentApiConfigName, currentApiConfigName,
listApiConfigMeta, listApiConfigMeta,
apiKeyPassword,
] = await Promise.all([ ] = await Promise.all([
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>, this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
this.getGlobalState("apiModelId") as Promise<string | undefined>, this.getGlobalState("apiModelId") as Promise<string | undefined>,
@@ -1600,7 +1629,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
this.getGlobalState("requestDelaySeconds") as Promise<number | undefined>, this.getGlobalState("requestDelaySeconds") as Promise<number | undefined>,
this.getGlobalState("currentApiConfigName") as Promise<string | undefined>, this.getGlobalState("currentApiConfigName") as Promise<string | undefined>,
this.getGlobalState("listApiConfigMeta") as Promise<ApiConfigMeta[] | undefined>, this.getGlobalState("listApiConfigMeta") as Promise<ApiConfigMeta[] | undefined>,
this.getSecret("apiConfigPassword") as Promise<string | undefined>,
]) ])
let apiProvider: ApiProvider let apiProvider: ApiProvider
@@ -1699,7 +1727,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
requestDelaySeconds: requestDelaySeconds ?? 5, requestDelaySeconds: requestDelaySeconds ?? 5,
currentApiConfigName: currentApiConfigName ?? "default", currentApiConfigName: currentApiConfigName ?? "default",
listApiConfigMeta: listApiConfigMeta ?? [], listApiConfigMeta: listApiConfigMeta ?? [],
apiKeyPassword: apiKeyPassword ?? ""
} }
} }
@@ -1777,7 +1804,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
"geminiApiKey", "geminiApiKey",
"openAiNativeApiKey", "openAiNativeApiKey",
"deepSeekApiKey", "deepSeekApiKey",
"apiConfigPassword"
] ]
for (const key of secretKeys) { for (const key of secretKeys) {
await this.storeSecret(key, undefined) await this.storeSecret(key, undefined)

View File

@@ -12,8 +12,8 @@ interface ApiConfigManagerProps {
} }
const ApiConfigManager = ({ const ApiConfigManager = ({
currentApiConfigName, currentApiConfigName = "",
listApiConfigMeta, listApiConfigMeta = [],
onSelectConfig, onSelectConfig,
onDeleteConfig, onDeleteConfig,
onRenameConfig, onRenameConfig,

View File

@@ -46,9 +46,10 @@ interface ApiOptionsProps {
showModelOptions: boolean showModelOptions: boolean
apiErrorMessage?: string apiErrorMessage?: string
modelIdErrorMessage?: string modelIdErrorMessage?: string
onSelectProvider: (apiProvider: any) => void
} }
const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) => { const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, onSelectProvider }: ApiOptionsProps) => {
const { apiConfiguration, setApiConfiguration, uriScheme } = useExtensionState() const { apiConfiguration, setApiConfiguration, uriScheme } = useExtensionState()
const [ollamaModels, setOllamaModels] = useState<string[]>([]) const [ollamaModels, setOllamaModels] = useState<string[]>([])
const [lmStudioModels, setLmStudioModels] = useState<string[]>([]) const [lmStudioModels, setLmStudioModels] = useState<string[]>([])
@@ -130,7 +131,10 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage }:
<VSCodeDropdown <VSCodeDropdown
id="api-provider" id="api-provider"
value={selectedProvider} value={selectedProvider}
onChange={handleInputChange("apiProvider")} onChange={(event: any) => {
onSelectProvider(event.target.value);
handleInputChange("apiProvider")(event);
}}
style={{ minWidth: 130, position: "relative", zIndex: OPENROUTER_MODEL_PICKER_Z_INDEX + 1 }}> style={{ minWidth: 130, position: "relative", zIndex: OPENROUTER_MODEL_PICKER_Z_INDEX + 1 }}>
<VSCodeOption value="openrouter">OpenRouter</VSCodeOption> <VSCodeOption value="openrouter">OpenRouter</VSCodeOption>
<VSCodeOption value="anthropic">Anthropic</VSCodeOption> <VSCodeOption value="anthropic">Anthropic</VSCodeOption>

View File

@@ -183,7 +183,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
onRenameConfig={(oldName: string, newName: string) => { onRenameConfig={(oldName: string, newName: string) => {
vscode.postMessage({ vscode.postMessage({
type: "renameApiConfiguration", type: "renameApiConfiguration",
values: {oldName, newName}, values: { oldName, newName },
apiConfiguration apiConfiguration
}) })
}} }}
@@ -199,6 +199,16 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
showModelOptions={true} showModelOptions={true}
apiErrorMessage={apiErrorMessage} apiErrorMessage={apiErrorMessage}
modelIdErrorMessage={modelIdErrorMessage} modelIdErrorMessage={modelIdErrorMessage}
onSelectProvider={(apiProvider: any) => {
vscode.postMessage({
type: "upsertApiConfiguration",
text: currentApiConfigName,
apiConfiguration: {
...apiConfiguration,
apiProvider: apiProvider,
}
})
}}
/> />
</div> </div>

View File

@@ -38,7 +38,7 @@ const WelcomeView = () => {
<b>To get started, this extension needs an API provider for Claude 3.5 Sonnet.</b> <b>To get started, this extension needs an API provider for Claude 3.5 Sonnet.</b>
<div style={{ marginTop: "10px" }}> <div style={{ marginTop: "10px" }}>
<ApiOptions showModelOptions={false} /> <ApiOptions showModelOptions={false} onSelectProvider={() => {}} />
<VSCodeButton onClick={handleSubmit} disabled={disableLetsGoButton} style={{ marginTop: "3px" }}> <VSCodeButton onClick={handleSubmit} disabled={disableLetsGoButton} style={{ marginTop: "3px" }}>
Let's go! Let's go!
</VSCodeButton> </VSCodeButton>