From e19492fb578ed7594c91cd2827eb9122b96eb504 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Tue, 26 Aug 2025 17:37:11 +0000 Subject: [PATCH 1/4] feat: redesign Auto-Approve UI with dropdown and two-column layout - Move AutoApproveMenu below text area with dropdown trigger similar to ModeSelector - Add Stamp icon for dropdown trigger - Implement text ellipsis for long text in dropdown trigger - Change to two-column layout for better organization - Add Select All/Select None functionality with Lucide icons (ListChecks, LayoutList) - Update tests to work with new dropdown UI --- .../src/components/chat/AutoApproveMenu.tsx | 210 ++++++++++-------- .../chat/AutoApproveToggleDropdown.tsx | 73 ++++++ webview-ui/src/components/chat/ChatView.tsx | 13 +- .../chat/__tests__/AutoApproveMenu.spec.tsx | 46 ++-- 4 files changed, 224 insertions(+), 118 deletions(-) create mode 100644 webview-ui/src/components/chat/AutoApproveToggleDropdown.tsx diff --git a/webview-ui/src/components/chat/AutoApproveMenu.tsx b/webview-ui/src/components/chat/AutoApproveMenu.tsx index 8961fc7f5d..b76601ea9e 100644 --- a/webview-ui/src/components/chat/AutoApproveMenu.tsx +++ b/webview-ui/src/components/chat/AutoApproveMenu.tsx @@ -1,14 +1,18 @@ import { memo, useCallback, useMemo, useState } from "react" import { Trans } from "react-i18next" import { VSCodeCheckbox, VSCodeLink } from "@vscode/webview-ui-toolkit/react" +import { Stamp, ListChecks, LayoutList } from "lucide-react" 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 { AutoApproveSetting, autoApproveSettingsConfig } from "../settings/AutoApproveToggle" +import { AutoApproveToggleDropdown } from "./AutoApproveToggleDropdown" +import { StandardTooltip, Popover, PopoverContent, PopoverTrigger } from "@src/components/ui" import { useAutoApprovalState } from "@src/hooks/useAutoApprovalState" import { useAutoApprovalToggles } from "@src/hooks/useAutoApprovalToggles" +import { cn } from "@src/lib/utils" +import { useRooPortal } from "@src/components/ui/hooks/useRooPortal" interface AutoApproveMenuProps { style?: React.CSSProperties @@ -16,6 +20,7 @@ interface AutoApproveMenuProps { const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { const [isExpanded, setIsExpanded] = useState(false) + const portalContainer = useRooPortal("roo-portal") const { autoApprovalEnabled, @@ -123,10 +128,6 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { ], ) - const toggleExpanded = useCallback(() => { - setIsExpanded((prev) => !prev) - }, []) - const enabledActionsList = Object.entries(toggles) .filter(([_key, value]) => !!value) .map(([key]) => t(autoApproveSettingsConfig[key as AutoApproveSetting].labelKey)) @@ -146,101 +147,118 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { [], ) + // Handler for Select All + const handleSelectAll = useCallback(() => { + const allSettings: AutoApproveSetting[] = Object.keys(toggles) as AutoApproveSetting[] + allSettings.forEach((key) => { + if (!toggles[key]) { + onAutoApproveToggle(key, true) + } + }) + }, [toggles, onAutoApproveToggle]) + + // Handler for Select None + const handleSelectNone = useCallback(() => { + const allSettings: AutoApproveSetting[] = Object.keys(toggles) as AutoApproveSetting[] + allSettings.forEach((key) => { + if (toggles[key]) { + onAutoApproveToggle(key, false) + } + }) + }, [toggles, onAutoApproveToggle]) + + const trigger = ( + + + {t("chat:autoApprove.title")} + {displayText} + + ) + return ( -
- {isExpanded && ( -
-
- , - }} - /> + + {trigger} + + +
+ {/* Header with master toggle */} +
+
+ + { + if (hasEnabledOptions) { + const newValue = !(autoApprovalEnabled ?? false) + setAutoApprovalEnabled(newValue) + vscode.postMessage({ type: "autoApprovalEnabled", bool: newValue }) + } + }} + /> + +

{t("chat:autoApprove.title")}

+
+
+ + + + + + +
- -
- )} + {/* Description */} +
+
+ , + }} + /> +
+
-
-
e.stopPropagation()}> - - { - if (hasEnabledOptions) { - const newValue = !(autoApprovalEnabled ?? false) - setAutoApprovalEnabled(newValue) - vscode.postMessage({ type: "autoApprovalEnabled", bool: newValue }) - } - // If no options enabled, do nothing - }} - /> - -
-
- - {t("chat:autoApprove.title")} - - - {displayText} - - + {/* Two-column layout for toggles */} +
+
+ +
+
-
-
+ + ) } diff --git a/webview-ui/src/components/chat/AutoApproveToggleDropdown.tsx b/webview-ui/src/components/chat/AutoApproveToggleDropdown.tsx new file mode 100644 index 0000000000..279194593a --- /dev/null +++ b/webview-ui/src/components/chat/AutoApproveToggleDropdown.tsx @@ -0,0 +1,73 @@ +import type { GlobalSettings } from "@roo-code/types" +import { useAppTranslation } from "@/i18n/TranslationContext" +import { cn } from "@/lib/utils" +import { StandardTooltip } from "@/components/ui" +import { autoApproveSettingsConfig, AutoApproveSetting } from "../settings/AutoApproveToggle" + +type AutoApproveToggles = Pick< + GlobalSettings, + | "alwaysAllowReadOnly" + | "alwaysAllowWrite" + | "alwaysAllowBrowser" + | "alwaysApproveResubmit" + | "alwaysAllowMcp" + | "alwaysAllowModeSwitch" + | "alwaysAllowSubtasks" + | "alwaysAllowExecute" + | "alwaysAllowFollowupQuestions" + | "alwaysAllowUpdateTodoList" +> + +type AutoApproveToggleDropdownProps = AutoApproveToggles & { + onToggle: (key: AutoApproveSetting, value: boolean) => void +} + +export const AutoApproveToggleDropdown = ({ onToggle, ...props }: AutoApproveToggleDropdownProps) => { + const { t } = useAppTranslation() + + // Split settings into two columns for better layout + const settings = Object.values(autoApproveSettingsConfig) + const halfLength = Math.ceil(settings.length / 2) + const leftColumn = settings.slice(0, halfLength) + const rightColumn = settings.slice(halfLength) + + const renderToggleItem = ({ + key, + descriptionKey, + labelKey, + icon, + testId, + }: (typeof autoApproveSettingsConfig)[AutoApproveSetting]) => ( + + + + ) + + return ( + <> +
{leftColumn.map(renderToggleItem)}
+
{rightColumn.map(renderToggleItem)}
+ + ) +} diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 44eeb33b66..3cd10138e8 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -1882,11 +1882,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction - -
- )} {task && ( <> @@ -1909,9 +1904,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction
-
- -
{areButtonsVisible && (
+ {/* Auto-Approve Menu positioned below text area */} +
+ +
+ {isProfileDisabled && (
diff --git a/webview-ui/src/components/chat/__tests__/AutoApproveMenu.spec.tsx b/webview-ui/src/components/chat/__tests__/AutoApproveMenu.spec.tsx index 185e5eeec6..0c185fec4d 100644 --- a/webview-ui/src/components/chat/__tests__/AutoApproveMenu.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/AutoApproveMenu.spec.tsx @@ -115,7 +115,7 @@ describe("AutoApproveMenu", () => { expect(screen.getByText("Read-only operations")).toBeInTheDocument() }) - it("should not allow toggling master checkbox when no options are selected", () => { + it("should not allow toggling master checkbox when no options are selected", async () => { ;(useExtensionState as ReturnType).mockReturnValue({ ...defaultExtensionState, autoApprovalEnabled: false, @@ -124,6 +124,15 @@ describe("AutoApproveMenu", () => { render() + // Click to open the dropdown + const trigger = screen.getByText("Auto-approve") + fireEvent.click(trigger) + + // Wait for the dropdown to open + await waitFor(() => { + expect(screen.getByRole("checkbox")).toBeInTheDocument() + }) + // Click on the master checkbox const masterCheckbox = screen.getByRole("checkbox") fireEvent.click(masterCheckbox) @@ -132,7 +141,7 @@ describe("AutoApproveMenu", () => { expect(mockPostMessage).not.toHaveBeenCalled() }) - it("should toggle master checkbox when options are selected", () => { + it("should toggle master checkbox when options are selected", async () => { ;(useExtensionState as ReturnType).mockReturnValue({ ...defaultExtensionState, autoApprovalEnabled: true, @@ -141,6 +150,15 @@ describe("AutoApproveMenu", () => { render() + // Click to open the dropdown + const trigger = screen.getByText("Auto-approve") + fireEvent.click(trigger) + + // Wait for the dropdown to open + await waitFor(() => { + expect(screen.getByRole("checkbox")).toBeInTheDocument() + }) + // Click on the master checkbox const masterCheckbox = screen.getByRole("checkbox") fireEvent.click(masterCheckbox) @@ -164,9 +182,9 @@ describe("AutoApproveMenu", () => { render() - // Expand the menu - const menuContainer = screen.getByText("Auto-approve").parentElement - fireEvent.click(menuContainer!) + // Click to open the dropdown + const trigger = screen.getByText("Auto-approve") + fireEvent.click(trigger) // Wait for the menu to expand and find the read-only button await waitFor(() => { @@ -192,9 +210,9 @@ describe("AutoApproveMenu", () => { render() - // Expand the menu - const menuContainer = screen.getByText("Auto-approve").parentElement - fireEvent.click(menuContainer!) + // Click to open the dropdown + const trigger = screen.getByText("Auto-approve") + fireEvent.click(trigger) await waitFor(() => { expect(screen.getByTestId("always-allow-write-toggle")).toBeInTheDocument() @@ -240,9 +258,9 @@ describe("AutoApproveMenu", () => { render() - // Expand the menu - const menuContainer = screen.getByText("Auto-approve").parentElement - fireEvent.click(menuContainer!) + // Click to open the dropdown + const trigger = screen.getByText("Auto-approve") + fireEvent.click(trigger) await waitFor(() => { expect(screen.getByTestId("always-allow-readonly-toggle")).toBeInTheDocument() @@ -279,9 +297,9 @@ describe("AutoApproveMenu", () => { render() - // Expand the menu - const menuContainer = screen.getByText("Auto-approve").parentElement - fireEvent.click(menuContainer!) + // Click to open the dropdown + const trigger = screen.getByText("Auto-approve") + fireEvent.click(trigger) await waitFor(() => { expect(screen.getByTestId("always-allow-readonly-toggle")).toBeInTheDocument() From 67d9d14549aec2fd3f3e4917eaa1dce41cf13bd4 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Tue, 26 Aug 2025 17:40:18 +0000 Subject: [PATCH 2/4] feat: add keyboard shortcuts for auto-approve selection - Add Alt+1 through Alt+0 keyboard shortcuts to toggle auto-approve options - Display keyboard shortcuts in tooltips for each option - Create AutoApproveKeyboardShortcuts component to handle global keyboard events --- .../chat/AutoApproveKeyboardShortcuts.tsx | 121 ++++++++++++++++++ .../chat/AutoApproveToggleDropdown.tsx | 65 ++++++---- webview-ui/src/components/chat/ChatView.tsx | 2 + 3 files changed, 164 insertions(+), 24 deletions(-) create mode 100644 webview-ui/src/components/chat/AutoApproveKeyboardShortcuts.tsx diff --git a/webview-ui/src/components/chat/AutoApproveKeyboardShortcuts.tsx b/webview-ui/src/components/chat/AutoApproveKeyboardShortcuts.tsx new file mode 100644 index 0000000000..ada19a8fe9 --- /dev/null +++ b/webview-ui/src/components/chat/AutoApproveKeyboardShortcuts.tsx @@ -0,0 +1,121 @@ +import { useEffect, useCallback, useMemo } from "react" +import { useExtensionState } from "@src/context/ExtensionStateContext" +import { vscode } from "@src/utils/vscode" +import { AutoApproveSetting } from "../settings/AutoApproveToggle" +import { useAutoApprovalToggles } from "@src/hooks/useAutoApprovalToggles" + +// Keyboard shortcuts mapping for auto-approve options +const KEYBOARD_SHORTCUTS: Record = { + "1": "alwaysAllowReadOnly", + "2": "alwaysAllowWrite", + "3": "alwaysAllowBrowser", + "4": "alwaysAllowExecute", + "5": "alwaysAllowMcp", + "6": "alwaysAllowModeSwitch", + "7": "alwaysAllowSubtasks", + "8": "alwaysAllowFollowupQuestions", + "9": "alwaysAllowUpdateTodoList", + "0": "alwaysApproveResubmit", +} + +export const AutoApproveKeyboardShortcuts = () => { + const { + setAlwaysAllowReadOnly, + setAlwaysAllowWrite, + setAlwaysAllowExecute, + setAlwaysAllowBrowser, + setAlwaysAllowMcp, + setAlwaysAllowModeSwitch, + setAlwaysAllowSubtasks, + setAlwaysApproveResubmit, + setAlwaysAllowFollowupQuestions, + setAlwaysAllowUpdateTodoList, + alwaysApproveResubmit, + } = useExtensionState() + + const baseToggles = useAutoApprovalToggles() + const toggles = useMemo( + () => ({ + ...baseToggles, + alwaysApproveResubmit, + }), + [baseToggles, alwaysApproveResubmit], + ) + + const handleToggle = useCallback( + (key: AutoApproveSetting) => { + const currentValue = toggles[key] + const newValue = !currentValue + + // Send message to extension + vscode.postMessage({ type: key, bool: newValue }) + + // Update local state + switch (key) { + case "alwaysAllowReadOnly": + setAlwaysAllowReadOnly(newValue) + break + case "alwaysAllowWrite": + setAlwaysAllowWrite(newValue) + break + case "alwaysAllowExecute": + setAlwaysAllowExecute(newValue) + break + case "alwaysAllowBrowser": + setAlwaysAllowBrowser(newValue) + break + case "alwaysAllowMcp": + setAlwaysAllowMcp(newValue) + break + case "alwaysAllowModeSwitch": + setAlwaysAllowModeSwitch(newValue) + break + case "alwaysAllowSubtasks": + setAlwaysAllowSubtasks(newValue) + break + case "alwaysApproveResubmit": + setAlwaysApproveResubmit(newValue) + break + case "alwaysAllowFollowupQuestions": + setAlwaysAllowFollowupQuestions(newValue) + break + case "alwaysAllowUpdateTodoList": + setAlwaysAllowUpdateTodoList(newValue) + break + } + }, + [ + toggles, + setAlwaysAllowReadOnly, + setAlwaysAllowWrite, + setAlwaysAllowExecute, + setAlwaysAllowBrowser, + setAlwaysAllowMcp, + setAlwaysAllowModeSwitch, + setAlwaysAllowSubtasks, + setAlwaysApproveResubmit, + setAlwaysAllowFollowupQuestions, + setAlwaysAllowUpdateTodoList, + ], + ) + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + // Check if Alt/Option key is pressed along with a number key + if (event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) { + const shortcut = KEYBOARD_SHORTCUTS[event.key] + if (shortcut) { + event.preventDefault() + handleToggle(shortcut) + } + } + } + + window.addEventListener("keydown", handleKeyDown) + return () => { + window.removeEventListener("keydown", handleKeyDown) + } + }, [handleToggle]) + + return null // This component doesn't render anything +} diff --git a/webview-ui/src/components/chat/AutoApproveToggleDropdown.tsx b/webview-ui/src/components/chat/AutoApproveToggleDropdown.tsx index 279194593a..4d13846350 100644 --- a/webview-ui/src/components/chat/AutoApproveToggleDropdown.tsx +++ b/webview-ui/src/components/chat/AutoApproveToggleDropdown.tsx @@ -22,6 +22,20 @@ type AutoApproveToggleDropdownProps = AutoApproveToggles & { onToggle: (key: AutoApproveSetting, value: boolean) => void } +// Keyboard shortcuts mapping +const KEYBOARD_SHORTCUTS: Record = { + alwaysAllowReadOnly: "Alt+1", + alwaysAllowWrite: "Alt+2", + alwaysAllowBrowser: "Alt+3", + alwaysAllowExecute: "Alt+4", + alwaysAllowMcp: "Alt+5", + alwaysAllowModeSwitch: "Alt+6", + alwaysAllowSubtasks: "Alt+7", + alwaysAllowFollowupQuestions: "Alt+8", + alwaysAllowUpdateTodoList: "Alt+9", + alwaysApproveResubmit: "Alt+0", +} + export const AutoApproveToggleDropdown = ({ onToggle, ...props }: AutoApproveToggleDropdownProps) => { const { t } = useAppTranslation() @@ -37,32 +51,35 @@ export const AutoApproveToggleDropdown = ({ onToggle, ...props }: AutoApproveTog labelKey, icon, testId, - }: (typeof autoApproveSettingsConfig)[AutoApproveSetting]) => ( - - - - ) + + {t(labelKey)} + + {props[key] ? "✓" : ""} + + + + ) + } return ( <> diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 3cd10138e8..53eb79bcd7 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -51,6 +51,7 @@ import ChatRow from "./ChatRow" import ChatTextArea from "./ChatTextArea" import TaskHeader from "./TaskHeader" import AutoApproveMenu from "./AutoApproveMenu" +import { AutoApproveKeyboardShortcuts } from "./AutoApproveKeyboardShortcuts" import SystemPromptWarning from "./SystemPromptWarning" import ProfileViolationWarning from "./ProfileViolationWarning" import { CheckpointWarning } from "./CheckpointWarning" @@ -1793,6 +1794,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction + {(showAnnouncement || showAnnouncementModal) && ( { From 31fd2b9b15d7725c4694b169a8fa74dd486d7f63 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Tue, 26 Aug 2025 18:17:33 +0000 Subject: [PATCH 3/4] refactor: address PR review feedback for auto-approve keyboard shortcuts - Extract KEYBOARD_SHORTCUTS to shared constants file to avoid duplication - Fix event listener cleanup using useRef and stable callback to prevent memory leaks - Add configuration support for keyboard shortcuts (enabled/disabled, Alt vs Ctrl+Shift) - Add comprehensive tests for keyboard shortcut functionality - Improve code organization and maintainability --- .../chat/AutoApproveKeyboardShortcuts.tsx | 51 +-- .../chat/AutoApproveToggleDropdown.tsx | 24 +- .../AutoApproveKeyboardShortcuts.spec.tsx | 292 ++++++++++++++++++ .../src/constants/autoApproveConstants.ts | 55 ++++ 4 files changed, 383 insertions(+), 39 deletions(-) create mode 100644 webview-ui/src/components/chat/__tests__/AutoApproveKeyboardShortcuts.spec.tsx create mode 100644 webview-ui/src/constants/autoApproveConstants.ts diff --git a/webview-ui/src/components/chat/AutoApproveKeyboardShortcuts.tsx b/webview-ui/src/components/chat/AutoApproveKeyboardShortcuts.tsx index ada19a8fe9..8b6f8c2fc7 100644 --- a/webview-ui/src/components/chat/AutoApproveKeyboardShortcuts.tsx +++ b/webview-ui/src/components/chat/AutoApproveKeyboardShortcuts.tsx @@ -1,22 +1,9 @@ -import { useEffect, useCallback, useMemo } from "react" +import { useEffect, useCallback, useMemo, useRef } from "react" import { useExtensionState } from "@src/context/ExtensionStateContext" import { vscode } from "@src/utils/vscode" import { AutoApproveSetting } from "../settings/AutoApproveToggle" import { useAutoApprovalToggles } from "@src/hooks/useAutoApprovalToggles" - -// Keyboard shortcuts mapping for auto-approve options -const KEYBOARD_SHORTCUTS: Record = { - "1": "alwaysAllowReadOnly", - "2": "alwaysAllowWrite", - "3": "alwaysAllowBrowser", - "4": "alwaysAllowExecute", - "5": "alwaysAllowMcp", - "6": "alwaysAllowModeSwitch", - "7": "alwaysAllowSubtasks", - "8": "alwaysAllowFollowupQuestions", - "9": "alwaysAllowUpdateTodoList", - "0": "alwaysApproveResubmit", -} +import { KEYBOARD_SHORTCUTS, DEFAULT_KEYBOARD_CONFIG } from "@src/constants/autoApproveConstants" export const AutoApproveKeyboardShortcuts = () => { const { @@ -99,23 +86,39 @@ export const AutoApproveKeyboardShortcuts = () => { ], ) + // Store the handleToggle function in a ref to avoid re-registrations + const handleToggleRef = useRef(handleToggle) useEffect(() => { - const handleKeyDown = (event: KeyboardEvent) => { - // Check if Alt/Option key is pressed along with a number key - if (event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) { - const shortcut = KEYBOARD_SHORTCUTS[event.key] - if (shortcut) { - event.preventDefault() - handleToggle(shortcut) - } + handleToggleRef.current = handleToggle + }, [handleToggle]) + + // Stable event handler that uses the ref + const handleKeyDown = useCallback((event: KeyboardEvent) => { + // Check if keyboard shortcuts are enabled + if (!DEFAULT_KEYBOARD_CONFIG.enabled) { + return + } + + // Support both Alt key and Ctrl+Shift key combinations based on configuration + const isValidModifier = DEFAULT_KEYBOARD_CONFIG.useCtrlShiftKey + ? event.ctrlKey && event.shiftKey && !event.altKey && !event.metaKey + : event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey + + if (isValidModifier) { + const shortcut = KEYBOARD_SHORTCUTS[event.key] + if (shortcut) { + event.preventDefault() + handleToggleRef.current(shortcut) } } + }, []) + useEffect(() => { window.addEventListener("keydown", handleKeyDown) return () => { window.removeEventListener("keydown", handleKeyDown) } - }, [handleToggle]) + }, [handleKeyDown]) return null // This component doesn't render anything } diff --git a/webview-ui/src/components/chat/AutoApproveToggleDropdown.tsx b/webview-ui/src/components/chat/AutoApproveToggleDropdown.tsx index 4d13846350..21a041f6ec 100644 --- a/webview-ui/src/components/chat/AutoApproveToggleDropdown.tsx +++ b/webview-ui/src/components/chat/AutoApproveToggleDropdown.tsx @@ -3,6 +3,7 @@ import { useAppTranslation } from "@/i18n/TranslationContext" import { cn } from "@/lib/utils" import { StandardTooltip } from "@/components/ui" import { autoApproveSettingsConfig, AutoApproveSetting } from "../settings/AutoApproveToggle" +import { KEYBOARD_SHORTCUTS_DISPLAY, DEFAULT_KEYBOARD_CONFIG } from "@/constants/autoApproveConstants" type AutoApproveToggles = Pick< GlobalSettings, @@ -22,20 +23,6 @@ type AutoApproveToggleDropdownProps = AutoApproveToggles & { onToggle: (key: AutoApproveSetting, value: boolean) => void } -// Keyboard shortcuts mapping -const KEYBOARD_SHORTCUTS: Record = { - alwaysAllowReadOnly: "Alt+1", - alwaysAllowWrite: "Alt+2", - alwaysAllowBrowser: "Alt+3", - alwaysAllowExecute: "Alt+4", - alwaysAllowMcp: "Alt+5", - alwaysAllowModeSwitch: "Alt+6", - alwaysAllowSubtasks: "Alt+7", - alwaysAllowFollowupQuestions: "Alt+8", - alwaysAllowUpdateTodoList: "Alt+9", - alwaysApproveResubmit: "Alt+0", -} - export const AutoApproveToggleDropdown = ({ onToggle, ...props }: AutoApproveToggleDropdownProps) => { const { t } = useAppTranslation() @@ -52,7 +39,14 @@ export const AutoApproveToggleDropdown = ({ onToggle, ...props }: AutoApproveTog icon, testId, }: (typeof autoApproveSettingsConfig)[AutoApproveSetting]) => { - const tooltipContent = `${t(descriptionKey || "")} (${KEYBOARD_SHORTCUTS[key]})` + // Get the appropriate keyboard shortcut display based on configuration + const shortcutDisplay = DEFAULT_KEYBOARD_CONFIG.useCtrlShiftKey + ? KEYBOARD_SHORTCUTS_DISPLAY[key].replace("Alt+", "Ctrl+Shift+") + : KEYBOARD_SHORTCUTS_DISPLAY[key] + + const tooltipContent = DEFAULT_KEYBOARD_CONFIG.enabled + ? `${t(descriptionKey || "")} (${shortcutDisplay})` + : t(descriptionKey || "") return ( - - - - -
-
- - {/* Description */} -
+ {/* Header */} +
+

+ {t("chat:autoApprove.title")} +

{
+ + {/* Footer with buttons on left and title on right */} +
+
+ + +
+
diff --git a/webview-ui/src/components/chat/AutoApproveToggleDropdown.tsx b/webview-ui/src/components/chat/AutoApproveToggleDropdown.tsx index 21a041f6ec..cb0909ac70 100644 --- a/webview-ui/src/components/chat/AutoApproveToggleDropdown.tsx +++ b/webview-ui/src/components/chat/AutoApproveToggleDropdown.tsx @@ -56,20 +56,22 @@ export const AutoApproveToggleDropdown = ({ onToggle, ...props }: AutoApproveTog data-testid={testId} className={cn( "w-full flex items-center gap-2 px-2 py-1.5 rounded text-xs text-left", - "transition-colors hover:bg-vscode-list-hoverBackground", + "transition-colors hover:bg-vscode-list-hoverBackground cursor-pointer", props[key] ? "bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground" : "opacity-70", )}> {t(labelKey)} - - {props[key] ? "✓" : ""} - + {DEFAULT_KEYBOARD_CONFIG.enabled && ( + + {shortcutDisplay.replace("Alt+", "⌥").replace("Ctrl+Shift+", "⌃⇧")} + + )} ) diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 5135eca2f2..17f4cfa178 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -32,6 +32,7 @@ import { SlashCommandsPopover } from "./SlashCommandsPopover" import { cn } from "@/lib/utils" import { usePromptHistory } from "./hooks/usePromptHistory" import { EditModeControls } from "./EditModeControls" +import AutoApproveMenu from "./AutoApproveMenu" interface ChatTextAreaProps { inputValue: string @@ -917,31 +918,58 @@ const ChatTextArea = forwardRef( // Helper function to render non-edit mode controls const renderNonEditModeControls = () => ( -
-
-
{renderModeSelector()}
- -
- +
+
+
+
{renderModeSelector()}
+ +
+ +
+ +
+ +
-
-
- {isTtsPlaying && ( - +
+ {isTtsPlaying && ( + + + + )} + + + - )} - - - - - +
) diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 53eb79bcd7..46aada42b4 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -50,7 +50,6 @@ import BrowserSessionRow from "./BrowserSessionRow" import ChatRow from "./ChatRow" import ChatTextArea from "./ChatTextArea" import TaskHeader from "./TaskHeader" -import AutoApproveMenu from "./AutoApproveMenu" import { AutoApproveKeyboardShortcuts } from "./AutoApproveKeyboardShortcuts" import SystemPromptWarning from "./SystemPromptWarning" import ProfileViolationWarning from "./ProfileViolationWarning" @@ -2019,11 +2018,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction - {/* Auto-Approve Menu positioned below text area */} -
- -
- {isProfileDisabled && (
diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index ff93fa6e08..0a364001e3 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -265,9 +265,10 @@ "issues": "It seems like you're having Windows PowerShell issues, please see this" }, "autoApprove": { - "title": "Auto-approve:", + "title": "Auto-Approve", + "dropdownTitle": "Auto:", "none": "None", - "description": "Auto-approve allows Roo Code to perform actions without asking for permission. Only enable for actions you fully trust. More detailed configuration available in Settings.", + "description": "Run these actions without asking for permission.Only enable for actions you fully trust. More in Settings.", "selectOptionsFirst": "Select at least one option below to enable auto-approval", "toggleAriaLabel": "Toggle auto-approval", "disabledAriaLabel": "Auto-approval disabled - select options first"