mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 04:11:10 -05:00
Add auto-approve UI and notification integration
This commit is contained in:
committed by
Matt Rubens
parent
201028b317
commit
4b4905ec9e
221
webview-ui/src/components/chat/AutoApproveMenu.tsx
Normal file
221
webview-ui/src/components/chat/AutoApproveMenu.tsx
Normal file
@@ -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<AutoApproveAction[]>([
|
||||||
|
{
|
||||||
|
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 (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: "0 15px",
|
||||||
|
userSelect: "none",
|
||||||
|
borderTop: isExpanded
|
||||||
|
? `0.5px solid color-mix(in srgb, var(--vscode-titleBar-inactiveForeground) 20%, transparent)`
|
||||||
|
: "none",
|
||||||
|
overflowY: "auto",
|
||||||
|
...style,
|
||||||
|
}}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "8px",
|
||||||
|
padding: isExpanded ? "8px 0" : "8px 0 0 0",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onClick={toggleExpanded}>
|
||||||
|
<VSCodeCheckbox
|
||||||
|
checked={enabledActions.length > 0}
|
||||||
|
onChange={(e) => {
|
||||||
|
const checked = (e.target as HTMLInputElement).checked
|
||||||
|
setActions((prev) =>
|
||||||
|
prev.map((action) => ({
|
||||||
|
...action,
|
||||||
|
enabled: checked,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
/>
|
||||||
|
<CollapsibleSection>
|
||||||
|
<span style={{ color: "var(--vscode-foreground)" }}>Auto-approve:</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
}}>
|
||||||
|
{enabledActions.length === 0 ? "None" : enabledActionsList}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={`codicon codicon-chevron-${isExpanded ? "down" : "right"}`}
|
||||||
|
style={{
|
||||||
|
// fontSize: "14px",
|
||||||
|
flexShrink: 0,
|
||||||
|
marginLeft: isExpanded ? "2px" : "-2px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</CollapsibleSection>
|
||||||
|
</div>
|
||||||
|
{isExpanded && (
|
||||||
|
<div style={{ padding: "0" }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginBottom: "10px",
|
||||||
|
color: "var(--vscode-descriptionForeground)",
|
||||||
|
fontSize: "12px",
|
||||||
|
}}>
|
||||||
|
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.
|
||||||
|
</div>
|
||||||
|
{actions.map((action) => (
|
||||||
|
<div key={action.id} style={{ margin: "6px 0" }}>
|
||||||
|
<VSCodeCheckbox checked={action.enabled} onChange={() => toggleAction(action.id)}>
|
||||||
|
{action.label}
|
||||||
|
</VSCodeCheckbox>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginLeft: "28px",
|
||||||
|
color: "var(--vscode-descriptionForeground)",
|
||||||
|
fontSize: "12px",
|
||||||
|
}}>
|
||||||
|
{action.description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: "0.5px",
|
||||||
|
background: "var(--vscode-titleBar-inactiveForeground)",
|
||||||
|
margin: "15px 0",
|
||||||
|
opacity: 0.2,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "8px",
|
||||||
|
marginTop: "10px",
|
||||||
|
marginBottom: "8px",
|
||||||
|
color: "var(--vscode-foreground)",
|
||||||
|
}}>
|
||||||
|
<span style={{ flexShrink: 1, minWidth: 0 }}>Max Requests:</span>
|
||||||
|
<VSCodeTextField
|
||||||
|
value={maxRequests.toString()}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = parseInt((e.target as HTMLInputElement).value)
|
||||||
|
if (!isNaN(value) && value > 0) {
|
||||||
|
setMaxRequests(value)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
color: "var(--vscode-descriptionForeground)",
|
||||||
|
fontSize: "12px",
|
||||||
|
marginBottom: "10px",
|
||||||
|
}}>
|
||||||
|
Cline will make this many API requests before asking for approval to proceed with the task.
|
||||||
|
</div>
|
||||||
|
<div style={{ margin: "6px 0" }}>
|
||||||
|
<VSCodeCheckbox
|
||||||
|
checked={enableNotifications}
|
||||||
|
onChange={() => setEnableNotifications((prev) => !prev)}>
|
||||||
|
Enable Notifications
|
||||||
|
</VSCodeCheckbox>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginLeft: "28px",
|
||||||
|
color: "var(--vscode-descriptionForeground)",
|
||||||
|
fontSize: "12px",
|
||||||
|
}}>
|
||||||
|
Receive system notifications when Cline requires approval to proceed or when a task is
|
||||||
|
completed.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
@@ -25,6 +25,7 @@ import BrowserSessionRow from "./BrowserSessionRow"
|
|||||||
import ChatRow from "./ChatRow"
|
import ChatRow from "./ChatRow"
|
||||||
import ChatTextArea from "./ChatTextArea"
|
import ChatTextArea from "./ChatTextArea"
|
||||||
import TaskHeader from "./TaskHeader"
|
import TaskHeader from "./TaskHeader"
|
||||||
|
import AutoApproveMenu from "./AutoApproveMenu"
|
||||||
import { AudioType } from "../../../../src/shared/WebviewMessage"
|
import { AudioType } from "../../../../src/shared/WebviewMessage"
|
||||||
import { validateCommand } from "../../utils/command-validation"
|
import { validateCommand } from "../../utils/command-validation"
|
||||||
|
|
||||||
@@ -866,10 +867,12 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
flexGrow: 1,
|
flex: "1 1 0", // flex-grow: 1, flex-shrink: 1, flex-basis: 0
|
||||||
|
minHeight: 0,
|
||||||
overflowY: "auto",
|
overflowY: "auto",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
|
paddingBottom: "10px",
|
||||||
}}>
|
}}>
|
||||||
{showAnnouncement && <Announcement version={version} hideAnnouncement={hideAnnouncement} />}
|
{showAnnouncement && <Announcement version={version} hideAnnouncement={hideAnnouncement} />}
|
||||||
<div style={{ padding: "0 20px", flexShrink: 0 }}>
|
<div style={{ padding: "0 20px", flexShrink: 0 }}>
|
||||||
@@ -885,6 +888,32 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||||||
{taskHistory.length > 0 && <HistoryPreview showHistoryView={showHistoryView} />}
|
{taskHistory.length > 0 && <HistoryPreview showHistoryView={showHistoryView} />}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/*
|
||||||
|
// 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 && (
|
||||||
|
<AutoApproveMenu
|
||||||
|
style={{
|
||||||
|
marginBottom: -2,
|
||||||
|
flex: "0 1 auto", // flex-grow: 0, flex-shrink: 1, flex-basis: auto
|
||||||
|
minHeight: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{task && (
|
{task && (
|
||||||
<>
|
<>
|
||||||
<div style={{ flexGrow: 1, display: "flex" }} ref={scrollContainerRef}>
|
<div style={{ flexGrow: 1, display: "flex" }} ref={scrollContainerRef}>
|
||||||
@@ -914,6 +943,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||||||
initialTopMostItemIndex={groupedMessages.length - 1}
|
initialTopMostItemIndex={groupedMessages.length - 1}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<AutoApproveMenu />
|
||||||
{showScrollToBottom ? (
|
{showScrollToBottom ? (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -938,7 +968,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||||||
: 0.5
|
: 0.5
|
||||||
: 0,
|
: 0,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
padding: "10px 15px 0px 15px",
|
padding: `${primaryButtonText || secondaryButtonText || isStreaming ? "10" : "0"}px 15px 0px 15px`,
|
||||||
}}>
|
}}>
|
||||||
{primaryButtonText && !isStreaming && (
|
{primaryButtonText && !isStreaming && (
|
||||||
<VSCodeButton
|
<VSCodeButton
|
||||||
|
|||||||
Reference in New Issue
Block a user