Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
264 changes: 88 additions & 176 deletions webview-ui/src/components/chat/AutoApproveMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,189 +1,120 @@
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
import { useCallback, useState } from "react"
import { useCallback, useMemo, useState } from "react"
import { Trans } from "react-i18next"
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"

import { Button } from "@/components/ui"
import { VSCodeCheckbox, VSCodeLink } from "@vscode/webview-ui-toolkit/react"

import { vscode } from "../../utils/vscode"
import { useExtensionState } from "../../context/ExtensionStateContext"
import { useAppTranslation } from "../../i18n/TranslationContext"

const ICON_MAP: Record<string, string> = {
readFiles: "eye",
editFiles: "edit",
executeCommands: "terminal",
useBrowser: "globe",
useMcp: "plug",
switchModes: "sync",
subtasks: "discard",
retryRequests: "refresh",
}

interface AutoApproveAction {
id: string
label: string
enabled: boolean
description: string
}
import { AutoApproveToggle, AutoApproveSetting, autoApproveSettingsConfig } from "../settings/AutoApproveToggle"

interface AutoApproveMenuProps {
style?: React.CSSProperties
}

const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
const [isExpanded, setIsExpanded] = useState(false)

const {
autoApprovalEnabled,
setAutoApprovalEnabled,
alwaysAllowReadOnly,
setAlwaysAllowReadOnly,
alwaysAllowWrite,
setAlwaysAllowWrite,
alwaysAllowExecute,
setAlwaysAllowExecute,
alwaysAllowBrowser,
setAlwaysAllowBrowser,
alwaysAllowMcp,
setAlwaysAllowMcp,
alwaysAllowModeSwitch,
setAlwaysAllowModeSwitch,
alwaysAllowSubtasks,
setAlwaysAllowSubtasks,
alwaysApproveResubmit,
setAlwaysAllowReadOnly,
setAlwaysAllowWrite,
setAlwaysAllowExecute,
setAlwaysAllowBrowser,
setAlwaysAllowMcp,
setAlwaysAllowModeSwitch,
setAlwaysAllowSubtasks,
setAlwaysApproveResubmit,
autoApprovalEnabled,
setAutoApprovalEnabled,
} = useExtensionState()

const { t } = useAppTranslation()

