Skip to content

Commit 4b4905e

Browse files
saoudrizwanmrubens
authored andcommitted
Add auto-approve UI and notification integration
1 parent 201028b commit 4b4905e

File tree

2 files changed

+253
-2
lines changed

2 files changed

+253
-2
lines changed
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
import { VSCodeCheckbox, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
2+
import { useCallback, useState } from "react"
3+
import styled from "styled-components"
4+
5+
interface AutoApproveAction {
6+
id: string
7+
label: string
8+
enabled: boolean
9+
description: string
10+
}
11+
12+
interface AutoApproveMenuProps {
13+
style?: React.CSSProperties
14+
}
15+
16+
const DEFAULT_MAX_REQUESTS = 50
17+
18+
const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
19+
const [isExpanded, setIsExpanded] = useState(false)
20+
const [actions, setActions] = useState<AutoApproveAction[]>([
21+
{
22+
id: "readFiles",
23+
label: "Read files and directories",
24+
enabled: false,
25+
description: "Allows access to read any file on your computer.",
26+
},
27+
{
28+
id: "editFiles",
29+
label: "Edit files",
30+
enabled: false,
31+
description: "Allows modification of any files on your computer.",
32+
},
33+
{
34+
id: "executeCommands",
35+
label: "Execute safe commands",
36+
enabled: false,
37+
description:
38+
"Allows automatic execution of safe terminal commands. The model will determine if a command is potentially destructive and ask for explicit approval.",
39+
},
40+
{
41+
id: "useBrowser",
42+
label: "Use the browser",
43+
enabled: false,
44+
description: "Allows ability to launch and interact with any website in a headless browser.",
45+
},
46+
{
47+
id: "useMcp",
48+
label: "Use MCP servers",
49+
enabled: false,
50+
description: "Allows use of configured MCP servers which may modify filesystem or interact with APIs.",
51+
},
52+
])
53+
const [maxRequests, setMaxRequests] = useState(DEFAULT_MAX_REQUESTS)
54+
const [enableNotifications, setEnableNotifications] = useState(false)
55+
56+
const toggleExpanded = useCallback(() => {
57+
setIsExpanded((prev) => !prev)
58+
}, [])
59+
60+
const toggleAction = useCallback((actionId: string) => {
61+
setActions((prev) =>
62+
prev.map((action) => (action.id === actionId ? { ...action, enabled: !action.enabled } : action)),
63+
)
64+
}, [])
65+
66+
const enabledActions = actions.filter((action) => action.enabled)
67+
const enabledActionsList = enabledActions.map((action) => action.label).join(", ")
68+
69+
return (
70+
<div
71+
style={{
72+
padding: "0 15px",
73+
userSelect: "none",
74+
borderTop: isExpanded
75+
? `0.5px solid color-mix(in srgb, var(--vscode-titleBar-inactiveForeground) 20%, transparent)`
76+
: "none",
77+
overflowY: "auto",
78+
...style,
79+
}}>
80+
<div
81+
style={{
82+
display: "flex",
83+
alignItems: "center",
84+
gap: "8px",
85+
padding: isExpanded ? "8px 0" : "8px 0 0 0",
86+
cursor: "pointer",
87+
}}
88+
onClick={toggleExpanded}>
89+
<VSCodeCheckbox
90+
checked={enabledActions.length > 0}
91+
onChange={(e) => {
92+
const checked = (e.target as HTMLInputElement).checked
93+
setActions((prev) =>
94+
prev.map((action) => ({
95+
...action,
96+
enabled: checked,
97+
})),
98+
)
99+
e.stopPropagation()
100+
}}
101+
onClick={(e) => e.stopPropagation()}
102+
/>
103+
<CollapsibleSection>
104+
<span style={{ color: "var(--vscode-foreground)" }}>Auto-approve:</span>
105+
<span
106+
style={{
107+
whiteSpace: "nowrap",
108+
overflow: "hidden",
109+
textOverflow: "ellipsis",
110+
}}>
111+
{enabledActions.length === 0 ? "None" : enabledActionsList}
112+
</span>
113+
<span
114+
className={`codicon codicon-chevron-${isExpanded ? "down" : "right"}`}
115+
style={{
116+
// fontSize: "14px",
117+
flexShrink: 0,
118+
marginLeft: isExpanded ? "2px" : "-2px",
119+
}}
120+
/>
121+
</CollapsibleSection>
122+
</div>
123+
{isExpanded && (
124+
<div style={{ padding: "0" }}>
125+
<div
126+
style={{
127+
marginBottom: "10px",
128+
color: "var(--vscode-descriptionForeground)",
129+
fontSize: "12px",
130+
}}>
131+
Auto-approve allows Cline to perform actions without asking for permission. Only enable for
132+
actions you fully trust, and consider setting a low request limit as a safeguard.
133+
</div>
134+
{actions.map((action) => (
135+
<div key={action.id} style={{ margin: "6px 0" }}>
136+
<VSCodeCheckbox checked={action.enabled} onChange={() => toggleAction(action.id)}>
137+
{action.label}
138+
</VSCodeCheckbox>
139+
<div
140+
style={{
141+
marginLeft: "28px",
142+
color: "var(--vscode-descriptionForeground)",
143+
fontSize: "12px",
144+
}}>
145+
{action.description}
146+
</div>
147+
</div>
148+
))}
149+
<div
150+
style={{
151+
height: "0.5px",
152+
background: "var(--vscode-titleBar-inactiveForeground)",
153+
margin: "15px 0",
154+
opacity: 0.2,
155+
}}
156+
/>
157+
<div
158+
style={{
159+
display: "flex",
160+
alignItems: "center",
161+
gap: "8px",
162+
marginTop: "10px",
163+
marginBottom: "8px",
164+
color: "var(--vscode-foreground)",
165+
}}>
166+
<span style={{ flexShrink: 1, minWidth: 0 }}>Max Requests:</span>
167+
<VSCodeTextField
168+
value={maxRequests.toString()}
169+
onChange={(e) => {
170+
const value = parseInt((e.target as HTMLInputElement).value)
171+
if (!isNaN(value) && value > 0) {
172+
setMaxRequests(value)
173+
}
174+
}}
175+
style={{ flex: 1 }}
176+
/>
177+
</div>
178+
<div
179+
style={{
180+
color: "var(--vscode-descriptionForeground)",
181+
fontSize: "12px",
182+
marginBottom: "10px",
183+
}}>
184+
Cline will make this many API requests before asking for approval to proceed with the task.
185+
</div>
186+
<div style={{ margin: "6px 0" }}>
187+
<VSCodeCheckbox
188+
checked={enableNotifications}
189+
onChange={() => setEnableNotifications((prev) => !prev)}>
190+
Enable Notifications
191+
</VSCodeCheckbox>
192+
<div
193+
style={{
194+
marginLeft: "28px",
195+
color: "var(--vscode-descriptionForeground)",
196+
fontSize: "12px",
197+
}}>
198+
Receive system notifications when Cline requires approval to proceed or when a task is
199+
completed.
200+
</div>
201+
</div>
202+
</div>
203+
)}
204+
</div>
205+
)
206+
}
207+
208+
const CollapsibleSection = styled.div`
209+
display: flex;
210+
align-items: center;
211+
gap: 4px;
212+
color: var(--vscode-descriptionForeground);
213+
flex: 1;
214+
min-width: 0;
215+
216+
&:hover {
217+
color: var(--vscode-foreground);
218+
}
219+
`
220+
221+
export default AutoApproveMenu

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import BrowserSessionRow from "./BrowserSessionRow"
2525
import ChatRow from "./ChatRow"
2626
import ChatTextArea from "./ChatTextArea"
2727
import TaskHeader from "./TaskHeader"
28+
import AutoApproveMenu from "./AutoApproveMenu"
2829
import { AudioType } from "../../../../src/shared/WebviewMessage"
2930
import { validateCommand } from "../../utils/command-validation"
3031

