From 4b4905ec9ee0a90e90abffac599e25d307eb7fbf Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Mon, 16 Dec 2024 20:02:23 -0800 Subject: [PATCH] Add auto-approve UI and notification integration --- .../src/components/chat/AutoApproveMenu.tsx | 221 ++++++++++++++++++ webview-ui/src/components/chat/ChatView.tsx | 34 ++- 2 files changed, 253 insertions(+), 2 deletions(-) create mode 100644 webview-ui/src/components/chat/AutoApproveMenu.tsx diff --git a/webview-ui/src/components/chat/AutoApproveMenu.tsx b/webview-ui/src/components/chat/AutoApproveMenu.tsx new file mode 100644 index 0000000..645082d --- /dev/null +++ b/webview-ui/src/components/chat/AutoApproveMenu.tsx @@ -0,0 +1,221 @@ +import { VSCodeCheckbox, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" +import { useCallback, useState } from "react" +import styled from "styled-components" + +interface AutoApproveAction { + id: string + label: string + enabled: boolean + description: string +} + +interface AutoApproveMenuProps { + style?: React.CSSProperties +} + +const DEFAULT_MAX_REQUESTS = 50 + +const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { + const [isExpanded, setIsExpanded] = useState(false) + const [actions, setActions] = useState([ + { + id: "readFiles", + label: "Read files and directories", + enabled: false, + description: "Allows access to read any file on your computer.", + }, + { + id: "editFiles", + label: "Edit files", + enabled: false, + description: "Allows modification of any files on your computer.", + }, + { + id: "executeCommands", + label: "Execute safe commands", + enabled: false, + description: + "Allows automatic execution of safe terminal commands. The model will determine if a command is potentially destructive and ask for explicit approval.", + }, + { + id: "useBrowser", + label: "Use the browser", + enabled: false, + description: "Allows ability to launch and interact with any website in a headless browser.", + }, + { + id: "useMcp", + label: "Use MCP servers", + enabled: false, + description: "Allows use of configured MCP servers which may modify filesystem or interact with APIs.", + }, + ]) + const [maxRequests, setMaxRequests] = useState(DEFAULT_MAX_REQUESTS) + const [enableNotifications, setEnableNotifications] = useState(false) + + const toggleExpanded = useCallback(() => { + setIsExpanded((prev) => !prev) + }, []) + + const toggleAction = useCallback((actionId: string) => { + setActions((prev) => + prev.map((action) => (action.id === actionId ? { ...action, enabled: !action.enabled } : action)), + ) + }, []) + + const enabledActions = actions.filter((action) => action.enabled) + const enabledActionsList = enabledActions.map((action) => action.label).join(", ") + + return ( +
+
+ 0} + onChange={(e) => { + const checked = (e.target as HTMLInputElement).checked + setActions((prev) => + prev.map((action) => ({ + ...action, + enabled: checked, + })), + ) + e.stopPropagation() + }} + onClick={(e) => e.stopPropagation()} + /> + + Auto-approve: + + {enabledActions.length === 0 ? "None" : enabledActionsList} + + + +
+ {isExpanded && ( +
+
+ Auto-approve allows Cline to perform actions without asking for permission. Only enable for + actions you fully trust, and consider setting a low request limit as a safeguard. +
+ {actions.map((action) => ( +
+ toggleAction(action.id)}> + {action.label} + +
+ {action.description} +
+
+ ))} +
+
+ Max Requests: + { + const value = parseInt((e.target as HTMLInputElement).value) + if (!isNaN(value) && value > 0) { + setMaxRequests(value) + } + }} + style={{ flex: 1 }} + /> +
+
+ Cline will make this many API requests before asking for approval to proceed with the task. +
+
+ setEnableNotifications((prev) => !prev)}> + Enable Notifications + +
+ Receive system notifications when Cline requires approval to proceed or when a task is + completed. +
+
+
+ )} +
+ ) +} + +const CollapsibleSection = styled.div` + display: flex; + align-items: center; + gap: 4px; + color: var(--vscode-descriptionForeground); + flex: 1; + min-width: 0; + + &:hover { + color: var(--vscode-foreground); + } +` + +export default AutoApproveMenu diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index af61760..72c6411 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -25,6 +25,7 @@ import BrowserSessionRow from "./BrowserSessionRow" import ChatRow from "./ChatRow" import ChatTextArea from "./ChatTextArea" import TaskHeader from "./TaskHeader" +import AutoApproveMenu from "./AutoApproveMenu" import { AudioType } from "../../../../src/shared/WebviewMessage" import { validateCommand } from "../../utils/command-validation" @@ -866,10 +867,12 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie ) : (
{showAnnouncement && }
@@ -885,6 +888,32 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie {taskHistory.length > 0 && }
)} + + {/* + // Flex layout explanation: + // 1. Content div above uses flex: "1 1 0" to: + // - Grow to fill available space (flex-grow: 1) + // - Shrink when AutoApproveMenu needs space (flex-shrink: 1) + // - Start from zero size (flex-basis: 0) to ensure proper distribution + // minHeight: 0 allows it to shrink below its content height + // + // 2. AutoApproveMenu uses flex: "0 1 auto" to: + // - Not grow beyond its content (flex-grow: 0) + // - Shrink when viewport is small (flex-shrink: 1) + // - Use its content size as basis (flex-basis: auto) + // This ensures it takes its natural height when there's space + // but becomes scrollable when the viewport is too small + */} + {!task && ( + + )} + {task && ( <>
@@ -914,6 +943,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie initialTopMostItemIndex={groupedMessages.length - 1} />
+ {showScrollToBottom ? (
{primaryButtonText && !isStreaming && (