Clean up the settings page

This commit is contained in:
Matt Rubens
2025-01-20 02:51:03 -05:00
parent 55852c41fe
commit 993ebaf999
6 changed files with 448 additions and 456 deletions

View File

@@ -64,7 +64,7 @@ export class ConfigManager {
/**
* List all available configs with metadata
*/
async ListConfig(): Promise<ApiConfigMeta[]> {
async listConfig(): Promise<ApiConfigMeta[]> {
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<void> {
async saveConfig(name: string, config: ApiConfiguration): Promise<void> {
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<ApiConfiguration> {
async loadConfig(name: string): Promise<ApiConfiguration> {
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<void> {
async deleteConfig(name: string): Promise<void> {
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<void> {
async setCurrentConfig(name: string): Promise<void> {
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<boolean> {
async hasConfig(name: string): Promise<boolean> {
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<void> {
async setModeConfig(mode: Mode, configId: string): Promise<void> {
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<string | undefined> {
async getModeConfigId(mode: Mode): Promise<string | undefined> {
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<void> {
await this.context.secrets.delete(this.getConfigKey())
}
private async readConfig(): Promise<ApiConfigData> {
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<void> {
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}`)
}

View File

@@ -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",
)
})

View File

@@ -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" })
}

View File

@@ -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 () => {

View File

@@ -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) => {
<div style={{ flex: 1, overflow: "auto", padding: "0 20px" }}>
<div style={{ marginBottom: "20px" }}>
<div style={{ marginBottom: "20px" }}>
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>Preferred Language</div>
<select
value={preferredLanguage}
onChange={(e) => {
setPreferredLanguage(e.target.value)
vscode.postMessage({
type: "preferredLanguage",
text: e.target.value,
})
}}
style={{
width: "100%",
padding: "4px 8px",
backgroundColor: "var(--vscode-input-background)",
color: "var(--vscode-input-foreground)",
border: "1px solid var(--vscode-input-border)",
borderRadius: "2px",
height: "28px",
}}>
<option value="English">English</option>
<option value="Arabic">Arabic - العربية</option>
<option value="Brazilian Portuguese">Portuguese - Português (Brasil)</option>
<option value="Czech">Czech - Čeština</option>
<option value="French">French - Français</option>
<option value="German">German - Deutsch</option>
<option value="Hindi">Hindi - ि</option>
<option value="Hungarian">Hungarian - Magyar</option>
<option value="Italian">Italian - Italiano</option>
<option value="Japanese">Japanese - </option>
<option value="Korean">Korean - </option>
<option value="Polish">Polish - Polski</option>
<option value="Portuguese">Portuguese - Português (Portugal)</option>
<option value="Russian">Russian - Русский</option>
<option value="Simplified Chinese">Simplified Chinese - </option>
<option value="Spanish">Spanish - Español</option>
<option value="Traditional Chinese">Traditional Chinese - </option>
<option value="Turkish">Turkish - Türkçe</option>
</select>
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
Select the language that Cline should use for communication.
</p>
</div>
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>Custom Instructions for All Modes</div>
<div
style={{ fontSize: "13px", color: "var(--vscode-descriptionForeground)", marginBottom: "8px" }}>

View File

@@ -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) => {
</div>
<div
style={{ flexGrow: 1, overflowY: "scroll", paddingRight: 8, display: "flex", flexDirection: "column" }}>
<div style={{ marginBottom: 5 }}>
<h3 style={{ color: "var(--vscode-foreground)", margin: 0, marginBottom: 15 }}>
Provider Settings
</h3>
<ApiConfigManager
currentApiConfigName={currentApiConfigName}
listApiConfigMeta={listApiConfigMeta}
onSelectConfig={(configName: string) => {
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,
})
}}
/>
<ApiOptions apiErrorMessage={apiErrorMessage} modelIdErrorMessage={modelIdErrorMessage} />
</div>
<div style={{ marginBottom: 5 }}>
<div style={{ marginBottom: 40 }}>
<h3 style={{ color: "var(--vscode-foreground)", margin: "0 0 15px 0" }}>Provider Settings</h3>
<div style={{ marginBottom: 15 }}>
<h3 style={{ color: "var(--vscode-foreground)", margin: 0, marginBottom: 15 }}>
Agent Settings
</h3>
<label style={{ fontWeight: "500", display: "block", marginBottom: 5 }}>
Preferred Language
</label>
<select
value={preferredLanguage}
onChange={(e) => setPreferredLanguage(e.target.value)}
style={{
width: "100%",
padding: "4px 8px",
backgroundColor: "var(--vscode-input-background)",
color: "var(--vscode-input-foreground)",
border: "1px solid var(--vscode-input-border)",
borderRadius: "2px",
height: "28px",
}}>
<option value="English">English</option>
<option value="Arabic">Arabic - العربية</option>
<option value="Brazilian Portuguese">Portuguese - Português (Brasil)</option>
<option value="Czech">Czech - Čeština</option>
<option value="French">French - Français</option>
<option value="German">German - Deutsch</option>
<option value="Hindi">Hindi - ि</option>
<option value="Hungarian">Hungarian - Magyar</option>
<option value="Italian">Italian - Italiano</option>
<option value="Japanese">Japanese - </option>
<option value="Korean">Korean - </option>
<option value="Polish">Polish - Polski</option>
<option value="Portuguese">Portuguese - Português (Portugal)</option>
<option value="Russian">Russian - Русский</option>
<option value="Simplified Chinese">Simplified Chinese - </option>
<option value="Spanish">Spanish - Español</option>
<option value="Traditional Chinese">Traditional Chinese - </option>
<option value="Turkish">Turkish - Türkçe</option>
</select>
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
Select the language that Cline should use for communication.
</p>
</div>
<div style={{ marginBottom: 15 }}>
<span style={{ fontWeight: "500" }}>Custom Instructions</span>
<VSCodeTextArea
value={customInstructions ?? ""}
style={{ width: "100%" }}
rows={4}
placeholder={
'e.g. "Run unit tests at the end", "Use TypeScript with async/await", "Speak in Spanish"'
}
onInput={(e: any) => setCustomInstructions(e.target?.value ?? "")}
/>
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
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{" "}
<span className="codicon codicon-notebook" style={{ fontSize: "10px" }}></span> Prompts tab
in the top menu.
</p>
</div>
<McpEnabledToggle />
</div>
<div style={{ marginBottom: 5 }}>
<div style={{ display: "flex", alignItems: "center", gap: "5px" }}>
<span style={{ fontWeight: "500", minWidth: "150px" }}>Terminal output limit</span>
<input
type="range"
min="100"
max="5000"
step="100"
value={terminalOutputLineLimit ?? 500}
onChange={(e) => setTerminalOutputLineLimit(parseInt(e.target.value))}
style={{
flexGrow: 1,
accentColor: "var(--vscode-button-background)",
height: "2px",
<ApiConfigManager
currentApiConfigName={currentApiConfigName}
listApiConfigMeta={listApiConfigMeta}
onSelectConfig={(configName: string) => {
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,
})
}}
/>
<span style={{ minWidth: "45px", textAlign: "left" }}>{terminalOutputLineLimit ?? 500}</span>
<ApiOptions apiErrorMessage={apiErrorMessage} modelIdErrorMessage={modelIdErrorMessage} />
</div>
<p style={{ fontSize: "12px", marginTop: "5px", color: "var(--vscode-descriptionForeground)" }}>
Maximum number of lines to include in terminal output when executing commands. When exceeded
lines will be removed from the middle, saving tokens.
</p>
</div>
<div style={{ marginBottom: 5 }}>
<VSCodeCheckbox
checked={diffEnabled}
onChange={(e: any) => {
setDiffEnabled(e.target.checked)
if (!e.target.checked) {
// Reset experimental strategy when diffs are disabled
setExperimentalDiffStrategy(false)
}
}}>
<span style={{ fontWeight: "500" }}>Enable editing through diffs</span>
</VSCodeCheckbox>
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
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.
</p>
{diffEnabled && (
<div style={{ marginTop: 10 }}>
<div style={{ display: "flex", alignItems: "center", gap: "5px" }}>
<span style={{ color: "var(--vscode-errorForeground)" }}></span>
<VSCodeCheckbox
checked={experimentalDiffStrategy}
onChange={(e: any) => setExperimentalDiffStrategy(e.target.checked)}>
<span style={{ fontWeight: "500" }}>Use experimental unified diff strategy</span>
</VSCodeCheckbox>
</div>
<p
style={{
fontSize: "12px",
marginBottom: 15,
color: "var(--vscode-descriptionForeground)",
}}>
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.
</p>
<div style={{ display: "flex", alignItems: "center", gap: "5px" }}>
<span style={{ fontWeight: "500", minWidth: "100px" }}>Match precision</span>
<input
type="range"
min="0.8"
max="1"
step="0.005"
value={fuzzyMatchThreshold ?? 1.0}
onChange={(e) => {
setFuzzyMatchThreshold(parseFloat(e.target.value))
}}
style={{
flexGrow: 1,
accentColor: "var(--vscode-button-background)",
height: "2px",
}}
/>
<span style={{ minWidth: "35px", textAlign: "left" }}>
{Math.round((fuzzyMatchThreshold || 1) * 100)}%
</span>
</div>
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
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.
</p>
</div>
)}
</div>
<div style={{ marginBottom: 5 }}>
<VSCodeCheckbox
checked={alwaysAllowReadOnly}
onChange={(e: any) => setAlwaysAllowReadOnly(e.target.checked)}>
<span style={{ fontWeight: "500" }}>Always approve read-only operations</span>
</VSCodeCheckbox>
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
When enabled, Cline will automatically view directory contents and read files without requiring
you to click the Approve button.
</p>
</div>
<div
style={{
marginBottom: 15,
border: "2px solid var(--vscode-errorForeground)",
borderRadius: "4px",
padding: "10px",
}}>
<h4 style={{ fontWeight: 500, margin: "0 0 10px 0", color: "var(--vscode-errorForeground)" }}>
High-Risk Auto-Approve Settings
</h4>
<div style={{ marginBottom: 40 }}>
<h3 style={{ color: "var(--vscode-foreground)", margin: "0 0 15px 0" }}>Auto-Approve Settings</h3>
<p style={{ fontSize: "12px", marginBottom: 15, color: "var(--vscode-descriptionForeground)" }}>
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.
</p>
<div style={{ marginBottom: 5 }}>
<div style={{ marginBottom: 15 }}>
<VSCodeCheckbox
checked={alwaysAllowReadOnly}
onChange={(e: any) => setAlwaysAllowReadOnly(e.target.checked)}>
<span style={{ fontWeight: "500" }}>Always approve read-only operations</span>
</VSCodeCheckbox>
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
When enabled, Cline will automatically view directory contents and read files without
requiring you to click the Approve button.
</p>
</div>
<div style={{ marginBottom: 15 }}>
<VSCodeCheckbox
checked={alwaysAllowWrite}
onChange={(e: any) => setAlwaysAllowWrite(e.target.checked)}>
@@ -457,7 +254,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
)}
</div>
<div style={{ marginBottom: 5 }}>
<div style={{ marginBottom: 15 }}>
<VSCodeCheckbox
checked={alwaysAllowBrowser}
onChange={(e: any) => setAlwaysAllowBrowser(e.target.checked)}>
@@ -470,7 +267,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
</p>
</div>
<div style={{ marginBottom: 5 }}>
<div style={{ marginBottom: 15 }}>
<VSCodeCheckbox
checked={alwaysApproveResubmit}
onChange={(e: any) => setAlwaysApproveResubmit(e.target.checked)}>
@@ -521,7 +318,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
</p>
</div>
<div style={{ marginBottom: 5 }}>
<div style={{ marginBottom: 15 }}>
<VSCodeCheckbox
checked={alwaysAllowExecute}
onChange={(e: any) => setAlwaysAllowExecute(e.target.checked)}>
@@ -614,135 +411,218 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
</div>
</div>
<div style={{ marginBottom: 5 }}>
<div style={{ marginBottom: 10 }}>
<div style={{ marginBottom: 15 }}>
<h3 style={{ color: "var(--vscode-foreground)", margin: 0, marginBottom: 15 }}>
Browser Settings
</h3>
<label style={{ fontWeight: "500", display: "block", marginBottom: 5 }}>
Viewport size
</label>
<select
value={browserViewportSize}
onChange={(e) => setBrowserViewportSize(e.target.value)}
style={{
width: "100%",
padding: "4px 8px",
backgroundColor: "var(--vscode-input-background)",
color: "var(--vscode-input-foreground)",
border: "1px solid var(--vscode-input-border)",
borderRadius: "2px",
height: "28px",
}}>
<option value="1280x800">Large Desktop (1280x800)</option>
<option value="900x600">Small Desktop (900x600)</option>
<option value="768x1024">Tablet (768x1024)</option>
<option value="360x640">Mobile (360x640)</option>
</select>
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
Select the viewport size for browser interactions. This affects how websites are
displayed and interacted with.
</p>
</div>
<div style={{ marginBottom: 15 }}>
<div style={{ display: "flex", alignItems: "center", gap: "5px" }}>
<span style={{ fontWeight: "500", minWidth: "100px" }}>Screenshot quality</span>
<input
type="range"
min="1"
max="100"
step="1"
value={screenshotQuality ?? 75}
onChange={(e) => setScreenshotQuality(parseInt(e.target.value))}
style={{
flexGrow: 1,
accentColor: "var(--vscode-button-background)",
height: "2px",
}}
/>
<span style={{ minWidth: "35px", textAlign: "left" }}>{screenshotQuality ?? 75}%</span>
</div>
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
Adjust the WebP quality of browser screenshots. Higher values provide clearer
screenshots but increase token usage.
</p>
</div>
</div>
<div style={{ marginBottom: 5 }}>
<div style={{ marginBottom: 10 }}>
<h3 style={{ color: "var(--vscode-foreground)", margin: 0, marginBottom: 15 }}>
Notification Settings
</h3>
<VSCodeCheckbox
checked={soundEnabled}
onChange={(e: any) => setSoundEnabled(e.target.checked)}>
<span style={{ fontWeight: "500" }}>Enable sound effects</span>
</VSCodeCheckbox>
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
When enabled, Cline will play sound effects for notifications and events.
</p>
</div>
{soundEnabled && (
<div style={{ marginLeft: 0 }}>
<div style={{ display: "flex", alignItems: "center", gap: "5px" }}>
<span style={{ fontWeight: "500", minWidth: "100px" }}>Volume</span>
<input
type="range"
min="0"
max="1"
step="0.01"
value={soundVolume ?? 0.5}
onChange={(e) => setSoundVolume(parseFloat(e.target.value))}
style={{
flexGrow: 1,
accentColor: "var(--vscode-button-background)",
height: "2px",
}}
aria-label="Volume"
/>
<span style={{ minWidth: "35px", textAlign: "left" }}>
{((soundVolume ?? 0.5) * 100).toFixed(0)}%
</span>
</div>
</div>
)}
</div>
</div>
{IS_DEV && (
<>
<div style={{ marginTop: "10px", marginBottom: "4px" }}>Debug</div>
<VSCodeButton onClick={handleResetState} style={{ marginTop: "5px", width: "auto" }}>
Reset State
</VSCodeButton>
<div style={{ marginBottom: 40 }}>
<h3 style={{ color: "var(--vscode-foreground)", margin: "0 0 15px 0" }}>Browser Settings</h3>
<div style={{ marginBottom: 15 }}>
<label style={{ fontWeight: "500", display: "block", marginBottom: 5 }}>Viewport size</label>
<select
value={browserViewportSize}
onChange={(e) => setBrowserViewportSize(e.target.value)}
style={{
width: "100%",
padding: "4px 8px",
backgroundColor: "var(--vscode-input-background)",
color: "var(--vscode-input-foreground)",
border: "1px solid var(--vscode-input-border)",
borderRadius: "2px",
height: "28px",
}}>
<option value="1280x800">Large Desktop (1280x800)</option>
<option value="900x600">Small Desktop (900x600)</option>
<option value="768x1024">Tablet (768x1024)</option>
<option value="360x640">Mobile (360x640)</option>
</select>
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
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.
</p>
</>
)}
</div>
<div style={{ marginBottom: 15 }}>
<div style={{ display: "flex", alignItems: "center", gap: "5px" }}>
<span style={{ fontWeight: "500", minWidth: "100px" }}>Screenshot quality</span>
<input
type="range"
min="1"
max="100"
step="1"
value={screenshotQuality ?? 75}
onChange={(e) => setScreenshotQuality(parseInt(e.target.value))}
style={{
flexGrow: 1,
accentColor: "var(--vscode-button-background)",
height: "2px",
}}
/>
<span style={{ minWidth: "35px", textAlign: "left" }}>{screenshotQuality ?? 75}%</span>
</div>
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
Adjust the WebP quality of browser screenshots. Higher values provide clearer screenshots
but increase token usage.
</p>
</div>
</div>
<div style={{ marginBottom: 40 }}>
<h3 style={{ color: "var(--vscode-foreground)", margin: "0 0 15px 0" }}>Notification Settings</h3>
<div style={{ marginBottom: 15 }}>
<VSCodeCheckbox checked={soundEnabled} onChange={(e: any) => setSoundEnabled(e.target.checked)}>
<span style={{ fontWeight: "500" }}>Enable sound effects</span>
</VSCodeCheckbox>
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
When enabled, Cline will play sound effects for notifications and events.
</p>
</div>
{soundEnabled && (
<div style={{ marginLeft: 0 }}>
<div style={{ display: "flex", alignItems: "center", gap: "5px" }}>
<span style={{ fontWeight: "500", minWidth: "100px" }}>Volume</span>
<input
type="range"
min="0"
max="1"
step="0.01"
value={soundVolume ?? 0.5}
onChange={(e) => setSoundVolume(parseFloat(e.target.value))}
style={{
flexGrow: 1,
accentColor: "var(--vscode-button-background)",
height: "2px",
}}
aria-label="Volume"
/>
<span style={{ minWidth: "35px", textAlign: "left" }}>
{((soundVolume ?? 0.5) * 100).toFixed(0)}%
</span>
</div>
</div>
)}
</div>
<div style={{ marginBottom: 40 }}>
<h3 style={{ color: "var(--vscode-foreground)", margin: "0 0 15px 0" }}>Advanced Settings</h3>
<div style={{ marginBottom: 15 }}>
<div style={{ display: "flex", alignItems: "center", gap: "5px" }}>
<span style={{ fontWeight: "500", minWidth: "150px" }}>Terminal output limit</span>
<input
type="range"
min="100"
max="5000"
step="100"
value={terminalOutputLineLimit ?? 500}
onChange={(e) => setTerminalOutputLineLimit(parseInt(e.target.value))}
style={{
flexGrow: 1,
accentColor: "var(--vscode-button-background)",
height: "2px",
}}
/>
<span style={{ minWidth: "45px", textAlign: "left" }}>
{terminalOutputLineLimit ?? 500}
</span>
</div>
<p style={{ fontSize: "12px", marginTop: "5px", color: "var(--vscode-descriptionForeground)" }}>
Maximum number of lines to include in terminal output when executing commands. When exceeded
lines will be removed from the middle, saving tokens.
</p>
</div>
<div style={{ marginBottom: 15 }}>
<VSCodeCheckbox
checked={diffEnabled}
onChange={(e: any) => {
setDiffEnabled(e.target.checked)
if (!e.target.checked) {
// Reset experimental strategy when diffs are disabled
setExperimentalDiffStrategy(false)
}
}}>
<span style={{ fontWeight: "500" }}>Enable editing through diffs</span>
</VSCodeCheckbox>
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
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.
</p>
{diffEnabled && (
<div style={{ marginTop: 10 }}>
<div style={{ display: "flex", alignItems: "center", gap: "5px" }}>
<span style={{ color: "var(--vscode-errorForeground)" }}>⚠️</span>
<VSCodeCheckbox
checked={experimentalDiffStrategy}
onChange={(e: any) => setExperimentalDiffStrategy(e.target.checked)}>
<span style={{ fontWeight: "500" }}>
Use experimental unified diff strategy
</span>
</VSCodeCheckbox>
</div>
<p
style={{
fontSize: "12px",
marginBottom: 15,
color: "var(--vscode-descriptionForeground)",
}}>
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.
</p>
<div style={{ display: "flex", alignItems: "center", gap: "5px" }}>
<span style={{ fontWeight: "500", minWidth: "100px" }}>Match precision</span>
<input
type="range"
min="0.8"
max="1"
step="0.005"
value={fuzzyMatchThreshold ?? 1.0}
onChange={(e) => {
setFuzzyMatchThreshold(parseFloat(e.target.value))
}}
style={{
flexGrow: 1,
accentColor: "var(--vscode-button-background)",
height: "2px",
}}
/>
<span style={{ minWidth: "35px", textAlign: "left" }}>
{Math.round((fuzzyMatchThreshold || 1) * 100)}%
</span>
</div>
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
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.
</p>
</div>
)}
</div>
</div>
<div
style={{
@@ -763,7 +643,25 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
reddit.com/r/roocline
</VSCodeLink>
</p>
<p style={{ fontStyle: "italic", margin: "10px 0 0 0", padding: 0 }}>v{version}</p>
<p style={{ fontStyle: "italic", margin: "10px 0 0 0", padding: 0, marginBottom: 100 }}>
v{version}
</p>
<p
style={{
fontSize: "12px",
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
This will reset all global state and secret storage in the extension.
</p>
<VSCodeButton
onClick={handleResetState}
appearance="secondary"
style={{ marginTop: "5px", width: "auto" }}>
Reset State
</VSCodeButton>
</div>
</div>
</div>