@@ -866,10 +867,12 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
866867
) : (
867868
<div
868869
style={{
869-
flexGrow: 1,
870+
flex: "1 1 0", // flex-grow: 1, flex-shrink: 1, flex-basis: 0
871+
minHeight: 0,
870872
overflowY: "auto",
871873
display: "flex",
872874
flexDirection: "column",
875+
paddingBottom: "10px",
873876
}}>
874877
{showAnnouncement && <Announcement version={version} hideAnnouncement={hideAnnouncement} />}
875878
<div style={{ padding: "0 20px", flexShrink: 0 }}>
@@ -885,6 +888,32 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
885888
{taskHistory.length > 0 && <HistoryPreview showHistoryView={showHistoryView} />}
886889
</div>
887890
)}
891+
892+
{/*
893+
// Flex layout explanation:
894+
// 1. Content div above uses flex: "1 1 0" to:
895+
// - Grow to fill available space (flex-grow: 1)
896+
// - Shrink when AutoApproveMenu needs space (flex-shrink: 1)
897+
// - Start from zero size (flex-basis: 0) to ensure proper distribution
898+
// minHeight: 0 allows it to shrink below its content height
899+
//
900+
// 2. AutoApproveMenu uses flex: "0 1 auto" to:
901+
// - Not grow beyond its content (flex-grow: 0)
902+
// - Shrink when viewport is small (flex-shrink: 1)
903+
// - Use its content size as basis (flex-basis: auto)
904+
// This ensures it takes its natural height when there's space
905+
// but becomes scrollable when the viewport is too small
906+
*/}
907+
{!task && (
908+
<AutoApproveMenu
909+
style={{
910+
marginBottom: -2,
911+
flex: "0 1 auto", // flex-grow: 0, flex-shrink: 1, flex-basis: auto
912+
minHeight: 0,
913+
}}
914+
/>
915+
)}
916+
888917
{task && (
889918
<>
890919
<div style={{ flexGrow: 1, display: "flex" }} ref={scrollContainerRef}>
@@ -914,6 +943,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
914943
initialTopMostItemIndex={groupedMessages.length - 1}
915944
/>
916945
</div>
946+
<AutoApproveMenu />
917947
{showScrollToBottom ? (
918948
<div
919949
style={{
@@ -938,7 +968,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
938968
: 0.5
939969
: 0,
940970
display: "flex",
941-
padding: "10px 15px 0px 15px",
971+
padding: `${primaryButtonText || secondaryButtonText || isStreaming ? "10" : "0"}px 15px 0px 15px`,
942972
}}>
943973
{primaryButtonText && !isStreaming && (
944974
<VSCodeButton

0 commit comments

Comments
 (0)