From 993ebaf99926176d76414f594f2ece51859981eb Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Mon, 20 Jan 2025 02:51:03 -0500 Subject: [PATCH] Clean up the settings page --- src/core/config/ConfigManager.ts | 36 +- .../config/__tests__/ConfigManager.test.ts | 58 +- src/core/webview/ClineProvider.ts | 57 +- .../webview/__tests__/ClineProvider.test.ts | 36 +- .../src/components/prompts/PromptsView.tsx | 51 ++ .../src/components/settings/SettingsView.tsx | 666 ++++++++---------- 6 files changed, 448 insertions(+), 456 deletions(-) diff --git a/src/core/config/ConfigManager.ts b/src/core/config/ConfigManager.ts index ef3d4d3..0299955 100644 --- a/src/core/config/ConfigManager.ts +++ b/src/core/config/ConfigManager.ts @@ -64,7 +64,7 @@ export class ConfigManager { /** * List all available configs with metadata */ - async ListConfig(): Promise { + async listConfig(): Promise { try { const config = await this.readConfig() return Object.entries(config.apiConfigs).map(([name, apiConfig]) => ({ @@ -80,7 +80,7 @@ export class ConfigManager { /** * Save a config with the given name */ - async SaveConfig(name: string, config: ApiConfiguration): Promise { + async saveConfig(name: string, config: ApiConfiguration): Promise { try { const currentConfig = await this.readConfig() const existingConfig = currentConfig.apiConfigs[name] @@ -97,7 +97,7 @@ export class ConfigManager { /** * Load a config by name */ - async LoadConfig(name: string): Promise { + async loadConfig(name: string): Promise { try { const config = await this.readConfig() const apiConfig = config.apiConfigs[name] @@ -118,7 +118,7 @@ export class ConfigManager { /** * Delete a config by name */ - async DeleteConfig(name: string): Promise { + async deleteConfig(name: string): Promise { try { const currentConfig = await this.readConfig() if (!currentConfig.apiConfigs[name]) { @@ -140,7 +140,7 @@ export class ConfigManager { /** * Set the current active API configuration */ - async SetCurrentConfig(name: string): Promise { + async setCurrentConfig(name: string): Promise { try { const currentConfig = await this.readConfig() if (!currentConfig.apiConfigs[name]) { @@ -157,7 +157,7 @@ export class ConfigManager { /** * Check if a config exists by name */ - async HasConfig(name: string): Promise { + async hasConfig(name: string): Promise { try { const config = await this.readConfig() return name in config.apiConfigs @@ -169,7 +169,7 @@ export class ConfigManager { /** * Set the API config for a specific mode */ - async SetModeConfig(mode: Mode, configId: string): Promise { + async setModeConfig(mode: Mode, configId: string): Promise { try { const currentConfig = await this.readConfig() if (!currentConfig.modeApiConfigs) { @@ -185,7 +185,7 @@ export class ConfigManager { /** * Get the API config ID for a specific mode */ - async GetModeConfigId(mode: Mode): Promise { + async getModeConfigId(mode: Mode): Promise { try { const config = await this.readConfig() return config.modeApiConfigs?.[mode] @@ -194,10 +194,23 @@ export class ConfigManager { } } + /** + * Get the key used for storing config in secrets + */ + private getConfigKey(): string { + return `${this.SCOPE_PREFIX}api_config` + } + + /** + * Reset all configuration by deleting the stored config from secrets + */ + public async resetAllConfigs(): Promise { + await this.context.secrets.delete(this.getConfigKey()) + } + private async readConfig(): Promise { try { - const configKey = `${this.SCOPE_PREFIX}api_config` - const content = await this.context.secrets.get(configKey) + const content = await this.context.secrets.get(this.getConfigKey()) if (!content) { return this.defaultConfig @@ -211,9 +224,8 @@ export class ConfigManager { private async writeConfig(config: ApiConfigData): Promise { try { - const configKey = `${this.SCOPE_PREFIX}api_config` const content = JSON.stringify(config, null, 2) - await this.context.secrets.store(configKey, content) + await this.context.secrets.store(this.getConfigKey(), content) } catch (error) { throw new Error(`Failed to write config to secrets: ${error}`) } diff --git a/src/core/config/__tests__/ConfigManager.test.ts b/src/core/config/__tests__/ConfigManager.test.ts index 59f36e6..3d65021 100644 --- a/src/core/config/__tests__/ConfigManager.test.ts +++ b/src/core/config/__tests__/ConfigManager.test.ts @@ -106,7 +106,7 @@ describe("ConfigManager", () => { mockSecrets.get.mockResolvedValue(JSON.stringify(existingConfig)) - const configs = await configManager.ListConfig() + const configs = await configManager.listConfig() expect(configs).toEqual([ { name: "default", id: "default", apiProvider: undefined }, { name: "test", id: "test-id", apiProvider: "anthropic" }, @@ -126,14 +126,14 @@ describe("ConfigManager", () => { mockSecrets.get.mockResolvedValue(JSON.stringify(emptyConfig)) - const configs = await configManager.ListConfig() + const configs = await configManager.listConfig() expect(configs).toEqual([]) }) it("should throw error if reading from secrets fails", async () => { mockSecrets.get.mockRejectedValue(new Error("Read failed")) - await expect(configManager.ListConfig()).rejects.toThrow( + await expect(configManager.listConfig()).rejects.toThrow( "Failed to list configs: Error: Failed to read config from secrets: Error: Read failed", ) }) @@ -160,7 +160,7 @@ describe("ConfigManager", () => { apiKey: "test-key", } - await configManager.SaveConfig("test", newConfig) + await configManager.saveConfig("test", newConfig) // Get the actual stored config to check the generated ID const storedConfig = JSON.parse(mockSecrets.store.mock.calls[0][1]) @@ -207,7 +207,7 @@ describe("ConfigManager", () => { apiKey: "new-key", } - await configManager.SaveConfig("test", updatedConfig) + await configManager.saveConfig("test", updatedConfig) const expectedConfig = { currentApiConfigName: "default", @@ -235,7 +235,7 @@ describe("ConfigManager", () => { ) mockSecrets.store.mockRejectedValueOnce(new Error("Storage failed")) - await expect(configManager.SaveConfig("test", {})).rejects.toThrow( + await expect(configManager.saveConfig("test", {})).rejects.toThrow( "Failed to save config: Error: Failed to write config to secrets: Error: Storage failed", ) }) @@ -258,7 +258,7 @@ describe("ConfigManager", () => { mockSecrets.get.mockResolvedValue(JSON.stringify(existingConfig)) - await configManager.DeleteConfig("test") + await configManager.deleteConfig("test") // Get the stored config to check the ID const storedConfig = JSON.parse(mockSecrets.store.mock.calls[0][1]) @@ -275,7 +275,7 @@ describe("ConfigManager", () => { }), ) - await expect(configManager.DeleteConfig("nonexistent")).rejects.toThrow("Config 'nonexistent' not found") + await expect(configManager.deleteConfig("nonexistent")).rejects.toThrow("Config 'nonexistent' not found") }) it("should throw error when trying to delete last remaining config", async () => { @@ -290,7 +290,7 @@ describe("ConfigManager", () => { }), ) - await expect(configManager.DeleteConfig("default")).rejects.toThrow( + await expect(configManager.deleteConfig("default")).rejects.toThrow( "Cannot delete the last remaining configuration.", ) }) @@ -311,7 +311,7 @@ describe("ConfigManager", () => { mockSecrets.get.mockResolvedValue(JSON.stringify(existingConfig)) - const config = await configManager.LoadConfig("test") + const config = await configManager.loadConfig("test") expect(config).toEqual({ apiProvider: "anthropic", @@ -342,7 +342,7 @@ describe("ConfigManager", () => { }), ) - await expect(configManager.LoadConfig("nonexistent")).rejects.toThrow("Config 'nonexistent' not found") + await expect(configManager.loadConfig("nonexistent")).rejects.toThrow("Config 'nonexistent' not found") }) it("should throw error if secrets storage fails", async () => { @@ -361,7 +361,7 @@ describe("ConfigManager", () => { ) mockSecrets.store.mockRejectedValueOnce(new Error("Storage failed")) - await expect(configManager.LoadConfig("test")).rejects.toThrow( + await expect(configManager.loadConfig("test")).rejects.toThrow( "Failed to load config: Error: Failed to write config to secrets: Error: Storage failed", ) }) @@ -384,7 +384,7 @@ describe("ConfigManager", () => { mockSecrets.get.mockResolvedValue(JSON.stringify(existingConfig)) - await configManager.SetCurrentConfig("test") + await configManager.setCurrentConfig("test") // Get the stored config to check the structure const storedConfig = JSON.parse(mockSecrets.store.mock.calls[0][1]) @@ -404,7 +404,7 @@ describe("ConfigManager", () => { }), ) - await expect(configManager.SetCurrentConfig("nonexistent")).rejects.toThrow( + await expect(configManager.setCurrentConfig("nonexistent")).rejects.toThrow( "Config 'nonexistent' not found", ) }) @@ -420,12 +420,34 @@ describe("ConfigManager", () => { ) mockSecrets.store.mockRejectedValueOnce(new Error("Storage failed")) - await expect(configManager.SetCurrentConfig("test")).rejects.toThrow( + await expect(configManager.setCurrentConfig("test")).rejects.toThrow( "Failed to set current config: Error: Failed to write config to secrets: Error: Storage failed", ) }) }) + describe("ResetAllConfigs", () => { + it("should delete all stored configs", async () => { + // Setup initial config + mockSecrets.get.mockResolvedValue( + JSON.stringify({ + currentApiConfigName: "test", + apiConfigs: { + test: { + apiProvider: "anthropic", + id: "test-id", + }, + }, + }), + ) + + await configManager.resetAllConfigs() + + // Should have called delete with the correct config key + expect(mockSecrets.delete).toHaveBeenCalledWith("roo_cline_config_api_config") + }) + }) + describe("HasConfig", () => { it("should return true for existing config", async () => { const existingConfig: ApiConfigData = { @@ -443,7 +465,7 @@ describe("ConfigManager", () => { mockSecrets.get.mockResolvedValue(JSON.stringify(existingConfig)) - const hasConfig = await configManager.HasConfig("test") + const hasConfig = await configManager.hasConfig("test") expect(hasConfig).toBe(true) }) @@ -455,14 +477,14 @@ describe("ConfigManager", () => { }), ) - const hasConfig = await configManager.HasConfig("nonexistent") + 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( + await expect(configManager.hasConfig("test")).rejects.toThrow( "Failed to check config existence: Error: Failed to read config from secrets: Error: Storage failed", ) }) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 0a775e2..04c24a1 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -446,7 +446,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { }) this.configManager - .ListConfig() + .listConfig() .then(async (listApiConfig) => { if (!listApiConfig) { return @@ -456,7 +456,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { // check if first time init then sync with exist config if (!checkExistKey(listApiConfig[0])) { const { apiConfiguration } = await this.getState() - await this.configManager.SaveConfig( + await this.configManager.saveConfig( listApiConfig[0].name ?? "default", apiConfiguration, ) @@ -467,11 +467,11 @@ export class ClineProvider implements vscode.WebviewViewProvider { let currentConfigName = (await this.getGlobalState("currentApiConfigName")) as string if (currentConfigName) { - if (!(await this.configManager.HasConfig(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( + const apiConfig = await this.configManager.loadConfig( listApiConfig?.[0]?.name, ) @@ -726,8 +726,8 @@ export class ClineProvider implements vscode.WebviewViewProvider { await this.updateGlobalState("mode", newMode) // Load the saved API config for the new mode if it exists - const savedConfigId = await this.configManager.GetModeConfigId(newMode) - const listApiConfig = await this.configManager.ListConfig() + const savedConfigId = await this.configManager.getModeConfigId(newMode) + const listApiConfig = await this.configManager.listConfig() // Update listApiConfigMeta first to ensure UI has latest data await this.updateGlobalState("listApiConfigMeta", listApiConfig) @@ -736,7 +736,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { if (savedConfigId) { const config = listApiConfig?.find((c) => c.id === savedConfigId) if (config?.name) { - const apiConfig = await this.configManager.LoadConfig(config.name) + const apiConfig = await this.configManager.loadConfig(config.name) await Promise.all([ this.updateGlobalState("currentApiConfigName", config.name), this.updateApiConfiguration(apiConfig), @@ -748,7 +748,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { if (currentApiConfigName) { const config = listApiConfig?.find((c) => c.name === currentApiConfigName) if (config?.id) { - await this.configManager.SetModeConfig(newMode, config.id) + await this.configManager.setModeConfig(newMode, config.id) } } } @@ -913,7 +913,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { if (enhancementApiConfigId) { const config = listApiConfigMeta?.find((c) => c.id === enhancementApiConfigId) if (config?.name) { - const loadedConfig = await this.configManager.LoadConfig(config.name) + const loadedConfig = await this.configManager.loadConfig(config.name) if (loadedConfig.apiProvider) { configToUse = loadedConfig } @@ -1004,8 +1004,8 @@ export class ClineProvider implements vscode.WebviewViewProvider { case "upsertApiConfiguration": if (message.text && message.apiConfiguration) { try { - await this.configManager.SaveConfig(message.text, message.apiConfiguration) - let listApiConfig = await this.configManager.ListConfig() + await this.configManager.saveConfig(message.text, message.apiConfiguration) + let listApiConfig = await this.configManager.listConfig() await Promise.all([ this.updateGlobalState("listApiConfigMeta", listApiConfig), @@ -1025,10 +1025,10 @@ export class ClineProvider implements vscode.WebviewViewProvider { try { const { oldName, newName } = message.values - await this.configManager.SaveConfig(newName, message.apiConfiguration) - await this.configManager.DeleteConfig(oldName) + await this.configManager.saveConfig(newName, message.apiConfiguration) + await this.configManager.deleteConfig(oldName) - let listApiConfig = await this.configManager.ListConfig() + let listApiConfig = await this.configManager.listConfig() const config = listApiConfig?.find((c) => c.name === newName) // Update listApiConfigMeta first to ensure UI has latest data @@ -1046,8 +1046,8 @@ export class ClineProvider implements vscode.WebviewViewProvider { case "loadApiConfiguration": if (message.text) { try { - const apiConfig = await this.configManager.LoadConfig(message.text) - const listApiConfig = await this.configManager.ListConfig() + const apiConfig = await this.configManager.loadConfig(message.text) + const listApiConfig = await this.configManager.listConfig() await Promise.all([ this.updateGlobalState("listApiConfigMeta", listApiConfig), @@ -1075,8 +1075,8 @@ export class ClineProvider implements vscode.WebviewViewProvider { } try { - await this.configManager.DeleteConfig(message.text) - const listApiConfig = await this.configManager.ListConfig() + await this.configManager.deleteConfig(message.text) + const listApiConfig = await this.configManager.listConfig() // Update listApiConfigMeta first to ensure UI has latest data await this.updateGlobalState("listApiConfigMeta", listApiConfig) @@ -1084,7 +1084,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { // If this was the current config, switch to first available let currentApiConfigName = await this.getGlobalState("currentApiConfigName") if (message.text === currentApiConfigName && listApiConfig?.[0]?.name) { - const apiConfig = await this.configManager.LoadConfig(listApiConfig[0].name) + const apiConfig = await this.configManager.loadConfig(listApiConfig[0].name) await Promise.all([ this.updateGlobalState("currentApiConfigName", listApiConfig[0].name), this.updateApiConfiguration(apiConfig), @@ -1100,7 +1100,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { break case "getListApiConfiguration": try { - let listApiConfig = await this.configManager.ListConfig() + let listApiConfig = await this.configManager.listConfig() await this.updateGlobalState("listApiConfigMeta", listApiConfig) this.postMessageToWebview({ type: "listApiConfig", listApiConfig }) } catch (error) { @@ -1127,10 +1127,10 @@ export class ClineProvider implements vscode.WebviewViewProvider { const { mode } = await this.getState() if (mode) { const currentApiConfigName = await this.getGlobalState("currentApiConfigName") - const listApiConfig = await this.configManager.ListConfig() + const listApiConfig = await this.configManager.listConfig() const config = listApiConfig?.find((c) => c.name === currentApiConfigName) if (config?.id) { - await this.configManager.SetModeConfig(mode, config.id) + await this.configManager.setModeConfig(mode, config.id) } } @@ -2077,7 +2077,16 @@ export class ClineProvider implements vscode.WebviewViewProvider { // dev async resetState() { - vscode.window.showInformationMessage("Resetting state...") + const answer = await vscode.window.showInformationMessage( + "Are you sure you want to reset all state and secret storage in the extension? This cannot be undone.", + { modal: true }, + "Yes", + ) + + if (answer !== "Yes") { + return + } + for (const key of this.context.globalState.keys()) { await this.context.globalState.update(key, undefined) } @@ -2097,11 +2106,11 @@ export class ClineProvider implements vscode.WebviewViewProvider { for (const key of secretKeys) { await this.storeSecret(key, undefined) } + await this.configManager.resetAllConfigs() if (this.cline) { this.cline.abortTask() this.cline = undefined } - vscode.window.showInformationMessage("State reset") await this.postStateToWebview() await this.postMessageToWebview({ type: "action", action: "chatButtonClicked" }) } diff --git a/src/core/webview/__tests__/ClineProvider.test.ts b/src/core/webview/__tests__/ClineProvider.test.ts index 52fb611..ed194dd 100644 --- a/src/core/webview/__tests__/ClineProvider.test.ts +++ b/src/core/webview/__tests__/ClineProvider.test.ts @@ -443,18 +443,18 @@ describe("ClineProvider", () => { // Mock ConfigManager methods provider.configManager = { - GetModeConfigId: jest.fn().mockResolvedValue("test-id"), - ListConfig: jest.fn().mockResolvedValue([{ name: "test-config", id: "test-id", apiProvider: "anthropic" }]), - LoadConfig: jest.fn().mockResolvedValue({ apiProvider: "anthropic" }), - SetModeConfig: jest.fn(), + getModeConfigId: jest.fn().mockResolvedValue("test-id"), + listConfig: jest.fn().mockResolvedValue([{ name: "test-config", id: "test-id", apiProvider: "anthropic" }]), + loadConfig: jest.fn().mockResolvedValue({ apiProvider: "anthropic" }), + setModeConfig: jest.fn(), } as any // Switch to architect mode await messageHandler({ type: "mode", text: "architect" }) // Should load the saved config for architect mode - expect(provider.configManager.GetModeConfigId).toHaveBeenCalledWith("architect") - expect(provider.configManager.LoadConfig).toHaveBeenCalledWith("test-config") + expect(provider.configManager.getModeConfigId).toHaveBeenCalledWith("architect") + expect(provider.configManager.loadConfig).toHaveBeenCalledWith("test-config") expect(mockContext.globalState.update).toHaveBeenCalledWith("currentApiConfigName", "test-config") }) @@ -464,11 +464,11 @@ describe("ClineProvider", () => { // Mock ConfigManager methods provider.configManager = { - GetModeConfigId: jest.fn().mockResolvedValue(undefined), - ListConfig: jest + getModeConfigId: jest.fn().mockResolvedValue(undefined), + listConfig: jest .fn() .mockResolvedValue([{ name: "current-config", id: "current-id", apiProvider: "anthropic" }]), - SetModeConfig: jest.fn(), + setModeConfig: jest.fn(), } as any // Mock current config name @@ -483,7 +483,7 @@ describe("ClineProvider", () => { await messageHandler({ type: "mode", text: "architect" }) // Should save current config as default for architect mode - expect(provider.configManager.SetModeConfig).toHaveBeenCalledWith("architect", "current-id") + expect(provider.configManager.setModeConfig).toHaveBeenCalledWith("architect", "current-id") }) test("saves config as default for current mode when loading config", async () => { @@ -491,10 +491,10 @@ describe("ClineProvider", () => { const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0] provider.configManager = { - LoadConfig: jest.fn().mockResolvedValue({ apiProvider: "anthropic", id: "new-id" }), - ListConfig: jest.fn().mockResolvedValue([{ name: "new-config", id: "new-id", apiProvider: "anthropic" }]), - SetModeConfig: jest.fn(), - GetModeConfigId: jest.fn().mockResolvedValue(undefined), + loadConfig: jest.fn().mockResolvedValue({ apiProvider: "anthropic", id: "new-id" }), + listConfig: jest.fn().mockResolvedValue([{ name: "new-config", id: "new-id", apiProvider: "anthropic" }]), + setModeConfig: jest.fn(), + getModeConfigId: jest.fn().mockResolvedValue(undefined), } as any // First set the mode @@ -504,7 +504,7 @@ describe("ClineProvider", () => { await messageHandler({ type: "loadApiConfiguration", text: "new-config" }) // Should save new config as default for architect mode - expect(provider.configManager.SetModeConfig).toHaveBeenCalledWith("architect", "new-id") + expect(provider.configManager.setModeConfig).toHaveBeenCalledWith("architect", "new-id") }) test("handles request delay settings messages", async () => { @@ -678,8 +678,8 @@ describe("ClineProvider", () => { const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0] provider.configManager = { - ListConfig: jest.fn().mockResolvedValue([{ name: "test-config", id: "test-id", apiProvider: "anthropic" }]), - SetModeConfig: jest.fn(), + listConfig: jest.fn().mockResolvedValue([{ name: "test-config", id: "test-id", apiProvider: "anthropic" }]), + setModeConfig: jest.fn(), } as any // Update API configuration @@ -689,7 +689,7 @@ describe("ClineProvider", () => { }) // Should save config as default for current mode - expect(provider.configManager.SetModeConfig).toHaveBeenCalledWith("code", "test-id") + expect(provider.configManager.setModeConfig).toHaveBeenCalledWith("code", "test-id") }) test("file content includes line numbers", async () => { diff --git a/webview-ui/src/components/prompts/PromptsView.tsx b/webview-ui/src/components/prompts/PromptsView.tsx index cebcf1a..888d1f1 100644 --- a/webview-ui/src/components/prompts/PromptsView.tsx +++ b/webview-ui/src/components/prompts/PromptsView.tsx @@ -22,6 +22,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { mode, customInstructions, setCustomInstructions, + preferredLanguage, + setPreferredLanguage, } = useExtensionState() const [testPrompt, setTestPrompt] = useState("") const [isEnhancing, setIsEnhancing] = useState(false) @@ -146,6 +148,55 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
+
+
Preferred Language
+ +

+ Select the language that Cline should use for communication. +

+
+
Custom Instructions for All Modes
diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index e9b12b0..76b139f 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -1,20 +1,11 @@ -import { - VSCodeButton, - VSCodeCheckbox, - VSCodeLink, - VSCodeTextArea, - VSCodeTextField, -} from "@vscode/webview-ui-toolkit/react" +import { VSCodeButton, VSCodeCheckbox, VSCodeLink, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" import { memo, useEffect, useState } from "react" import { useExtensionState } from "../../context/ExtensionStateContext" import { validateApiConfiguration, validateModelId } from "../../utils/validate" import { vscode } from "../../utils/vscode" import ApiOptions from "./ApiOptions" -import McpEnabledToggle from "../mcp/McpEnabledToggle" import ApiConfigManager from "./ApiConfigManager" -const IS_DEV = false // FIXME: use flags when packaging - type SettingsViewProps = { onDone: () => void } @@ -23,8 +14,6 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { const { apiConfiguration, version, - customInstructions, - setCustomInstructions, alwaysAllowReadOnly, setAlwaysAllowReadOnly, alwaysAllowWrite, @@ -49,8 +38,6 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { allowedCommands, fuzzyMatchThreshold, setFuzzyMatchThreshold, - preferredLanguage, - setPreferredLanguage, writeDelayMs, setWriteDelayMs, screenshotQuality, @@ -82,7 +69,6 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { type: "apiConfiguration", apiConfiguration, }) - vscode.postMessage({ type: "customInstructions", text: customInstructions }) vscode.postMessage({ type: "alwaysAllowReadOnly", bool: alwaysAllowReadOnly }) vscode.postMessage({ type: "alwaysAllowWrite", bool: alwaysAllowWrite }) vscode.postMessage({ type: "alwaysAllowExecute", bool: alwaysAllowExecute }) @@ -94,7 +80,6 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { vscode.postMessage({ type: "diffEnabled", bool: diffEnabled }) vscode.postMessage({ type: "browserViewportSize", text: browserViewportSize }) vscode.postMessage({ type: "fuzzyMatchThreshold", value: fuzzyMatchThreshold ?? 1.0 }) - vscode.postMessage({ type: "preferredLanguage", text: preferredLanguage }) vscode.postMessage({ type: "writeDelayMs", value: writeDelayMs }) vscode.postMessage({ type: "screenshotQuality", value: screenshotQuality ?? 75 }) vscode.postMessage({ type: "terminalOutputLineLimit", value: terminalOutputLineLimit ?? 500 }) @@ -168,257 +153,69 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
-
-

- Provider Settings -

- { - vscode.postMessage({ - type: "loadApiConfiguration", - text: configName, - }) - }} - onDeleteConfig={(configName: string) => { - vscode.postMessage({ - type: "deleteApiConfiguration", - text: configName, - }) - }} - onRenameConfig={(oldName: string, newName: string) => { - vscode.postMessage({ - type: "renameApiConfiguration", - values: { oldName, newName }, - apiConfiguration, - }) - }} - onUpsertConfig={(configName: string) => { - vscode.postMessage({ - type: "upsertApiConfiguration", - text: configName, - apiConfiguration, - }) - }} - /> - -
- -
+
+

Provider Settings

-

- Agent Settings -

- - - -

- Select the language that Cline should use for communication. -

-
- -
- Custom Instructions - setCustomInstructions(e.target?.value ?? "")} - /> -

- These instructions are added to the end of the system prompt sent with every request. Custom - instructions set in .clinerules in the working directory are also included. For - mode-specific instructions, use the{" "} - Prompts tab - in the top menu. -

-
- - -
- -
-
- Terminal output limit - setTerminalOutputLineLimit(parseInt(e.target.value))} - style={{ - flexGrow: 1, - accentColor: "var(--vscode-button-background)", - height: "2px", + { + vscode.postMessage({ + type: "loadApiConfiguration", + text: configName, + }) + }} + onDeleteConfig={(configName: string) => { + vscode.postMessage({ + type: "deleteApiConfiguration", + text: configName, + }) + }} + onRenameConfig={(oldName: string, newName: string) => { + vscode.postMessage({ + type: "renameApiConfiguration", + values: { oldName, newName }, + apiConfiguration, + }) + }} + onUpsertConfig={(configName: string) => { + vscode.postMessage({ + type: "upsertApiConfiguration", + text: configName, + apiConfiguration, + }) }} /> - {terminalOutputLineLimit ?? 500} +
-

- Maximum number of lines to include in terminal output when executing commands. When exceeded - lines will be removed from the middle, saving tokens. -

-
- { - setDiffEnabled(e.target.checked) - if (!e.target.checked) { - // Reset experimental strategy when diffs are disabled - setExperimentalDiffStrategy(false) - } - }}> - Enable editing through diffs - -

- When enabled, Cline will be able to edit files more quickly and will automatically reject - truncated full-file writes. Works best with the latest Claude 3.5 Sonnet model. -

- - {diffEnabled && ( -
-
- ⚠️ - setExperimentalDiffStrategy(e.target.checked)}> - Use experimental unified diff strategy - -
-

- Enable the experimental unified diff strategy. This strategy might reduce the number of - retries caused by model errors but may cause unexpected behavior or incorrect edits. - Only enable if you understand the risks and are willing to carefully review all changes. -

- -
- Match precision - { - setFuzzyMatchThreshold(parseFloat(e.target.value)) - }} - style={{ - flexGrow: 1, - accentColor: "var(--vscode-button-background)", - height: "2px", - }} - /> - - {Math.round((fuzzyMatchThreshold || 1) * 100)}% - -
-

- This slider controls how precisely code sections must match when applying diffs. Lower - values allow more flexible matching but increase the risk of incorrect replacements. Use - values below 100% with extreme caution. -

-
- )} -
- -
- setAlwaysAllowReadOnly(e.target.checked)}> - Always approve read-only operations - -

- When enabled, Cline will automatically view directory contents and read files without requiring - you to click the Approve button. -

-
- -
-

- ⚠️ High-Risk Auto-Approve Settings -

+
+

Auto-Approve Settings

- The following settings allow Cline to automatically perform potentially dangerous operations - without requiring approval. Enable these settings only if you fully trust the AI and understand - the associated security risks. + The following settings allow Cline to automatically perform operations without requiring + approval. Enable these settings only if you fully trust the AI and understand the associated + security risks.

-
+
+ setAlwaysAllowReadOnly(e.target.checked)}> + Always approve read-only operations + +

+ When enabled, Cline will automatically view directory contents and read files without + requiring you to click the Approve button. +

+
+ +
setAlwaysAllowWrite(e.target.checked)}> @@ -457,7 +254,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { )}
-
+
setAlwaysAllowBrowser(e.target.checked)}> @@ -470,7 +267,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {

-
+
setAlwaysApproveResubmit(e.target.checked)}> @@ -521,7 +318,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {

-
+
setAlwaysAllowExecute(e.target.checked)}> @@ -614,135 +411,218 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
-
-
-
-

- Browser Settings -

- - -

- Select the viewport size for browser interactions. This affects how websites are - displayed and interacted with. -

-
- -
-
- Screenshot quality - setScreenshotQuality(parseInt(e.target.value))} - style={{ - flexGrow: 1, - accentColor: "var(--vscode-button-background)", - height: "2px", - }} - /> - {screenshotQuality ?? 75}% -
-

- Adjust the WebP quality of browser screenshots. Higher values provide clearer - screenshots but increase token usage. -

-
-
- -
-
-

- Notification Settings -

- setSoundEnabled(e.target.checked)}> - Enable sound effects - -

- When enabled, Cline will play sound effects for notifications and events. -

-
- {soundEnabled && ( -
-
- Volume - setSoundVolume(parseFloat(e.target.value))} - style={{ - flexGrow: 1, - accentColor: "var(--vscode-button-background)", - height: "2px", - }} - aria-label="Volume" - /> - - {((soundVolume ?? 0.5) * 100).toFixed(0)}% - -
-
- )} -
-
- - {IS_DEV && ( - <> -
Debug
- - Reset State - +
+

Browser Settings

+
+ +

- This will reset all global state and secret storage in the extension. + Select the viewport size for browser interactions. This affects how websites are displayed + and interacted with.

- - )} +
+ +
+
+ Screenshot quality + setScreenshotQuality(parseInt(e.target.value))} + style={{ + flexGrow: 1, + accentColor: "var(--vscode-button-background)", + height: "2px", + }} + /> + {screenshotQuality ?? 75}% +
+

+ Adjust the WebP quality of browser screenshots. Higher values provide clearer screenshots + but increase token usage. +

+
+
+ +
+

Notification Settings

+
+ setSoundEnabled(e.target.checked)}> + Enable sound effects + +

+ When enabled, Cline will play sound effects for notifications and events. +

+
+ {soundEnabled && ( +
+
+ Volume + setSoundVolume(parseFloat(e.target.value))} + style={{ + flexGrow: 1, + accentColor: "var(--vscode-button-background)", + height: "2px", + }} + aria-label="Volume" + /> + + {((soundVolume ?? 0.5) * 100).toFixed(0)}% + +
+
+ )} +
+ +
+

Advanced Settings

+
+
+ Terminal output limit + setTerminalOutputLineLimit(parseInt(e.target.value))} + style={{ + flexGrow: 1, + accentColor: "var(--vscode-button-background)", + height: "2px", + }} + /> + + {terminalOutputLineLimit ?? 500} + +
+

+ Maximum number of lines to include in terminal output when executing commands. When exceeded + lines will be removed from the middle, saving tokens. +

+
+ +
+ { + setDiffEnabled(e.target.checked) + if (!e.target.checked) { + // Reset experimental strategy when diffs are disabled + setExperimentalDiffStrategy(false) + } + }}> + Enable editing through diffs + +

+ When enabled, Cline will be able to edit files more quickly and will automatically reject + truncated full-file writes. Works best with the latest Claude 3.5 Sonnet model. +

+ + {diffEnabled && ( +
+
+ ⚠️ + setExperimentalDiffStrategy(e.target.checked)}> + + Use experimental unified diff strategy + + +
+

+ Enable the experimental unified diff strategy. This strategy might reduce the number + of retries caused by model errors but may cause unexpected behavior or incorrect + edits. Only enable if you understand the risks and are willing to carefully review + all changes. +

+ +
+ Match precision + { + setFuzzyMatchThreshold(parseFloat(e.target.value)) + }} + style={{ + flexGrow: 1, + accentColor: "var(--vscode-button-background)", + height: "2px", + }} + /> + + {Math.round((fuzzyMatchThreshold || 1) * 100)}% + +
+

+ This slider controls how precisely code sections must match when applying diffs. + Lower values allow more flexible matching but increase the risk of incorrect + replacements. Use values below 100% with extreme caution. +

+
+ )} +
+
{ reddit.com/r/roocline

-

v{version}

+

+ v{version} +

+ +

+ This will reset all global state and secret storage in the extension. +

+ + + Reset State +