mirror of
https://github.com/cline/cline.git
synced 2026-09-01 15:11:04 +08:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b3076cc2c8 | |||
| fd1fe64bb9 | |||
| 7990ae3eca | |||
| d9688d0c93 | |||
| ec592ac08a | |||
| ddddb5d519 | |||
| 678aaceb3a |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
New settings page split into tabs
|
||||
Generated
+51
-62
@@ -11,6 +11,7 @@
|
||||
"@floating-ui/react": "^0.27.4",
|
||||
"@heroui/react": "^2.8.0-beta.2",
|
||||
"@vscode/webview-ui-toolkit": "^1.4.0",
|
||||
"clsx": "^2.1.1",
|
||||
"debounce": "^2.1.1",
|
||||
"dompurify": "^3.2.4",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
@@ -19,6 +20,7 @@
|
||||
"fuse.js": "^7.0.0",
|
||||
"fzf": "^0.5.2",
|
||||
"katex": "^0.16.22",
|
||||
"lucide-react": "^0.511.0",
|
||||
"mermaid": "^11.4.1",
|
||||
"package.json": "^2.0.1",
|
||||
"posthog-js": "^1.224.0",
|
||||
@@ -37,6 +39,7 @@
|
||||
"remark-math": "^6.0.0",
|
||||
"remark-stringify": "^11.0.0",
|
||||
"styled-components": "^6.1.15",
|
||||
"tailwind-merge": "^3.3.0",
|
||||
"unified": "^11.0.5",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
@@ -2513,6 +2516,14 @@
|
||||
"react": ">=18 || >=19.0.0-rc.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/system-rsc/node_modules/clsx": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/table": {
|
||||
"version": "2.2.17-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/table/-/table-2.2.17-beta.2.tgz",
|
||||
@@ -2590,6 +2601,23 @@
|
||||
"tailwindcss": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/theme/node_modules/clsx": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/theme/node_modules/tailwind-merge": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.2.tgz",
|
||||
"integrity": "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/dcastil"
|
||||
}
|
||||
},
|
||||
"node_modules/@heroui/toast": {
|
||||
"version": "2.0.8-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@heroui/toast/-/toast-2.0.8-beta.2.tgz",
|
||||
@@ -3615,15 +3643,6 @@
|
||||
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/focus/node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/form": {
|
||||
"version": "3.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@react-aria/form/-/form-3.0.14.tgz",
|
||||
@@ -3839,15 +3858,6 @@
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/grid/node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/i18n": {
|
||||
"version": "3.12.7",
|
||||
"resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.12.7.tgz",
|
||||
@@ -3970,15 +3980,6 @@
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/landmark/node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/link": {
|
||||
"version": "3.7.10",
|
||||
"resolved": "https://registry.npmjs.org/@react-aria/link/-/link-3.7.10.tgz",
|
||||
@@ -4290,15 +4291,6 @@
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/spinbutton/node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/ssr": {
|
||||
"version": "3.9.7",
|
||||
"resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.7.tgz",
|
||||
@@ -4535,15 +4527,6 @@
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/toggle/node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/toolbar": {
|
||||
"version": "3.0.0-beta.14",
|
||||
"resolved": "https://registry.npmjs.org/@react-aria/toolbar/-/toolbar-3.0.0-beta.14.tgz",
|
||||
@@ -4597,15 +4580,6 @@
|
||||
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/utils/node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/visually-hidden": {
|
||||
"version": "3.8.21",
|
||||
"resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.21.tgz",
|
||||
@@ -8278,10 +8252,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
|
||||
"license": "MIT",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
@@ -12487,6 +12460,14 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "0.511.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.511.0.tgz",
|
||||
"integrity": "sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lz-string": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||
@@ -15865,10 +15846,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.2.tgz",
|
||||
"integrity": "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==",
|
||||
"license": "MIT",
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.0.tgz",
|
||||
"integrity": "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/dcastil"
|
||||
@@ -15890,6 +15870,15 @@
|
||||
"tailwindcss": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwind-variants/node_modules/tailwind-merge": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.2.tgz",
|
||||
"integrity": "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/dcastil"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.5.tgz",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"@floating-ui/react": "^0.27.4",
|
||||
"@heroui/react": "^2.8.0-beta.2",
|
||||
"@vscode/webview-ui-toolkit": "^1.4.0",
|
||||
"clsx": "^2.1.1",
|
||||
"debounce": "^2.1.1",
|
||||
"dompurify": "^3.2.4",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
@@ -26,6 +27,7 @@
|
||||
"fuse.js": "^7.0.0",
|
||||
"fzf": "^0.5.2",
|
||||
"katex": "^0.16.22",
|
||||
"lucide-react": "^0.511.0",
|
||||
"mermaid": "^11.4.1",
|
||||
"package.json": "^2.0.1",
|
||||
"posthog-js": "^1.224.0",
|
||||
@@ -44,6 +46,7 @@
|
||||
"remark-math": "^6.0.0",
|
||||
"remark-stringify": "^11.0.0",
|
||||
"styled-components": "^6.1.15",
|
||||
"tailwind-merge": "^3.3.0",
|
||||
"unified": "^11.0.5",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
|
||||
@@ -71,7 +71,7 @@ export const BrowserSettingsMenu = () => {
|
||||
// After a short delay, send a message to scroll to browser settings
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await UiServiceClient.scrollToSettings({ value: "browser-settings-section" })
|
||||
await UiServiceClient.scrollToSettings({ value: "browser" })
|
||||
} catch (error) {
|
||||
console.error("Error scrolling to browser settings:", error)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import React, { HTMLAttributes, useCallback, forwardRef } from "react"
|
||||
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { cn } from "@/utils/cn"
|
||||
|
||||
type TabProps = HTMLAttributes<HTMLDivElement>
|
||||
|
||||
export const Tab = ({ className, children, ...props }: TabProps) => (
|
||||
<div className={cn("fixed inset-0 flex flex-col", className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
export const TabHeader = ({ className, children, ...props }: TabProps) => (
|
||||
<div className={cn("px-5 py-2.5 border-b border-[var(--vscode-panel-border)]", className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
export const TabContent = ({ className, children, ...props }: TabProps) => {
|
||||
const onWheel = useCallback((e: React.WheelEvent<HTMLDivElement>) => {
|
||||
const target = e.target as HTMLElement
|
||||
|
||||
// Prevent scrolling if the target is a listbox or option
|
||||
// (e.g. selects, dropdowns, etc).
|
||||
if (target.role === "listbox" || target.role === "option") {
|
||||
return
|
||||
}
|
||||
|
||||
e.currentTarget.scrollTop += e.deltaY
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={cn("flex-1 overflow-auto", className)} onWheel={onWheel} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const TabList = forwardRef<
|
||||
HTMLDivElement,
|
||||
HTMLAttributes<HTMLDivElement> & {
|
||||
value: string
|
||||
onValueChange: (value: string) => void
|
||||
}
|
||||
>(({ children, className, value, onValueChange, ...props }, ref) => {
|
||||
const handleTabSelect = useCallback(
|
||||
(tabValue: string) => {
|
||||
console.log("Tab selected:", tabValue)
|
||||
onValueChange(tabValue)
|
||||
},
|
||||
[onValueChange],
|
||||
)
|
||||
|
||||
return (
|
||||
<div ref={ref} role="tablist" className={cn("flex", className)} {...props}>
|
||||
{React.Children.map(children, (child) => {
|
||||
if (React.isValidElement(child)) {
|
||||
// Make sure we're passing the correct props to the TabTrigger
|
||||
return React.cloneElement(child as React.ReactElement<any>, {
|
||||
isSelected: child.props.value === value,
|
||||
onSelect: () => handleTabSelect(child.props.value),
|
||||
})
|
||||
}
|
||||
return child
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
export const TabTrigger = forwardRef<
|
||||
HTMLButtonElement,
|
||||
React.ButtonHTMLAttributes<HTMLButtonElement> & {
|
||||
value: string
|
||||
isSelected?: boolean
|
||||
onSelect?: () => void
|
||||
}
|
||||
>(({ children, className, value, isSelected, onSelect, ...props }, ref) => {
|
||||
// Ensure we're using the value prop correctly
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
role="tab"
|
||||
aria-selected={isSelected}
|
||||
tabIndex={isSelected ? 0 : -1}
|
||||
className={cn("focus:outline-none", className)}
|
||||
onClick={onSelect}
|
||||
data-value={value} // Add data-value attribute for debugging
|
||||
{...props}>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
})
|
||||
+24
-1
@@ -1,9 +1,12 @@
|
||||
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
|
||||
import { UiServiceClient } from "@/services/grpc-client"
|
||||
|
||||
import ServersToggleList from "./ServersToggleList"
|
||||
const InstalledServersView = () => {
|
||||
const { mcpServers: servers } = useExtensionState()
|
||||
const { mcpServers: servers, navigateToSettings } = useExtensionState()
|
||||
|
||||
return (
|
||||
<div style={{ padding: "16px 20px" }}>
|
||||
@@ -42,6 +45,26 @@ const InstalledServersView = () => {
|
||||
<span className="codicon codicon-server" style={{ marginRight: "6px" }}></span>
|
||||
Configure MCP Servers
|
||||
</VSCodeButton>
|
||||
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<VSCodeLink
|
||||
onClick={() => {
|
||||
// First open the settings panel using direct navigation
|
||||
navigateToSettings()
|
||||
|
||||
// After a short delay, send a message to scroll to browser settings
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await UiServiceClient.scrollToSettings({ value: "features" })
|
||||
} catch (error) {
|
||||
console.error("Error scrolling to mcp settings:", error)
|
||||
}
|
||||
}, 300)
|
||||
}}
|
||||
style={{ fontSize: "12px" }}>
|
||||
Advanced MCP Settings
|
||||
</VSCodeLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -366,11 +366,7 @@ export const BrowserSettingsSection: React.FC = () => {
|
||||
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 id="browser-settings-section" style={{ marginBottom: 20 }}>
|
||||
{/* Master Toggle */}
|
||||
<div style={{ marginBottom: isSubSettingsOpen ? 0 : 10 }}>
|
||||
<VSCodeCheckbox
|
||||
|
||||
@@ -14,8 +14,7 @@ const FeatureSettingsSection = () => {
|
||||
} = useExtensionState()
|
||||
|
||||
return (
|
||||
<div 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" }}>Feature Settings</h3>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<div>
|
||||
<VSCodeCheckbox
|
||||
checked={enableCheckpointsSetting}
|
||||
|
||||
@@ -9,7 +9,7 @@ interface PreferredLanguageSettingProps {
|
||||
|
||||
const PreferredLanguageSetting: React.FC<PreferredLanguageSettingProps> = ({ chatSettings, setChatSettings }) => {
|
||||
return (
|
||||
<div style={{ marginTop: 10, marginBottom: 10 }}>
|
||||
<div style={{}}>
|
||||
<label htmlFor="preferred-language-dropdown" className="block mb-1 text-sm font-medium">
|
||||
Preferred Language
|
||||
</label>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { HTMLAttributes } from "react"
|
||||
import { cn } from "@/utils/cn"
|
||||
|
||||
type SectionProps = HTMLAttributes<HTMLDivElement>
|
||||
|
||||
export const Section = ({ className, ...props }: SectionProps) => (
|
||||
<div className={cn("flex flex-col gap-3 p-5 py-2", className)} {...props} />
|
||||
)
|
||||
|
||||
export default Section
|
||||
@@ -0,0 +1,25 @@
|
||||
import { HTMLAttributes } from "react"
|
||||
import { cn } from "@/utils/cn"
|
||||
|
||||
import { OPENROUTER_MODEL_PICKER_Z_INDEX } from "./OpenRouterModelPicker"
|
||||
|
||||
type SectionHeaderProps = HTMLAttributes<HTMLDivElement> & {
|
||||
children: React.ReactNode
|
||||
description?: string
|
||||
}
|
||||
|
||||
export const SectionHeader = ({ description, children, className, ...props }: SectionHeaderProps) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
`sticky top-0 z-${OPENROUTER_MODEL_PICKER_Z_INDEX + 20} text-[var(--vscode-foreground)] bg-[var(--vscode-panel-background)] px-5 py-3`,
|
||||
className,
|
||||
)}
|
||||
{...props}>
|
||||
<h4 className="m-0">{children}</h4>
|
||||
{description && <p className="text-[var(--vscode-descriptionForeground)] text-sm mt-2 mb-0">{description}</p>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SectionHeader
|
||||
@@ -6,7 +6,24 @@ import {
|
||||
VSCodeOption,
|
||||
VSCodeTextArea,
|
||||
} from "@vscode/webview-ui-toolkit/react"
|
||||
import { memo, useCallback, useEffect, useState } from "react"
|
||||
import { memo, useCallback, useEffect, useState, useRef } from "react"
|
||||
import {
|
||||
Settings,
|
||||
Webhook,
|
||||
CheckCheck,
|
||||
SquareMousePointer,
|
||||
GitBranch,
|
||||
Bell,
|
||||
Database,
|
||||
SquareTerminal,
|
||||
FlaskConical,
|
||||
Globe,
|
||||
Info,
|
||||
LucideIcon,
|
||||
} from "lucide-react"
|
||||
import HeroTooltip from "@/components/common/HeroTooltip"
|
||||
import SectionHeader from "./SectionHeader"
|
||||
import Section from "./Section"
|
||||
import PreferredLanguageSetting from "./PreferredLanguageSetting" // Added import
|
||||
import { OpenAIReasoningEffort } from "@shared/ChatSettings"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
@@ -22,13 +39,91 @@ import FeatureSettingsSection from "./FeatureSettingsSection"
|
||||
import BrowserSettingsSection from "./BrowserSettingsSection"
|
||||
import TerminalSettingsSection from "./TerminalSettingsSection"
|
||||
import { FEATURE_FLAGS } from "@shared/services/feature-flags/feature-flags"
|
||||
import { Tab, TabContent, TabHeader, TabList, TabTrigger } from "../common/Tab"
|
||||
import { cn } from "@/utils/cn"
|
||||
const { IS_DEV } = process.env
|
||||
|
||||
// Styles for the tab system
|
||||
const settingsTabsContainer = "flex flex-1 overflow-hidden [&.narrow_.tab-label]:hidden"
|
||||
const settingsTabList =
|
||||
"w-48 data-[compact=true]:w-12 flex-shrink-0 flex flex-col overflow-y-auto overflow-x-hidden border-r border-[var(--vscode-sideBar-background)]"
|
||||
const settingsTabTrigger =
|
||||
"whitespace-nowrap overflow-hidden min-w-0 h-12 px-4 py-3 box-border flex items-center border-l-2 border-transparent text-[var(--vscode-foreground)] opacity-70 bg-transparent hover:bg-[var(--vscode-list-hoverBackground)] data-[compact=true]:w-12 data-[compact=true]:p-4 cursor-pointer"
|
||||
const settingsTabTriggerActive =
|
||||
"opacity-100 border-l-2 border-l-[var(--vscode-focusBorder)] border-t-0 border-r-0 border-b-0 bg-[var(--vscode-list-activeSelectionBackground)]"
|
||||
|
||||
// Tab definitions
|
||||
interface SettingsTab {
|
||||
id: string
|
||||
name: string
|
||||
tooltipText: string
|
||||
headerText: string
|
||||
icon: LucideIcon
|
||||
}
|
||||
|
||||
export const SETTINGS_TABS: SettingsTab[] = [
|
||||
{
|
||||
id: "api-config",
|
||||
name: "API Configuration",
|
||||
tooltipText: "API Configuration",
|
||||
headerText: "API Configuration",
|
||||
icon: Webhook,
|
||||
},
|
||||
{
|
||||
id: "general",
|
||||
name: "General",
|
||||
tooltipText: "General Settings",
|
||||
headerText: "General Settings",
|
||||
icon: Settings,
|
||||
},
|
||||
{
|
||||
id: "features",
|
||||
name: "Features",
|
||||
tooltipText: "Feature Settings",
|
||||
headerText: "Feature Settings",
|
||||
icon: CheckCheck,
|
||||
},
|
||||
{
|
||||
id: "browser",
|
||||
name: "Browser",
|
||||
tooltipText: "Browser Settings",
|
||||
headerText: "Browser Settings",
|
||||
icon: SquareMousePointer,
|
||||
},
|
||||
{
|
||||
id: "terminal",
|
||||
name: "Terminal",
|
||||
tooltipText: "Terminal Settings",
|
||||
headerText: "Terminal Settings",
|
||||
icon: SquareTerminal,
|
||||
},
|
||||
// Only show in dev mode
|
||||
...(IS_DEV
|
||||
? [
|
||||
{
|
||||
id: "debug",
|
||||
name: "Debug",
|
||||
tooltipText: "Debug Tools",
|
||||
headerText: "Debug",
|
||||
icon: FlaskConical,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
id: "about",
|
||||
name: "About",
|
||||
tooltipText: "About Cline",
|
||||
headerText: "About",
|
||||
icon: Info,
|
||||
},
|
||||
]
|
||||
|
||||
type SettingsViewProps = {
|
||||
onDone: () => void
|
||||
targetSection?: string
|
||||
}
|
||||
|
||||
const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
|
||||
const {
|
||||
apiConfiguration,
|
||||
version,
|
||||
@@ -126,24 +221,35 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
setPendingTabChange(null)
|
||||
}
|
||||
break
|
||||
// Handle tab navigation through targetSection prop instead
|
||||
case "grpc_response":
|
||||
if (message.grpc_response?.message?.action === "scrollToSettings") {
|
||||
setTimeout(() => {
|
||||
const elementId = message.grpc_response?.message?.value
|
||||
if (elementId) {
|
||||
const element = document.getElementById(elementId)
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: "smooth" })
|
||||
const tabId = message.grpc_response?.message?.value
|
||||
if (tabId) {
|
||||
console.log("Opening settings tab from GRPC response:", tabId)
|
||||
// Check if the value corresponds to a valid tab ID
|
||||
const isValidTabId = SETTINGS_TABS.some((tab) => tab.id === tabId)
|
||||
|
||||
element.style.transition = "background-color 0.5s ease"
|
||||
element.style.backgroundColor = "var(--vscode-textPreformat-background)"
|
||||
if (isValidTabId) {
|
||||
// Set the active tab directly
|
||||
setActiveTab(tabId)
|
||||
} else {
|
||||
// Fall back to the old behavior of scrolling to an element
|
||||
setTimeout(() => {
|
||||
const element = document.getElementById(tabId)
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: "smooth" })
|
||||
|
||||
setTimeout(() => {
|
||||
element.style.backgroundColor = "transparent"
|
||||
}, 1200)
|
||||
}
|
||||
element.style.transition = "background-color 0.5s ease"
|
||||
element.style.backgroundColor = "var(--vscode-textPreformat-background)"
|
||||
|
||||
setTimeout(() => {
|
||||
element.style.backgroundColor = "transparent"
|
||||
}, 1200)
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -161,7 +267,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleTabChange = (tab: "plan" | "act") => {
|
||||
const handlePlanActModeChange = (tab: "plan" | "act") => {
|
||||
if (tab === chatSettings.mode) {
|
||||
return
|
||||
}
|
||||
@@ -169,136 +275,325 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
handleSubmit(true)
|
||||
}
|
||||
|
||||
// Track active tab
|
||||
const [activeTab, setActiveTab] = useState<string>(targetSection || SETTINGS_TABS[0].id)
|
||||
|
||||
// Update active tab when targetSection changes
|
||||
useEffect(() => {
|
||||
if (targetSection) {
|
||||
setActiveTab(targetSection)
|
||||
}
|
||||
}, [targetSection])
|
||||
|
||||
// Enhanced tab change handler with debugging
|
||||
const handleTabChange = useCallback(
|
||||
(tabId: string) => {
|
||||
console.log("Tab change requested:", tabId, "Current:", activeTab)
|
||||
setActiveTab(tabId)
|
||||
},
|
||||
[activeTab],
|
||||
)
|
||||
|
||||
// Debug tab changes
|
||||
useEffect(() => {
|
||||
console.log("Active tab changed to:", activeTab)
|
||||
}, [activeTab])
|
||||
|
||||
// Track whether we're in compact mode
|
||||
const [isCompactMode, setIsCompactMode] = useState(false)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
// Setup resize observer to detect when we should switch to compact mode
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return
|
||||
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
// If container width is less than 500px, switch to compact mode
|
||||
setIsCompactMode(entry.contentRect.width < 500)
|
||||
}
|
||||
})
|
||||
|
||||
observer.observe(containerRef.current)
|
||||
|
||||
return () => {
|
||||
observer?.disconnect()
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="fixed top-0 left-0 right-0 bottom-0 pt-[10px] pr-0 pb-0 pl-5 flex flex-col overflow-hidden">
|
||||
<div className="flex justify-between items-center mb-[13px] pr-[17px]">
|
||||
<h3 className="text-[var(--vscode-foreground)] m-0">Settings</h3>
|
||||
<VSCodeButton onClick={() => handleSubmit(false)}>Save</VSCodeButton>
|
||||
<Tab>
|
||||
<TabHeader className="flex justify-between items-center gap-2">
|
||||
<div className="flex items-center gap-1">
|
||||
<h3 className="text-[var(--vscode-foreground)] m-0">Settings</h3>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<VSCodeButton onClick={() => handleSubmit(false)}>Save</VSCodeButton>
|
||||
</div>
|
||||
</TabHeader>
|
||||
|
||||
{/* Vertical tabs layout */}
|
||||
<div ref={containerRef} className={cn(settingsTabsContainer, isCompactMode && "narrow")}>
|
||||
{/* Tab sidebar */}
|
||||
<TabList
|
||||
value={activeTab}
|
||||
onValueChange={handleTabChange}
|
||||
className={cn(settingsTabList)}
|
||||
data-compact={isCompactMode}>
|
||||
{SETTINGS_TABS.map((tab) =>
|
||||
isCompactMode ? (
|
||||
<HeroTooltip key={tab.id} content={tab.tooltipText} placement="right">
|
||||
<div
|
||||
className={cn(
|
||||
activeTab === tab.id
|
||||
? `${settingsTabTrigger} ${settingsTabTriggerActive}`
|
||||
: settingsTabTrigger,
|
||||
"focus:ring-0",
|
||||
)}
|
||||
data-compact={isCompactMode}
|
||||
data-testid={`tab-${tab.id}`}
|
||||
data-value={tab.id}
|
||||
onClick={() => {
|
||||
console.log("Compact tab clicked:", tab.id)
|
||||
handleTabChange(tab.id)
|
||||
}}>
|
||||
<div className={cn("flex items-center gap-2", isCompactMode && "justify-center")}>
|
||||
<tab.icon className="w-4 h-4" />
|
||||
<span className="tab-label">{tab.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
</HeroTooltip>
|
||||
) : (
|
||||
<TabTrigger
|
||||
key={tab.id}
|
||||
value={tab.id}
|
||||
className={cn(
|
||||
activeTab === tab.id
|
||||
? `${settingsTabTrigger} ${settingsTabTriggerActive}`
|
||||
: settingsTabTrigger,
|
||||
"focus:ring-0",
|
||||
)}
|
||||
data-compact={isCompactMode}
|
||||
data-testid={`tab-${tab.id}`}>
|
||||
<div className={cn("flex items-center gap-2", isCompactMode && "justify-center")}>
|
||||
<tab.icon className="w-4 h-4" />
|
||||
<span className="tab-label">{tab.name}</span>
|
||||
</div>
|
||||
</TabTrigger>
|
||||
),
|
||||
)}
|
||||
</TabList>
|
||||
|
||||
{/* Helper function to render section header */}
|
||||
{(() => {
|
||||
const renderSectionHeader = (tabId: string) => {
|
||||
const tab = SETTINGS_TABS.find((t) => t.id === tabId)
|
||||
if (!tab) return null
|
||||
|
||||
return (
|
||||
<SectionHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
{(() => {
|
||||
const Icon = tab.icon
|
||||
return <Icon className="w-4" />
|
||||
})()}
|
||||
<div>{tab.headerText}</div>
|
||||
</div>
|
||||
</SectionHeader>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<TabContent className="flex-1 overflow-auto">
|
||||
{/* API Configuration Tab */}
|
||||
{activeTab === "api-config" && (
|
||||
<div>
|
||||
{renderSectionHeader("api-config")}
|
||||
<Section>
|
||||
{/* Tabs container */}
|
||||
{planActSeparateModelsSetting ? (
|
||||
<div className="rounded-md mb-5 bg-[var(--vscode-panel-background)]">
|
||||
<div className="flex gap-[1px] mb-[10px] -mt-2 border-0 border-b border-solid border-[var(--vscode-panel-border)]">
|
||||
<TabButton
|
||||
isActive={chatSettings.mode === "plan"}
|
||||
onClick={() => handlePlanActModeChange("plan")}>
|
||||
Plan Mode
|
||||
</TabButton>
|
||||
<TabButton
|
||||
isActive={chatSettings.mode === "act"}
|
||||
onClick={() => handlePlanActModeChange("act")}>
|
||||
Act Mode
|
||||
</TabButton>
|
||||
</div>
|
||||
|
||||
{/* Content container */}
|
||||
<div className="-mb-3">
|
||||
<ApiOptions
|
||||
key={chatSettings.mode}
|
||||
showModelOptions={true}
|
||||
apiErrorMessage={apiErrorMessage}
|
||||
modelIdErrorMessage={modelIdErrorMessage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<ApiOptions
|
||||
key={"single"}
|
||||
showModelOptions={true}
|
||||
apiErrorMessage={apiErrorMessage}
|
||||
modelIdErrorMessage={modelIdErrorMessage}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="mb-[5px]">
|
||||
<VSCodeCheckbox
|
||||
className="mb-[5px]"
|
||||
checked={planActSeparateModelsSetting}
|
||||
onChange={(e: any) => {
|
||||
const checked = e.target.checked === true
|
||||
setPlanActSeparateModelsSetting(checked)
|
||||
}}>
|
||||
Use different models for Plan and Act modes
|
||||
</VSCodeCheckbox>
|
||||
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
|
||||
Switching between Plan and Act mode will persist the API and model used in the
|
||||
previous mode. This may be helpful e.g. when using a strong reasoning model to
|
||||
architect a plan for a cheaper coding model to act on.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-[5px]">
|
||||
<VSCodeTextArea
|
||||
value={customInstructions ?? ""}
|
||||
className="w-full"
|
||||
resize="vertical"
|
||||
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 ?? "")}>
|
||||
<span className="font-medium">Custom Instructions</span>
|
||||
</VSCodeTextArea>
|
||||
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
|
||||
These instructions are added to the end of the system prompt sent with every
|
||||
request.
|
||||
</p>
|
||||
</div>
|
||||
</Section>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* General Settings Tab */}
|
||||
{activeTab === "general" && (
|
||||
<div>
|
||||
{renderSectionHeader("general")}
|
||||
<Section>
|
||||
{chatSettings && (
|
||||
<PreferredLanguageSetting
|
||||
chatSettings={chatSettings}
|
||||
setChatSettings={setChatSettings}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="mb-[5px]">
|
||||
<VSCodeCheckbox
|
||||
className="mb-[5px]"
|
||||
checked={telemetrySetting === "enabled"}
|
||||
onChange={(e: any) => {
|
||||
const checked = e.target.checked === true
|
||||
setTelemetrySetting(checked ? "enabled" : "disabled")
|
||||
}}>
|
||||
Allow anonymous error and usage reporting
|
||||
</VSCodeCheckbox>
|
||||
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
|
||||
Help improve Cline by sending anonymous usage data and error reports. No code,
|
||||
prompts, or personal information are ever sent. See our{" "}
|
||||
<VSCodeLink
|
||||
href="https://docs.cline.bot/more-info/telemetry"
|
||||
className="text-inherit">
|
||||
telemetry overview
|
||||
</VSCodeLink>{" "}
|
||||
and{" "}
|
||||
<VSCodeLink href="https://cline.bot/privacy" className="text-inherit">
|
||||
privacy policy
|
||||
</VSCodeLink>{" "}
|
||||
for more details.
|
||||
</p>
|
||||
</div>
|
||||
</Section>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Feature Settings Tab */}
|
||||
{activeTab === "features" && (
|
||||
<div>
|
||||
{renderSectionHeader("features")}
|
||||
<Section>
|
||||
<FeatureSettingsSection />
|
||||
</Section>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Browser Settings Tab */}
|
||||
{activeTab === "browser" && (
|
||||
<div>
|
||||
{renderSectionHeader("browser")}
|
||||
<Section>
|
||||
<BrowserSettingsSection />
|
||||
</Section>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Terminal Settings Tab */}
|
||||
{activeTab === "terminal" && (
|
||||
<div>
|
||||
{renderSectionHeader("terminal")}
|
||||
<Section>
|
||||
<TerminalSettingsSection />
|
||||
</Section>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Debug Tab (only in dev mode) */}
|
||||
{IS_DEV && activeTab === "debug" && (
|
||||
<div>
|
||||
{renderSectionHeader("debug")}
|
||||
<Section>
|
||||
<VSCodeButton
|
||||
onClick={handleResetState}
|
||||
className="mt-[5px] w-auto"
|
||||
style={{ backgroundColor: "var(--vscode-errorForeground)", color: "black" }}>
|
||||
Reset State
|
||||
</VSCodeButton>
|
||||
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
|
||||
This will reset all global state and secret storage in the extension.
|
||||
</p>
|
||||
</Section>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* About Tab */}
|
||||
{activeTab === "about" && (
|
||||
<div>
|
||||
{renderSectionHeader("about")}
|
||||
<Section>
|
||||
<div className="text-center text-[var(--vscode-descriptionForeground)] text-xs leading-[1.2] px-0 py-0 pr-2 pb-[15px] mt-auto">
|
||||
<p className="break-words m-0 p-0">
|
||||
If you have any questions or feedback, feel free to open an issue at{" "}
|
||||
<VSCodeLink href="https://github.com/cline/cline" className="inline">
|
||||
https://github.com/cline/cline
|
||||
</VSCodeLink>
|
||||
</p>
|
||||
<p className="italic mt-[10px] mb-0 p-0">v{version}</p>
|
||||
</div>
|
||||
</Section>
|
||||
</div>
|
||||
)}
|
||||
</TabContent>
|
||||
)
|
||||
})()}
|
||||
</div>
|
||||
<div className="grow overflow-y-scroll pr-2 flex flex-col">
|
||||
{/* Tabs container */}
|
||||
{planActSeparateModelsSetting ? (
|
||||
<div className="border border-solid border-[var(--vscode-panel-border)] rounded-md p-[10px] mb-5 bg-[var(--vscode-panel-background)]">
|
||||
<div className="flex gap-[1px] mb-[10px] -mt-2 border-0 border-b border-solid border-[var(--vscode-panel-border)]">
|
||||
<TabButton isActive={chatSettings.mode === "plan"} onClick={() => handleTabChange("plan")}>
|
||||
Plan Mode
|
||||
</TabButton>
|
||||
<TabButton isActive={chatSettings.mode === "act"} onClick={() => handleTabChange("act")}>
|
||||
Act Mode
|
||||
</TabButton>
|
||||
</div>
|
||||
|
||||
{/* Content container */}
|
||||
<div className="-mb-3">
|
||||
<ApiOptions
|
||||
key={chatSettings.mode}
|
||||
showModelOptions={true}
|
||||
apiErrorMessage={apiErrorMessage}
|
||||
modelIdErrorMessage={modelIdErrorMessage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<ApiOptions
|
||||
key={"single"}
|
||||
showModelOptions={true}
|
||||
apiErrorMessage={apiErrorMessage}
|
||||
modelIdErrorMessage={modelIdErrorMessage}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="mb-[5px]">
|
||||
<VSCodeTextArea
|
||||
value={customInstructions ?? ""}
|
||||
className="w-full"
|
||||
resize="vertical"
|
||||
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 ?? "")}>
|
||||
<span className="font-medium">Custom Instructions</span>
|
||||
</VSCodeTextArea>
|
||||
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
|
||||
These instructions are added to the end of the system prompt sent with every request.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{chatSettings && <PreferredLanguageSetting chatSettings={chatSettings} setChatSettings={setChatSettings} />}
|
||||
|
||||
<div className="mb-[5px]">
|
||||
<VSCodeCheckbox
|
||||
className="mb-[5px]"
|
||||
checked={planActSeparateModelsSetting}
|
||||
onChange={(e: any) => {
|
||||
const checked = e.target.checked === true
|
||||
setPlanActSeparateModelsSetting(checked)
|
||||
}}>
|
||||
Use different models for Plan and Act modes
|
||||
</VSCodeCheckbox>
|
||||
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
|
||||
Switching between Plan and Act mode will persist the API and model used in the previous mode. This may be
|
||||
helpful e.g. when using a strong reasoning model to architect a plan for a cheaper coding model to act on.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-[5px]">
|
||||
<VSCodeCheckbox
|
||||
className="mb-[5px]"
|
||||
checked={telemetrySetting === "enabled"}
|
||||
onChange={(e: any) => {
|
||||
const checked = e.target.checked === true
|
||||
setTelemetrySetting(checked ? "enabled" : "disabled")
|
||||
}}>
|
||||
Allow anonymous error and usage reporting
|
||||
</VSCodeCheckbox>
|
||||
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
|
||||
Help improve Cline by sending anonymous usage data and error reports. No code, prompts, or personal
|
||||
information are ever sent. See our{" "}
|
||||
<VSCodeLink href="https://docs.cline.bot/more-info/telemetry" className="text-inherit">
|
||||
telemetry overview
|
||||
</VSCodeLink>{" "}
|
||||
and{" "}
|
||||
<VSCodeLink href="https://cline.bot/privacy" className="text-inherit">
|
||||
privacy policy
|
||||
</VSCodeLink>{" "}
|
||||
for more details.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Feature Settings Section */}
|
||||
<FeatureSettingsSection />
|
||||
|
||||
{/* Browser Settings Section */}
|
||||
<BrowserSettingsSection />
|
||||
|
||||
{/* Terminal Settings Section */}
|
||||
<TerminalSettingsSection />
|
||||
|
||||
{IS_DEV && (
|
||||
<>
|
||||
<div className="mt-[10px] mb-1">Debug</div>
|
||||
<VSCodeButton
|
||||
onClick={handleResetState}
|
||||
className="mt-[5px] w-auto"
|
||||
style={{ backgroundColor: "var(--vscode-errorForeground)", color: "black" }}>
|
||||
Reset State
|
||||
</VSCodeButton>
|
||||
<p className="text-xs mt-[5px] text-[var(--vscode-descriptionForeground)]">
|
||||
This will reset all global state and secret storage in the extension.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="text-center text-[var(--vscode-descriptionForeground)] text-xs leading-[1.2] px-0 py-0 pr-2 pb-[15px] mt-auto">
|
||||
<p className="break-words m-0 p-0">
|
||||
If you have any questions or feedback, feel free to open an issue at{" "}
|
||||
<VSCodeLink href="https://github.com/cline/cline" className="inline">
|
||||
https://github.com/cline/cline
|
||||
</VSCodeLink>
|
||||
</p>
|
||||
<p className="italic mt-[10px] mb-0 p-0">v{version}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tab>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -49,10 +49,7 @@ export const TerminalSettingsSection: React.FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
id="terminal-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" }}>Terminal Settings</h3>
|
||||
<div id="terminal-settings-section" style={{ marginBottom: 20 }}>
|
||||
<div style={{ marginBottom: 15 }}>
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<label style={{ fontWeight: "500", display: "block", marginBottom: 5 }}>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
/**
|
||||
* A utility function that combines clsx and tailwind-merge to handle class name merging
|
||||
* with proper Tailwind CSS conflict resolution.
|
||||
*
|
||||
* @param inputs - Class values to be merged
|
||||
* @returns A string of merged class names
|
||||
*/
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
Reference in New Issue
Block a user