Browse Source

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

sam hoang 1 year ago
parent
commit
3346844584

+ 12 - 0
src/core/config/ConfigManager.ts

@@ -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`

+ 38 - 2
src/core/config/__tests__/ConfigManager.test.ts

@@ -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'
+      )
+    })
+  })
 })
 })

+ 51 - 25
src/core/webview/ClineProvider.ts

@@ -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) {
+
+							const answer = await vscode.window.showInformationMessage(
+								"What would you like to delete this api config?",
+								{ modal: true },
+								"Yes",
+								"No",
+							)
+
+							if (answer === "No" || answer === undefined) {
+								break
+							}
+
 							try {
 							try {
 								await this.configManager.DeleteConfig(message.text);
 								await this.configManager.DeleteConfig(message.text);
-								let currentApiConfigName = (await this.getGlobalState("currentApiConfigName") as string) ?? "default"
+								let listApiConfig = await this.configManager.ListConfig()
+								let currentApiConfigName = await this.getGlobalState("currentApiConfigName")
 
 
 								if (message.text === currentApiConfigName) {
 								if (message.text === currentApiConfigName) {
-									await this.updateGlobalState("currentApiConfigName", "default")
+									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()
+									}
 								}
 								}
 
 
-								let listApiConfig = await this.configManager.ListConfig();
-								await this.updateGlobalState("listApiConfigMeta", listApiConfig)
-								this.postMessageToWebview({ type: "listApiConfig", listApiConfig })
+								// 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)

+ 2 - 2
webview-ui/src/components/settings/ApiConfigManager.tsx

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

+ 6 - 2
webview-ui/src/components/settings/ApiOptions.tsx

@@ -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>

+ 11 - 1
webview-ui/src/components/settings/SettingsView.tsx

@@ -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>
 
 

+ 1 - 1
webview-ui/src/components/welcome/WelcomeView.tsx

@@ -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>