const actions: AutoApproveAction[] = [
{
id: "readFiles",
label: t("chat:autoApprove.actions.readFiles.label"),
enabled: alwaysAllowReadOnly ?? false,
description: t("chat:autoApprove.actions.readFiles.description"),
},
{
id: "editFiles",
label: t("chat:autoApprove.actions.editFiles.label"),
enabled: alwaysAllowWrite ?? false,
description: t("chat:autoApprove.actions.editFiles.description"),
},
{
id: "executeCommands",
label: t("chat:autoApprove.actions.executeCommands.label"),
enabled: alwaysAllowExecute ?? false,
description: t("chat:autoApprove.actions.executeCommands.description"),
},
{
id: "useBrowser",
label: t("chat:autoApprove.actions.useBrowser.label"),
enabled: alwaysAllowBrowser ?? false,
description: t("chat:autoApprove.actions.useBrowser.description"),
},
{
id: "useMcp",
label: t("chat:autoApprove.actions.useMcp.label"),
enabled: alwaysAllowMcp ?? false,
description: t("chat:autoApprove.actions.useMcp.description"),
},
{
id: "switchModes",
label: t("chat:autoApprove.actions.switchModes.label"),
enabled: alwaysAllowModeSwitch ?? false,
description: t("chat:autoApprove.actions.switchModes.description"),
const onAutoApproveToggle = useCallback(
(key: AutoApproveSetting, value: boolean) => {
vscode.postMessage({ type: key, bool: value })

switch (key) {
case "alwaysAllowReadOnly":
setAlwaysAllowReadOnly(value)
break
case "alwaysAllowWrite":
setAlwaysAllowWrite(value)
break
case "alwaysAllowExecute":
setAlwaysAllowExecute(value)
break
case "alwaysAllowBrowser":
setAlwaysAllowBrowser(value)
break
case "alwaysAllowMcp":
setAlwaysAllowMcp(value)
break
case "alwaysAllowModeSwitch":
setAlwaysAllowModeSwitch(value)
break
case "alwaysAllowSubtasks":
setAlwaysAllowSubtasks(value)
break
case "alwaysApproveResubmit":
setAlwaysApproveResubmit(value)
break
}
},
{
id: "subtasks",
label: t("chat:autoApprove.actions.subtasks.label"),
enabled: alwaysAllowSubtasks ?? false,
description: t("chat:autoApprove.actions.subtasks.description"),
},
{
id: "retryRequests",
label: t("chat:autoApprove.actions.retryRequests.label"),
enabled: alwaysApproveResubmit ?? false,
description: t("chat:autoApprove.actions.retryRequests.description"),
},
]
[
setAlwaysAllowReadOnly,
setAlwaysAllowWrite,
setAlwaysAllowExecute,
setAlwaysAllowBrowser,
setAlwaysAllowMcp,
setAlwaysAllowModeSwitch,
setAlwaysAllowSubtasks,
setAlwaysApproveResubmit,
],
)

const toggleExpanded = useCallback(() => {
setIsExpanded((prev) => !prev)
}, [])
const toggleExpanded = useCallback(() => setIsExpanded((prev) => !prev), [])

const toggles = useMemo(
() => ({
alwaysAllowReadOnly: alwaysAllowReadOnly,
alwaysAllowWrite: alwaysAllowWrite,
alwaysAllowExecute: alwaysAllowExecute,
alwaysAllowBrowser: alwaysAllowBrowser,
alwaysAllowMcp: alwaysAllowMcp,
alwaysAllowModeSwitch: alwaysAllowModeSwitch,
alwaysAllowSubtasks: alwaysAllowSubtasks,
alwaysApproveResubmit: alwaysApproveResubmit,
}),
[
alwaysAllowReadOnly,
alwaysAllowWrite,
alwaysAllowExecute,
alwaysAllowBrowser,
alwaysAllowMcp,
alwaysAllowModeSwitch,
alwaysAllowSubtasks,
alwaysApproveResubmit,
],
)

const enabledActionsList = actions
.filter((action) => action.enabled)
.map((action) => action.label)
const enabledActionsList = Object.entries(toggles)
.filter(([_key, value]) => !!value)
.map(([key]) => t(autoApproveSettingsConfig[key as AutoApproveSetting].labelKey))
.join(", ")

// Individual checkbox handlers - each one only updates its own state.
const handleReadOnlyChange = useCallback(() => {
const newValue = !(alwaysAllowReadOnly ?? false)
setAlwaysAllowReadOnly(newValue)
vscode.postMessage({ type: "alwaysAllowReadOnly", bool: newValue })
}, [alwaysAllowReadOnly, setAlwaysAllowReadOnly])

const handleWriteChange = useCallback(() => {
const newValue = !(alwaysAllowWrite ?? false)
setAlwaysAllowWrite(newValue)
vscode.postMessage({ type: "alwaysAllowWrite", bool: newValue })
}, [alwaysAllowWrite, setAlwaysAllowWrite])

const handleExecuteChange = useCallback(() => {
const newValue = !(alwaysAllowExecute ?? false)
setAlwaysAllowExecute(newValue)
vscode.postMessage({ type: "alwaysAllowExecute", bool: newValue })
}, [alwaysAllowExecute, setAlwaysAllowExecute])

const handleBrowserChange = useCallback(() => {
const newValue = !(alwaysAllowBrowser ?? false)
setAlwaysAllowBrowser(newValue)
vscode.postMessage({ type: "alwaysAllowBrowser", bool: newValue })
}, [alwaysAllowBrowser, setAlwaysAllowBrowser])

const handleMcpChange = useCallback(() => {
const newValue = !(alwaysAllowMcp ?? false)
setAlwaysAllowMcp(newValue)
vscode.postMessage({ type: "alwaysAllowMcp", bool: newValue })
}, [alwaysAllowMcp, setAlwaysAllowMcp])

const handleModeSwitchChange = useCallback(() => {
const newValue = !(alwaysAllowModeSwitch ?? false)
setAlwaysAllowModeSwitch(newValue)
vscode.postMessage({ type: "alwaysAllowModeSwitch", bool: newValue })
}, [alwaysAllowModeSwitch, setAlwaysAllowModeSwitch])

const handleSubtasksChange = useCallback(() => {
const newValue = !(alwaysAllowSubtasks ?? false)
setAlwaysAllowSubtasks(newValue)
vscode.postMessage({ type: "alwaysAllowSubtasks", bool: newValue })
}, [alwaysAllowSubtasks, setAlwaysAllowSubtasks])

const handleRetryChange = useCallback(() => {
const newValue = !(alwaysApproveResubmit ?? false)
setAlwaysApproveResubmit(newValue)
vscode.postMessage({ type: "alwaysApproveResubmit", bool: newValue })
}, [alwaysApproveResubmit, setAlwaysApproveResubmit])

const handleOpenSettings = useCallback(() => {
window.postMessage({
type: "action",
action: "settingsButtonClicked",
values: { section: "autoApprove" },
})
}, [])

// Map action IDs to their specific handlers.
const actionHandlers: Record<AutoApproveAction["id"], () => void> = {
readFiles: handleReadOnlyChange,
editFiles: handleWriteChange,
executeCommands: handleExecuteChange,
useBrowser: handleBrowserChange,
useMcp: handleMcpChange,
switchModes: handleModeSwitchChange,
subtasks: handleSubtasksChange,
retryRequests: handleRetryChange,
}
const handleOpenSettings = useCallback(
() =>
window.postMessage({ type: "action", action: "settingsButtonClicked", values: { section: "autoApprove" } }),
[],
)

return (
<div
Expand Down Expand Up @@ -250,6 +181,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
/>
</div>
</div>

{isExpanded && (
<div className="flex flex-col gap-2">
<div
Expand All @@ -264,27 +196,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
}}
/>
</div>
<div className="grid grid-cols-2 [@media(min-width:320px)]:grid-cols-4 gap-2">
{actions.map((action) => {
const codicon = ICON_MAP[action.id] || "question"
return (
<Button
key={action.id}
variant={action.enabled ? "default" : "ghost"}
onClick={(e) => {
e.stopPropagation()
actionHandlers[action.id]()
}}
title={action.description}
className="h-12">
<span className="flex flex-col items-center gap-1">
<span className={`codicon codicon-${codicon}`} />
<span className="text-sm text-center">{action.label}</span>
</span>
</Button>
)
})}
</div>
<AutoApproveToggle {...toggles} onToggle={onAutoApproveToggle} />
</div>
)}
</div>
Expand Down
Loading