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
121 changes: 76 additions & 45 deletions webview-ui/src/components/chat/AutoApproveMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { vscode } from "@src/utils/vscode"
import { useExtensionState } from "@src/context/ExtensionStateContext"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { AutoApproveToggle, AutoApproveSetting, autoApproveSettingsConfig } from "../settings/AutoApproveToggle"
import { StandardTooltip } from "@src/components/ui"
import { useAutoApprovalState } from "@src/hooks/useAutoApprovalState"
import { useAutoApprovalToggles } from "@src/hooks/useAutoApprovalToggles"

interface AutoApproveMenuProps {
style?: React.CSSProperties
Expand All @@ -17,16 +20,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
const {
autoApprovalEnabled,
setAutoApprovalEnabled,
alwaysAllowReadOnly,
alwaysAllowWrite,
alwaysAllowExecute,
alwaysAllowBrowser,
alwaysAllowMcp,
alwaysAllowModeSwitch,
alwaysAllowSubtasks,
alwaysApproveResubmit,
alwaysAllowFollowupQuestions,
alwaysAllowUpdateTodoList,
allowedMaxRequests,
setAlwaysAllowReadOnly,
setAlwaysAllowWrite,
Expand All @@ -43,10 +37,24 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {

const { t } = useAppTranslation()

const baseToggles = useAutoApprovalToggles()

// AutoApproveMenu needs alwaysApproveResubmit in addition to the base toggles
const toggles = useMemo(
() => ({
...baseToggles,
alwaysApproveResubmit: alwaysApproveResubmit,
}),
[baseToggles, alwaysApproveResubmit],
)

const { hasEnabledOptions, effectiveAutoApprovalEnabled } = useAutoApprovalState(toggles, autoApprovalEnabled)

const onAutoApproveToggle = useCallback(
(key: AutoApproveSetting, value: boolean) => {
vscode.postMessage({ type: key, bool: value })

// Update the specific toggle state
switch (key) {
case "alwaysAllowReadOnly":
setAlwaysAllowReadOnly(value)
Expand Down Expand Up @@ -79,8 +87,30 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
setAlwaysAllowUpdateTodoList(value)
break
}

// Check if we need to update the master auto-approval state
// Create a new toggles state with the updated value
const updatedToggles = {
...toggles,
[key]: value,
}

const willHaveEnabledOptions = Object.values(updatedToggles).some((v) => !!v)

// If enabling the first option, enable master auto-approval
if (value && !hasEnabledOptions && willHaveEnabledOptions) {
setAutoApprovalEnabled(true)
vscode.postMessage({ type: "autoApprovalEnabled", bool: true })
}
// If disabling the last option, disable master auto-approval
else if (!value && hasEnabledOptions && !willHaveEnabledOptions) {
setAutoApprovalEnabled(false)
vscode.postMessage({ type: "autoApprovalEnabled", bool: false })
}
},
[
toggles,
hasEnabledOptions,
setAlwaysAllowReadOnly,
setAlwaysAllowWrite,
setAlwaysAllowExecute,
Expand All @@ -91,43 +121,32 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
setAlwaysApproveResubmit,
setAlwaysAllowFollowupQuestions,
setAlwaysAllowUpdateTodoList,
setAutoApprovalEnabled,
],
)

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,
alwaysAllowFollowupQuestions: alwaysAllowFollowupQuestions,
alwaysAllowUpdateTodoList: alwaysAllowUpdateTodoList,
}),
[
alwaysAllowReadOnly,
alwaysAllowWrite,
alwaysAllowExecute,
alwaysAllowBrowser,
alwaysAllowMcp,
alwaysAllowModeSwitch,
alwaysAllowSubtasks,
alwaysApproveResubmit,
alwaysAllowFollowupQuestions,
alwaysAllowUpdateTodoList,
],
)
// Disable main checkbox while menu is open or no options selected
const isCheckboxDisabled = useMemo(() => {
return !hasEnabledOptions || isExpanded
}, [hasEnabledOptions, isExpanded])

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

// Update displayed text logic
const displayText = useMemo(() => {
if (!effectiveAutoApprovalEnabled || !hasEnabledOptions) {
return t("chat:autoApprove.none")
}
return enabledActionsList || t("chat:autoApprove.none")
}, [effectiveAutoApprovalEnabled, hasEnabledOptions, enabledActionsList, t])

const handleOpenSettings = useCallback(
() =>
window.postMessage({ type: "action", action: "settingsButtonClicked", values: { section: "autoApprove" } }),
Expand Down Expand Up @@ -155,14 +174,26 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
}}
onClick={toggleExpanded}>
<div onClick={(e) => e.stopPropagation()}>
<VSCodeCheckbox
checked={autoApprovalEnabled ?? false}
onChange={() => {
const newValue = !(autoApprovalEnabled ?? false)
setAutoApprovalEnabled(newValue)
vscode.postMessage({ type: "autoApprovalEnabled", bool: newValue })
}}
/>
<StandardTooltip
content={!hasEnabledOptions ? t("chat:autoApprove.selectOptionsFirst") : undefined}>
<VSCodeCheckbox
checked={effectiveAutoApprovalEnabled}
disabled={isCheckboxDisabled}
aria-label={
hasEnabledOptions
? t("chat:autoApprove.toggleAriaLabel")
: t("chat:autoApprove.disabledAriaLabel")
}
onChange={() => {
if (hasEnabledOptions) {
const newValue = !(autoApprovalEnabled ?? false)
setAutoApprovalEnabled(newValue)
vscode.postMessage({ type: "autoApprovalEnabled", bool: newValue })
}
// If no options enabled, do nothing
}}
/>
</StandardTooltip>
</div>
<div
style={{
Expand All @@ -188,7 +219,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
flex: 1,
minWidth: 0,
}}>
{enabledActionsList || t("chat:autoApprove.none")}
{displayText}
</span>
<span
className={`codicon codicon-chevron-${isExpanded ? "down" : "right"}`}
Expand Down
14 changes: 14 additions & 0 deletions webview-ui/src/components/chat/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import { useSelectedModel } from "@src/components/ui/hooks/useSelectedModel"
import RooHero from "@src/components/welcome/RooHero"
import RooTips from "@src/components/welcome/RooTips"
import { StandardTooltip } from "@src/components/ui"
import { useAutoApprovalState } from "@src/hooks/useAutoApprovalState"
import { useAutoApprovalToggles } from "@src/hooks/useAutoApprovalToggles"

import TelemetryBanner from "../common/TelemetryBanner"
import VersionIndicator from "../common/VersionIndicator"
Expand Down Expand Up @@ -959,12 +961,23 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
[deniedCommands],
)

// Create toggles object for useAutoApprovalState hook
const autoApprovalToggles = useAutoApprovalToggles()

const { hasEnabledOptions } = useAutoApprovalState(autoApprovalToggles, autoApprovalEnabled)

const isAutoApproved = useCallback(
(message: ClineMessage | undefined) => {
// First check if auto-approval is enabled AND we have at least one permission
if (!autoApprovalEnabled || !message || message.type !== "ask") {
return false
}

// Use the hook's result instead of duplicating the logic
if (!hasEnabledOptions) {
return false
}

if (message.ask === "followup") {
return alwaysAllowFollowupQuestions
}
Expand Down Expand Up @@ -1038,6 +1051,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
},
[
autoApprovalEnabled,
hasEnabledOptions,
alwaysAllowBrowser,
alwaysAllowReadOnly,
alwaysAllowReadOnlyOutsideWorkspace,
Expand Down
Loading