Compare commits

...
Author SHA1 Message Date
Cline Evaluation 1bb8512ac7 adding stuff 2025-05-12 19:59:27 +04:00
Cline Evaluation a081b3a958 Add cute animation 2025-05-12 18:07:57 +04:00
Cline Evaluation e5b807f081 Add cute animation 2025-05-12 01:08:03 +04:00
Cline Evaluation 52ab54a93a Add cute animation 2025-05-12 00:40:25 +04:00
Cline Evaluation 50d1722cca Add cute animation 2025-05-11 19:00:26 +04:00
Cline Evaluation 2cba4cfe89 Add cute animation 2025-05-11 18:57:12 +04:00
Cline Evaluation 17db0a9e64 Add cute animation 2025-05-11 18:50:49 +04:00
Cline Evaluation 43595ee498 Add cute animation 2025-05-11 18:05:11 +04:00
Cline Evaluation 87210947f1 Removing browser stuff 2025-05-11 17:55:57 +04:00
Cline Evaluation 8c9631b664 Removing browser stuff 2025-05-11 17:41:02 +04:00
Cline Evaluation 22f5fac523 Removing browser stuff 2025-05-11 17:05:37 +04:00
Cline Evaluation afacfe01ac Removing browser stuff 2025-05-11 17:00:36 +04:00
Cline Evaluation 623d6ce36a Add chromeExecutablePath to BrowserSettings and UpdateBrowserSettingsRequest
- Introduced optional chromeExecutablePath field in BrowserSettings and UpdateBrowserSettingsRequest.
- Updated updateBrowserSettings function to merge new settings with existing ones, preserving previous values.
- Enhanced BrowserSession to check for the chromeExecutablePath in global state.
- Modified BrowserSettingsSection to include a UI input for specifying the Chrome executable path.
2025-05-11 16:58:13 +04:00
Cline Evaluation bd7257ed34 Removing redundant settings 2025-05-11 16:22:13 +04:00
9 changed files with 387 additions and 152 deletions
-34
View File
@@ -235,45 +235,11 @@
"configuration": {
"title": "Cline",
"properties": {
"cline.vsCodeLmModelSelector": {
"type": "object",
"properties": {
"vendor": {
"type": "string",
"description": "The vendor of the language model (e.g. copilot)"
},
"family": {
"type": "string",
"description": "The family of the language model (e.g. gpt-4)"
}
},
"description": "Settings for VSCode Language Model API"
},
"cline.enableCheckpoints": {
"type": "boolean",
"default": true,
"description": "Enables extension to save checkpoints of workspace throughout the task. Uses git under the hood which may not work well with large workspaces."
},
"cline.disableBrowserTool": {
"type": "boolean",
"default": false,
"description": "Disables extension from spawning browser session."
},
"cline.modelSettings.o3Mini.reasoningEffort": {
"type": "string",
"enum": [
"low",
"medium",
"high"
],
"default": "medium",
"description": "Controls the reasoning effort when using an OpenAI reasoning model. Higher values may result in more thorough but slower responses."
},
"cline.chromeExecutablePath": {
"type": "string",
"default": null,
"description": "Path to Chrome executable for browser use functionality. If not set, the extension will attempt to find or download it automatically."
},
"cline.preferredLanguage": {
"type": "string",
"enum": [
+4
View File
@@ -40,6 +40,8 @@ message BrowserSettings {
Viewport viewport = 1;
optional string remote_browser_host = 2;
optional bool remote_browser_enabled = 3;
optional string chrome_executable_path = 4;
optional bool disable_tool_use = 5;
}
message UpdateBrowserSettingsRequest {
@@ -47,4 +49,6 @@ message UpdateBrowserSettingsRequest {
Viewport viewport = 2;
optional string remote_browser_host = 3;
optional bool remote_browser_enabled = 4;
optional string chrome_executable_path = 5;
optional bool disable_tool_use = 6;
}
@@ -1,8 +1,8 @@
import { UpdateBrowserSettingsRequest } from "../../../shared/proto/browser"
import { Boolean } from "../../../shared/proto/common"
import { Controller } from "../index"
import { updateGlobalState } from "../../storage/state"
import { BrowserSettings as SharedBrowserSettings } from "../../../shared/BrowserSettings"
import { updateGlobalState, getGlobalState } from "../../storage/state"
import { BrowserSettings as SharedBrowserSettings, DEFAULT_BROWSER_SETTINGS } from "../../../shared/BrowserSettings"
/**
* Update browser settings
@@ -12,23 +12,39 @@ import { BrowserSettings as SharedBrowserSettings } from "../../../shared/Browse
*/
export async function updateBrowserSettings(controller: Controller, request: UpdateBrowserSettingsRequest): Promise<Boolean> {
try {
// Convert from protobuf format to shared format
const browserSettings: SharedBrowserSettings = {
// Get current browser settings to preserve fields not in the request
const currentSettings = (await getGlobalState(controller.context, "browserSettings")) as SharedBrowserSettings | undefined
const mergedWithDefaults = { ...DEFAULT_BROWSER_SETTINGS, ...currentSettings }
// Convert from protobuf format to shared format, merging with existing settings
const newBrowserSettings: SharedBrowserSettings = {
...mergedWithDefaults, // Start with existing settings (and defaults)
viewport: {
width: request.viewport?.width || 900,
height: request.viewport?.height || 600,
// Apply updates from request
width: request.viewport?.width || mergedWithDefaults.viewport.width,
height: request.viewport?.height || mergedWithDefaults.viewport.height,
},
remoteBrowserEnabled: request.remoteBrowserEnabled || false,
remoteBrowserHost: request.remoteBrowserHost || undefined,
// Explicitly handle optional boolean and string fields from the request
remoteBrowserEnabled:
request.remoteBrowserEnabled === undefined
? mergedWithDefaults.remoteBrowserEnabled
: request.remoteBrowserEnabled,
remoteBrowserHost:
request.remoteBrowserHost === undefined ? mergedWithDefaults.remoteBrowserHost : request.remoteBrowserHost,
chromeExecutablePath:
// If chromeExecutablePath is explicitly in the request (even as ""), use it.
// Otherwise, fall back to mergedWithDefaults.
"chromeExecutablePath" in request ? request.chromeExecutablePath : mergedWithDefaults.chromeExecutablePath,
disableToolUse: request.disableToolUse === undefined ? mergedWithDefaults.disableToolUse : request.disableToolUse,
}
// Update global state with new settings
await updateGlobalState(controller.context, "browserSettings", browserSettings)
await updateGlobalState(controller.context, "browserSettings", newBrowserSettings)
// Update task browser settings if task exists
if (controller.task) {
controller.task.browserSettings = browserSettings
controller.task.browserSession.browserSettings = browserSettings
controller.task.browserSettings = newBrowserSettings
controller.task.browserSession.browserSettings = newBrowserSettings
}
// Post updated state to webview
+16 -1
View File
@@ -1442,13 +1442,28 @@ export class Task {
return statusCode && !message.includes(statusCode.toString()) ? `${statusCode} - ${message}` : message
}
/**
* Migrates the disableBrowserTool setting from VSCode configuration to browserSettings
*/
private async migrateDisableBrowserToolSetting(): Promise<void> {
const config = vscode.workspace.getConfiguration("cline")
const disableBrowserTool = vscode.workspace.getConfiguration("cline").get<boolean>("disableBrowserTool")
if (disableBrowserTool !== undefined) {
this.browserSettings.disableToolUse = disableBrowserTool
// Remove from VSCode configuration
await config.update("disableBrowserTool", undefined, true)
}
}
async *attemptApiRequest(previousApiReqIndex: number): ApiStream {
// Wait for MCP servers to be connected before generating system prompt
await pWaitFor(() => this.mcpHub.isConnecting !== true, { timeout: 10_000 }).catch(() => {
console.error("MCP servers failed to connect in time")
})
const disableBrowserTool = vscode.workspace.getConfiguration("cline").get<boolean>("disableBrowserTool") ?? false
await this.migrateDisableBrowserToolSetting()
const disableBrowserTool = this.browserSettings.disableToolUse ?? false
// cline browser tool uses image recognition for navigation (requires model image support).
const modelSupportsBrowserUse = this.api.getModel().info.supportsImages ?? false
+18 -4
View File
@@ -67,11 +67,25 @@ export class BrowserSession {
}
}
async getDetectedChromePath(): Promise<{ path: string; isBundled: boolean }> {
// First check VSCode config
/**
* Migrates the chromeExecutablePath setting from VSCode configuration to browserSettings
*/
private async migrateChromeExecutablePathSetting(): Promise<void> {
const config = vscode.workspace.getConfiguration("cline")
const configPath = vscode.workspace.getConfiguration("cline").get<string>("chromeExecutablePath")
if (configPath && (await fileExistsAtPath(configPath))) {
return { path: configPath, isBundled: false }
if (configPath !== undefined) {
this.browserSettings.chromeExecutablePath = configPath
// Remove from VSCode configuration
await config.update("chromeExecutablePath", undefined, true)
}
}
async getDetectedChromePath(): Promise<{ path: string; isBundled: boolean }> {
// First check browserSettings (from UI, stored in global state)
await this.migrateChromeExecutablePathSetting()
if (this.browserSettings.chromeExecutablePath && (await fileExistsAtPath(this.browserSettings.chromeExecutablePath))) {
return { path: this.browserSettings.chromeExecutablePath, isBundled: false }
}
// Then try to find system Chrome
+4
View File
@@ -8,6 +8,8 @@ export interface BrowserSettings {
// chromeType: "chromium" | "system"
remoteBrowserHost?: string
remoteBrowserEnabled?: boolean
chromeExecutablePath?: string
disableToolUse?: boolean
}
export const DEFAULT_BROWSER_SETTINGS: BrowserSettings = {
@@ -17,7 +19,9 @@ export const DEFAULT_BROWSER_SETTINGS: BrowserSettings = {
},
remoteBrowserEnabled: false,
remoteBrowserHost: "http://localhost:9222",
chromeExecutablePath: "", // Changed from undefined to empty string
// chromeType: "chromium",
disableToolUse: false,
}
export const BROWSER_VIEWPORT_PRESETS = {
+83 -2
View File
@@ -36,6 +36,8 @@ export interface BrowserSettings {
viewport?: Viewport | undefined
remoteBrowserHost?: string | undefined
remoteBrowserEnabled?: boolean | undefined
chromeExecutablePath?: string | undefined
disableToolUse?: boolean | undefined
}
export interface UpdateBrowserSettingsRequest {
@@ -43,6 +45,8 @@ export interface UpdateBrowserSettingsRequest {
viewport?: Viewport | undefined
remoteBrowserHost?: string | undefined
remoteBrowserEnabled?: boolean | undefined
chromeExecutablePath?: string | undefined
disableToolUse?: boolean | undefined
}
function createBaseBrowserConnectionInfo(): BrowserConnectionInfo {
@@ -382,7 +386,13 @@ export const Viewport: MessageFns<Viewport> = {
}
function createBaseBrowserSettings(): BrowserSettings {
return { viewport: undefined, remoteBrowserHost: undefined, remoteBrowserEnabled: undefined }
return {
viewport: undefined,
remoteBrowserHost: undefined,
remoteBrowserEnabled: undefined,
chromeExecutablePath: undefined,
disableToolUse: undefined,
}
}
export const BrowserSettings: MessageFns<BrowserSettings> = {
@@ -396,6 +406,12 @@ export const BrowserSettings: MessageFns<BrowserSettings> = {
if (message.remoteBrowserEnabled !== undefined) {
writer.uint32(24).bool(message.remoteBrowserEnabled)
}
if (message.chromeExecutablePath !== undefined) {
writer.uint32(34).string(message.chromeExecutablePath)
}
if (message.disableToolUse !== undefined) {
writer.uint32(40).bool(message.disableToolUse)
}
return writer
},
@@ -430,6 +446,22 @@ export const BrowserSettings: MessageFns<BrowserSettings> = {
message.remoteBrowserEnabled = reader.bool()
continue
}
case 4: {
if (tag !== 34) {
break
}
message.chromeExecutablePath = reader.string()
continue
}
case 5: {
if (tag !== 40) {
break
}
message.disableToolUse = reader.bool()
continue
}
}
if ((tag & 7) === 4 || tag === 0) {
break
@@ -446,6 +478,8 @@ export const BrowserSettings: MessageFns<BrowserSettings> = {
remoteBrowserEnabled: isSet(object.remoteBrowserEnabled)
? globalThis.Boolean(object.remoteBrowserEnabled)
: undefined,
chromeExecutablePath: isSet(object.chromeExecutablePath) ? globalThis.String(object.chromeExecutablePath) : undefined,
disableToolUse: isSet(object.disableToolUse) ? globalThis.Boolean(object.disableToolUse) : undefined,
}
},
@@ -460,6 +494,12 @@ export const BrowserSettings: MessageFns<BrowserSettings> = {
if (message.remoteBrowserEnabled !== undefined) {
obj.remoteBrowserEnabled = message.remoteBrowserEnabled
}
if (message.chromeExecutablePath !== undefined) {
obj.chromeExecutablePath = message.chromeExecutablePath
}
if (message.disableToolUse !== undefined) {
obj.disableToolUse = message.disableToolUse
}
return obj
},
@@ -472,12 +512,21 @@ export const BrowserSettings: MessageFns<BrowserSettings> = {
object.viewport !== undefined && object.viewport !== null ? Viewport.fromPartial(object.viewport) : undefined
message.remoteBrowserHost = object.remoteBrowserHost ?? undefined
message.remoteBrowserEnabled = object.remoteBrowserEnabled ?? undefined
message.chromeExecutablePath = object.chromeExecutablePath ?? undefined
message.disableToolUse = object.disableToolUse ?? undefined
return message
},
}
function createBaseUpdateBrowserSettingsRequest(): UpdateBrowserSettingsRequest {
return { metadata: undefined, viewport: undefined, remoteBrowserHost: undefined, remoteBrowserEnabled: undefined }
return {
metadata: undefined,
viewport: undefined,
remoteBrowserHost: undefined,
remoteBrowserEnabled: undefined,
chromeExecutablePath: undefined,
disableToolUse: undefined,
}
}
export const UpdateBrowserSettingsRequest: MessageFns<UpdateBrowserSettingsRequest> = {
@@ -494,6 +543,12 @@ export const UpdateBrowserSettingsRequest: MessageFns<UpdateBrowserSettingsReque
if (message.remoteBrowserEnabled !== undefined) {
writer.uint32(32).bool(message.remoteBrowserEnabled)
}
if (message.chromeExecutablePath !== undefined) {
writer.uint32(42).string(message.chromeExecutablePath)
}
if (message.disableToolUse !== undefined) {
writer.uint32(48).bool(message.disableToolUse)
}
return writer
},
@@ -536,6 +591,22 @@ export const UpdateBrowserSettingsRequest: MessageFns<UpdateBrowserSettingsReque
message.remoteBrowserEnabled = reader.bool()
continue
}
case 5: {
if (tag !== 42) {
break
}
message.chromeExecutablePath = reader.string()
continue
}
case 6: {
if (tag !== 48) {
break
}
message.disableToolUse = reader.bool()
continue
}
}
if ((tag & 7) === 4 || tag === 0) {
break
@@ -553,6 +624,8 @@ export const UpdateBrowserSettingsRequest: MessageFns<UpdateBrowserSettingsReque
remoteBrowserEnabled: isSet(object.remoteBrowserEnabled)
? globalThis.Boolean(object.remoteBrowserEnabled)
: undefined,
chromeExecutablePath: isSet(object.chromeExecutablePath) ? globalThis.String(object.chromeExecutablePath) : undefined,
disableToolUse: isSet(object.disableToolUse) ? globalThis.Boolean(object.disableToolUse) : undefined,
}
},
@@ -570,6 +643,12 @@ export const UpdateBrowserSettingsRequest: MessageFns<UpdateBrowserSettingsReque
if (message.remoteBrowserEnabled !== undefined) {
obj.remoteBrowserEnabled = message.remoteBrowserEnabled
}
if (message.chromeExecutablePath !== undefined) {
obj.chromeExecutablePath = message.chromeExecutablePath
}
if (message.disableToolUse !== undefined) {
obj.disableToolUse = message.disableToolUse
}
return obj
},
@@ -584,6 +663,8 @@ export const UpdateBrowserSettingsRequest: MessageFns<UpdateBrowserSettingsReque
object.viewport !== undefined && object.viewport !== null ? Viewport.fromPartial(object.viewport) : undefined
message.remoteBrowserHost = object.remoteBrowserHost ?? undefined
message.remoteBrowserEnabled = object.remoteBrowserEnabled ?? undefined
message.chromeExecutablePath = object.chromeExecutablePath ?? undefined
message.disableToolUse = object.disableToolUse ?? undefined
return message
},
}
-10
View File
@@ -53,14 +53,4 @@ describe("Extension Tests", function () {
await vscode.commands.executeCommand("cline.historyButtonClicked")
// Success if no error thrown
})
it("should handle advanced settings configuration", async () => {
// Test browser session setting
await vscode.workspace.getConfiguration().update("cline.disableBrowserTool", true, true)
const updatedConfig = vscode.workspace.getConfiguration("cline")
expect(updatedConfig.get("disableBrowserTool")).to.be.true
// Reset settings
await vscode.workspace.getConfiguration().update("cline.disableBrowserTool", undefined, true)
})
})
@@ -37,8 +37,22 @@ const ConnectionStatusIndicator = ({
)
}
const CollapsibleContent = styled.div<{ isOpen: boolean }>`
overflow: hidden;
transition:
max-height 0.3s ease-in-out,
opacity 0.3s ease-in-out,
margin-top 0.3s ease-in-out,
visibility 0.3s ease-in-out;
max-height: ${({ isOpen }) => (isOpen ? "1000px" : "0")}; // Sufficiently large height
opacity: ${({ isOpen }) => (isOpen ? 1 : 0)};
margin-top: ${({ isOpen }) => (isOpen ? "15px" : "0")};
visibility: ${({ isOpen }) => (isOpen ? "visible" : "hidden")};
`
export const BrowserSettingsSection: React.FC = () => {
const { browserSettings } = useExtensionState()
const [localChromePath, setLocalChromePath] = useState(browserSettings.chromeExecutablePath || "")
const [isCheckingConnection, setIsCheckingConnection] = useState(false)
const [connectionStatus, setConnectionStatus] = useState<boolean | null>(null)
const [relaunchResult, setRelaunchResult] = useState<{ success: boolean; message: string } | null>(null)
@@ -91,6 +105,14 @@ export const BrowserSettingsSection: React.FC = () => {
})
}, [])
// Sync localChromePath with global state
useEffect(() => {
if (browserSettings.chromeExecutablePath !== localChromePath) {
setLocalChromePath(browserSettings.chromeExecutablePath || "")
}
// Removed sync for local disableToolUse state
}, [browserSettings.chromeExecutablePath, browserSettings.disableToolUse])
// Debounced connection check function
const debouncedCheckConnection = useCallback(
debounce(() => {
@@ -147,6 +169,8 @@ export const BrowserSettingsSection: React.FC = () => {
},
remoteBrowserEnabled: browserSettings.remoteBrowserEnabled,
remoteBrowserHost: browserSettings.remoteBrowserHost,
chromeExecutablePath: browserSettings.chromeExecutablePath,
disableToolUse: browserSettings.disableToolUse,
})
.then((response) => {
if (!response.value) {
@@ -169,6 +193,8 @@ export const BrowserSettingsSection: React.FC = () => {
remoteBrowserEnabled: enabled,
// If disabling, also clear the host
remoteBrowserHost: enabled ? browserSettings.remoteBrowserHost : undefined,
chromeExecutablePath: browserSettings.chromeExecutablePath,
disableToolUse: browserSettings.disableToolUse,
})
.then((response) => {
if (!response.value) {
@@ -189,6 +215,55 @@ export const BrowserSettingsSection: React.FC = () => {
},
remoteBrowserEnabled: browserSettings.remoteBrowserEnabled,
remoteBrowserHost: host,
chromeExecutablePath: browserSettings.chromeExecutablePath,
disableToolUse: browserSettings.disableToolUse,
})
.then((response) => {
if (!response.value) {
console.error("Failed to update browser settings")
}
})
.catch((error) => {
console.error("Error updating browser settings:", error)
})
}
const debouncedUpdateChromePath = useCallback(
debounce((newPath: string | undefined) => {
BrowserServiceClient.updateBrowserSettings({
metadata: {},
viewport: {
width: browserSettings.viewport.width,
height: browserSettings.viewport.height,
},
remoteBrowserEnabled: browserSettings.remoteBrowserEnabled,
remoteBrowserHost: browserSettings.remoteBrowserHost,
chromeExecutablePath: newPath,
disableToolUse: browserSettings.disableToolUse,
})
.then((response) => {
if (!response.value) {
console.error("Failed to update browser settings for chromeExecutablePath")
}
})
.catch((error) => {
console.error("Error updating browser settings for chromeExecutablePath:", error)
})
}, 500),
[browserSettings],
)
const updateChromeExecutablePath = (path: string | undefined) => {
BrowserServiceClient.updateBrowserSettings({
metadata: {},
viewport: {
width: browserSettings.viewport.width,
height: browserSettings.viewport.height,
},
remoteBrowserEnabled: browserSettings.remoteBrowserEnabled,
remoteBrowserHost: browserSettings.remoteBrowserHost,
chromeExecutablePath: path,
disableToolUse: browserSettings.disableToolUse,
})
.then((response) => {
if (!response.value) {
@@ -247,6 +322,28 @@ export const BrowserSettingsSection: React.FC = () => {
return () => clearInterval(pollInterval)
}, [browserSettings.remoteBrowserEnabled, checkConnectionOnce])
const updateDisableToolUse = (disabled: boolean) => {
BrowserServiceClient.updateBrowserSettings({
metadata: {},
viewport: {
width: browserSettings.viewport.width,
height: browserSettings.viewport.height,
},
remoteBrowserEnabled: browserSettings.remoteBrowserEnabled,
remoteBrowserHost: browserSettings.remoteBrowserHost,
chromeExecutablePath: browserSettings.chromeExecutablePath,
disableToolUse: disabled,
})
.then((response) => {
if (!response.value) {
console.error("Failed to update disableToolUse setting")
}
})
.catch((error) => {
console.error("Error updating disableToolUse setting:", error)
})
}
const relaunchChromeDebugMode = () => {
setDebugMode(true)
setRelaunchResult(null)
@@ -260,121 +357,169 @@ export const BrowserSettingsSection: React.FC = () => {
// Determine if we should show the relaunch button
const isRemoteEnabled = Boolean(browserSettings.remoteBrowserEnabled)
const shouldShowRelaunchButton = isRemoteEnabled && connectionStatus === false
const isSubSettingsOpen = !(browserSettings.disableToolUse || false)
return (
<div
id="browser-settings-section"
style={{ marginBottom: 20, borderTop: "1px solid var(--vscode-panel-border)", paddingTop: 15 }}>
<h3 style={{ color: "var(--vscode-foreground)", margin: "0 0 10px 0", fontSize: "14px" }}>Browser Settings</h3>
<div style={{ marginBottom: 15 }}>
<div style={{ marginBottom: 8 }}>
<label style={{ fontWeight: "500", display: "block", marginBottom: 5 }}>Viewport size</label>
<VSCodeDropdown
style={{ width: "100%" }}
value={
Object.entries(BROWSER_VIEWPORT_PRESETS).find(([_, size]) => {
const typedSize = size as { width: number; height: number }
return (
typedSize.width === browserSettings.viewport.width &&
typedSize.height === browserSettings.viewport.height
)
})?.[0]
}
onChange={(event) => handleViewportChange(event as Event)}>
{Object.entries(BROWSER_VIEWPORT_PRESETS).map(([name]) => (
<VSCodeOption key={name} value={name}>
{name}
</VSCodeOption>
))}
</VSCodeDropdown>
</div>
{/* Master Toggle */}
<div style={{ marginBottom: isSubSettingsOpen ? 0 : 10 }}>
<VSCodeCheckbox
checked={browserSettings.disableToolUse || false}
onChange={(e) => updateDisableToolUse((e.target as HTMLInputElement).checked)}>
Disable browser tool usage
</VSCodeCheckbox>
<p
style={{
fontSize: "12px",
color: "var(--vscode-descriptionForeground)",
margin: 0,
margin: "4px 0 0 0px",
}}>
Set the size of the browser viewport for screenshots and interactions.
Prevent Cline from using browser actions (e.g. launch, click, type).
</p>
</div>
<div style={{ marginBottom: 0 }}>
<div style={{ marginBottom: 4, display: "flex", alignItems: "center", justifyContent: "space-between" }}>
<VSCodeCheckbox
checked={browserSettings.remoteBrowserEnabled}
onChange={(e) => updateRemoteBrowserEnabled((e.target as HTMLInputElement).checked)}>
Use remote browser connection
</VSCodeCheckbox>
<ConnectionStatusIndicator
isChecking={isCheckingConnection}
isConnected={connectionStatus}
remoteBrowserEnabled={browserSettings.remoteBrowserEnabled}
/>
<CollapsibleContent isOpen={isSubSettingsOpen}>
<div style={{ marginBottom: 15 }}>
<div style={{ marginBottom: 8 }}>
<label style={{ fontWeight: "500", display: "block", marginBottom: 5 }}>Viewport size</label>
<VSCodeDropdown
style={{ width: "100%" }}
value={
Object.entries(BROWSER_VIEWPORT_PRESETS).find(([_, size]) => {
const typedSize = size as { width: number; height: number }
return (
typedSize.width === browserSettings.viewport.width &&
typedSize.height === browserSettings.viewport.height
)
})?.[0]
}
onChange={(event) => handleViewportChange(event as Event)}>
{Object.entries(BROWSER_VIEWPORT_PRESETS).map(([name]) => (
<VSCodeOption key={name} value={name}>
{name}
</VSCodeOption>
))}
</VSCodeDropdown>
</div>
<p
style={{
fontSize: "12px",
color: "var(--vscode-descriptionForeground)",
margin: 0,
}}>
Set the size of the browser viewport for screenshots and interactions.
</p>
</div>
<p
style={{
fontSize: "12px",
color: "var(--vscode-descriptionForeground)",
margin: "0 0 6px 0px",
}}>
Enable Cline to use your Chrome
{isBundled ? "(not detected on your machine)" : detectedChromePath ? ` (${detectedChromePath})` : ""}. This
requires starting Chrome in debug mode
{browserSettings.remoteBrowserEnabled ? (
<>
{" "}
manually (<code>--remote-debugging-port=9222</code>) or using the button below. Enter the host address
or leave it blank for automatic discovery.
</>
) : (
"."
)}
</p>
{browserSettings.remoteBrowserEnabled && (
<div style={{ marginLeft: 0 }}>
<VSCodeTextField
value={browserSettings.remoteBrowserHost || ""}
placeholder="http://localhost:9222"
style={{ width: "100%", marginBottom: 8 }}
onChange={(e: any) => updateRemoteBrowserHost(e.target.value || undefined)}
<div style={{ marginBottom: 0 }}>
{" "}
{/* This div now contains Remote Connection & Chrome Path */}
<div style={{ marginBottom: 4, display: "flex", alignItems: "center", justifyContent: "space-between" }}>
<VSCodeCheckbox
checked={browserSettings.remoteBrowserEnabled}
onChange={(e) => updateRemoteBrowserEnabled((e.target as HTMLInputElement).checked)}>
Use remote browser connection
</VSCodeCheckbox>
<ConnectionStatusIndicator
isChecking={isCheckingConnection}
isConnected={connectionStatus}
remoteBrowserEnabled={browserSettings.remoteBrowserEnabled}
/>
{shouldShowRelaunchButton && (
<div style={{ display: "flex", gap: "10px", marginBottom: 8, justifyContent: "center" }}>
<VSCodeButton style={{ flex: 1 }} disabled={debugMode} onClick={relaunchChromeDebugMode}>
{debugMode ? "Relaunching Browser..." : "Relaunch Browser with Debug Mode"}
</VSCodeButton>
</div>
</div>
<p
style={{
fontSize: "12px",
color: "var(--vscode-descriptionForeground)",
margin: "0 0 6px 0px",
}}>
Enable Cline to use your Chrome
{isBundled ? "(not detected on your machine)" : detectedChromePath ? ` (${detectedChromePath})` : ""}. You
can specify a custom path below. Using a remote browser connection requires starting Chrome in debug mode
{browserSettings.remoteBrowserEnabled ? (
<>
{" "}
manually (<code>--remote-debugging-port=9222</code>) or using the button below. Enter the host
address or leave it blank for automatic discovery.
</>
) : (
"."
)}
</p>
{/* Moved remote-specific settings to appear directly after enabling remote connection */}
{browserSettings.remoteBrowserEnabled && (
<div style={{ marginLeft: 0, marginTop: 8 }}>
<VSCodeTextField
value={browserSettings.remoteBrowserHost || ""}
placeholder="http://localhost:9222"
style={{ width: "100%", marginBottom: 8 }}
onChange={(e: any) => updateRemoteBrowserHost(e.target.value || undefined)}
/>
{relaunchResult && (
<div
{shouldShowRelaunchButton && (
<div style={{ display: "flex", gap: "10px", marginBottom: 8, justifyContent: "center" }}>
<VSCodeButton style={{ flex: 1 }} disabled={debugMode} onClick={relaunchChromeDebugMode}>
{debugMode ? "Relaunching Browser..." : "Relaunch Browser with Debug Mode"}
</VSCodeButton>
</div>
)}
{relaunchResult && (
<div
style={{
padding: "8px",
marginBottom: "8px",
backgroundColor: relaunchResult.success ? "rgba(0, 128, 0, 0.1)" : "rgba(255, 0, 0, 0.1)",
color: relaunchResult.success
? "var(--vscode-terminal-ansiGreen)"
: "var(--vscode-terminal-ansiRed)",
borderRadius: "3px",
fontSize: "11px",
whiteSpace: "pre-wrap",
wordBreak: "break-word",
}}>
{relaunchResult.message}
</div>
)}
<p
style={{
padding: "8px",
marginBottom: "8px",
backgroundColor: relaunchResult.success ? "rgba(0, 128, 0, 0.1)" : "rgba(255, 0, 0, 0.1)",
color: relaunchResult.success
? "var(--vscode-terminal-ansiGreen)"
: "var(--vscode-terminal-ansiRed)",
borderRadius: "3px",
fontSize: "11px",
whiteSpace: "pre-wrap",
wordBreak: "break-word",
}}>
{relaunchResult.message}
</div>
)}
fontSize: "12px",
color: "var(--vscode-descriptionForeground)",
margin: 0,
}}></p>
</div>
)}
{/* Chrome Executable Path section now follows remote-specific settings */}
<div style={{ marginBottom: 8, marginTop: 8 }}>
<label htmlFor="chrome-executable-path" style={{ fontWeight: "500", display: "block", marginBottom: 5 }}>
Chrome Executable Path (Optional)
</label>
<VSCodeTextField
id="chrome-executable-path"
value={localChromePath}
placeholder="e.g., /usr/bin/google-chrome or C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
style={{ width: "100%" }}
onChange={(e: any) => {
const newValue = e.target.value || ""
setLocalChromePath(newValue)
debouncedUpdateChromePath(newValue) // Send "" if empty, not undefined
}}
/>
<p
style={{
fontSize: "12px",
color: "var(--vscode-descriptionForeground)",
margin: 0,
}}></p>
margin: "4px 0 0 0",
}}>
Leave blank to auto-detect.
</p>
</div>
)}
</div>
</div>
</CollapsibleContent>
</div>
)
}