diff --git a/webview-ui/src/components/chat/ApiConfigSelector.tsx b/webview-ui/src/components/chat/ApiConfigSelector.tsx
index 6d3f458b347c..653bfc39e54b 100644
--- a/webview-ui/src/components/chat/ApiConfigSelector.tsx
+++ b/webview-ui/src/components/chat/ApiConfigSelector.tsx
@@ -149,7 +149,7 @@ export const ApiConfigSelector = ({
disabled={disabled}
data-testid="dropdown-trigger"
className={cn(
- "w-full min-w-0 max-w-full inline-flex items-center gap-1.5 relative whitespace-nowrap px-1.5 py-1 text-xs",
+ "min-w-0 inline-flex items-center gap-1.5 relative whitespace-nowrap px-1.5 py-1 text-xs",
"bg-transparent border border-[rgba(255,255,255,0.08)] rounded-md text-vscode-foreground",
"transition-all duration-150 focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder focus-visible:ring-inset",
disabled
diff --git a/webview-ui/src/components/chat/AutoApproveDropdown.tsx b/webview-ui/src/components/chat/AutoApproveDropdown.tsx
new file mode 100644
index 000000000000..b3176d187c3d
--- /dev/null
+++ b/webview-ui/src/components/chat/AutoApproveDropdown.tsx
@@ -0,0 +1,305 @@
+import React from "react"
+import { ListChecks, LayoutList, Settings, CheckCheck } from "lucide-react"
+
+import { vscode } from "@/utils/vscode"
+import { cn } from "@/lib/utils"
+import { useExtensionState } from "@/context/ExtensionStateContext"
+import { useAppTranslation } from "@/i18n/TranslationContext"
+import { useRooPortal } from "@/components/ui/hooks/useRooPortal"
+import { Popover, PopoverContent, PopoverTrigger, StandardTooltip } from "@/components/ui"
+import { AutoApproveSetting, autoApproveSettingsConfig } from "../settings/AutoApproveToggle"
+import { useAutoApprovalToggles } from "@/hooks/useAutoApprovalToggles"
+
+interface AutoApproveDropdownProps {
+ disabled?: boolean
+ triggerClassName?: string
+}
+
+export const AutoApproveDropdown = ({ disabled = false, triggerClassName = "" }: AutoApproveDropdownProps) => {
+ const [open, setOpen] = React.useState(false)
+ const portalContainer = useRooPortal("roo-portal")
+ const { t } = useAppTranslation()
+
+ const {
+ autoApprovalEnabled,
+ setAutoApprovalEnabled,
+ alwaysApproveResubmit,
+ setAlwaysAllowReadOnly,
+ setAlwaysAllowWrite,
+ setAlwaysAllowExecute,
+ setAlwaysAllowBrowser,
+ setAlwaysAllowMcp,
+ setAlwaysAllowModeSwitch,
+ setAlwaysAllowSubtasks,
+ setAlwaysApproveResubmit,
+ setAlwaysAllowFollowupQuestions,
+ setAlwaysAllowUpdateTodoList,
+ } = useExtensionState()
+
+ const baseToggles = useAutoApprovalToggles()
+
+ // Include alwaysApproveResubmit in addition to the base toggles
+ const toggles = React.useMemo(
+ () => ({
+ ...baseToggles,
+ alwaysApproveResubmit: alwaysApproveResubmit,
+ }),
+ [baseToggles, alwaysApproveResubmit],
+ )
+
+ const onAutoApproveToggle = React.useCallback(
+ (key: AutoApproveSetting, value: boolean) => {
+ vscode.postMessage({ type: key, bool: value })
+
+ // Update the specific toggle state
+ 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
+ case "alwaysAllowFollowupQuestions":
+ setAlwaysAllowFollowupQuestions(value)
+ break
+ case "alwaysAllowUpdateTodoList":
+ setAlwaysAllowUpdateTodoList(value)
+ break
+ }
+
+ // If enabling any option, ensure autoApprovalEnabled is true
+ if (value && !autoApprovalEnabled) {
+ setAutoApprovalEnabled(true)
+ vscode.postMessage({ type: "autoApprovalEnabled", bool: true })
+ }
+ },
+ [
+ autoApprovalEnabled,
+ setAlwaysAllowReadOnly,
+ setAlwaysAllowWrite,
+ setAlwaysAllowExecute,
+ setAlwaysAllowBrowser,
+ setAlwaysAllowMcp,
+ setAlwaysAllowModeSwitch,
+ setAlwaysAllowSubtasks,
+ setAlwaysApproveResubmit,
+ setAlwaysAllowFollowupQuestions,
+ setAlwaysAllowUpdateTodoList,
+ setAutoApprovalEnabled,
+ ],
+ )
+
+ const handleSelectAll = React.useCallback(() => {
+ // Enable all options
+ Object.keys(autoApproveSettingsConfig).forEach((key) => {
+ onAutoApproveToggle(key as AutoApproveSetting, true)
+ })
+ // Enable master auto-approval
+ if (!autoApprovalEnabled) {
+ setAutoApprovalEnabled(true)
+ vscode.postMessage({ type: "autoApprovalEnabled", bool: true })
+ }
+ }, [onAutoApproveToggle, autoApprovalEnabled, setAutoApprovalEnabled])
+
+ const handleSelectNone = React.useCallback(() => {
+ // Disable all options
+ Object.keys(autoApproveSettingsConfig).forEach((key) => {
+ onAutoApproveToggle(key as AutoApproveSetting, false)
+ })
+ // Disable master auto-approval
+ if (autoApprovalEnabled) {
+ setAutoApprovalEnabled(false)
+ vscode.postMessage({ type: "autoApprovalEnabled", bool: false })
+ }
+ }, [onAutoApproveToggle, autoApprovalEnabled, setAutoApprovalEnabled])
+
+ const handleOpenSettings = React.useCallback(
+ () =>
+ window.postMessage({ type: "action", action: "settingsButtonClicked", values: { section: "autoApprove" } }),
+ [],
+ )
+
+ // Calculate enabled and total counts as separate properties
+ const enabledCount = React.useMemo(() => {
+ return Object.values(toggles).filter((value) => !!value).length
+ }, [toggles])
+
+ const totalCount = React.useMemo(() => {
+ return Object.keys(toggles).length
+ }, [toggles])
+
+ // Split settings into two columns
+ const settingsArray = Object.values(autoApproveSettingsConfig)
+ const halfLength = Math.ceil(settingsArray.length / 2)
+ const firstColumn = settingsArray.slice(0, halfLength)
+ const secondColumn = settingsArray.slice(halfLength)
+
+ return (
+
+
+
+
+
+ {enabledCount === totalCount
+ ? t("chat:autoApprove.triggerLabelAll")
+ : t("chat:autoApprove.triggerLabel", { count: enabledCount })}
+
+
+
+ e.preventDefault()}>
+
+ {/* Header with description */}
+
+
+
+ {t("chat:autoApprove.title")}
+
+
+
+
+ {t("chat:autoApprove.description")}
+
+
+
+ {/* Two-column layout for approval options */}
+
+
+ {/* First Column */}
+
+ {firstColumn.map(({ key, labelKey, descriptionKey, icon }) => {
+ const isEnabled = toggles[key]
+ return (
+
+
+
+ )
+ })}
+
+
+ {/* Second Column */}
+
+ {secondColumn.map(({ key, labelKey, descriptionKey, icon }) => {
+ const isEnabled = toggles[key]
+ return (
+
+
+
+ )
+ })}
+
+
+
+
+ {/* Bottom bar with Select All/None buttons */}
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/webview-ui/src/components/chat/AutoApproveMenu.tsx b/webview-ui/src/components/chat/AutoApproveMenu.tsx
deleted file mode 100644
index 25b936eb7457..000000000000
--- a/webview-ui/src/components/chat/AutoApproveMenu.tsx
+++ /dev/null
@@ -1,273 +0,0 @@
-import { memo, useCallback, useMemo, useState } from "react"
-import { Trans } from "react-i18next"
-import { VSCodeCheckbox, VSCodeLink } from "@vscode/webview-ui-toolkit/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 { useAutoApprovalState } from "@src/hooks/useAutoApprovalState"
-import { useAutoApprovalToggles } from "@src/hooks/useAutoApprovalToggles"
-import DismissibleUpsell from "@src/components/common/DismissibleUpsell"
-import { useCloudUpsell } from "@src/hooks/useCloudUpsell"
-import { CloudUpsellDialog } from "@src/components/cloud/CloudUpsellDialog"
-
-interface AutoApproveMenuProps {
- style?: React.CSSProperties
-}
-
-const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
- const [isExpanded, setIsExpanded] = useState(false)
-
- const {
- autoApprovalEnabled,
- setAutoApprovalEnabled,
- alwaysApproveResubmit,
- setAlwaysAllowReadOnly,
- setAlwaysAllowWrite,
- setAlwaysAllowExecute,
- setAlwaysAllowBrowser,
- setAlwaysAllowMcp,
- setAlwaysAllowModeSwitch,
- setAlwaysAllowSubtasks,
- setAlwaysApproveResubmit,
- setAlwaysAllowFollowupQuestions,
- setAlwaysAllowUpdateTodoList,
- } = useExtensionState()
-
- const { t } = useAppTranslation()
-
- const { isOpen, openUpsell, closeUpsell, handleConnect } = useCloudUpsell({
- autoOpenOnAuth: false,
- })
-
- const baseToggles = useAutoApprovalToggles()
- const enabledCount = useMemo(() => Object.values(baseToggles).filter(Boolean).length, [baseToggles])
-
- // 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)
- 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
- case "alwaysAllowFollowupQuestions":
- setAlwaysAllowFollowupQuestions(value)
- break
- case "alwaysAllowUpdateTodoList":
- 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,
- setAlwaysAllowBrowser,
- setAlwaysAllowMcp,
- setAlwaysAllowModeSwitch,
- setAlwaysAllowSubtasks,
- setAlwaysApproveResubmit,
- setAlwaysAllowFollowupQuestions,
- setAlwaysAllowUpdateTodoList,
- setAutoApprovalEnabled,
- ],
- )
-
- const toggleExpanded = useCallback(() => {
- setIsExpanded((prev) => !prev)
- }, [])
-
- 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" } }),
- [],
- )
-
- return (
-
- {isExpanded && (
-
-
- ,
- }}
- />
-
-
-
-
- {enabledCount > 7 && (
- <>
-
openUpsell()}
- dismissOnClick={false}
- variant="banner">
- ,
- }}
- />
-
- >
- )}
-
- )}
-
-
-
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}
-
-
-
-
-
-
- )
-}
-
-export default memo(AutoApproveMenu)
diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx
index e7a9f67b8b6c..b93a6c2a6ba0 100644
--- a/webview-ui/src/components/chat/ChatTextArea.tsx
+++ b/webview-ui/src/components/chat/ChatTextArea.tsx
@@ -26,6 +26,7 @@ import { StandardTooltip } from "@src/components/ui"
import Thumbnails from "../common/Thumbnails"
import { ModeSelector } from "./ModeSelector"
import { ApiConfigSelector } from "./ApiConfigSelector"
+import { AutoApproveDropdown } from "./AutoApproveDropdown"
import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
import ContextMenu from "./ContextMenu"
import { IndexingStatusBadge } from "./IndexingStatusBadge"
@@ -1173,34 +1174,31 @@ export const ChatTextArea = forwardRef(
/>
)}
-
-
-
-
-
-
+
+
-
+
{isTtsPlaying && (