Skip to content

Commit 5ef7c1e

Browse files
committed
UI working
1 parent 24eb6e4 commit 5ef7c1e

File tree

14 files changed

+193
-11
lines changed

14 files changed

+193
-11
lines changed

packages/types/src/global-settings.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ export const globalSettingsSchema = z.object({
4545
alwaysAllowModeSwitch: z.boolean().optional(),
4646
alwaysAllowSubtasks: z.boolean().optional(),
4747
alwaysAllowExecute: z.boolean().optional(),
48+
alwaysAllowFollowupQuestions: z.boolean().optional(),
49+
followupAutoApproveTimeoutMs: z.number().optional(),
4850
allowedCommands: z.array(z.string()).optional(),
4951
allowedMaxRequests: z.number().nullish(),
5052
autoCondenseContext: z.boolean().optional(),
@@ -189,6 +191,8 @@ export const EVALS_SETTINGS: RooCodeSettings = {
189191
alwaysAllowModeSwitch: true,
190192
alwaysAllowSubtasks: true,
191193
alwaysAllowExecute: true,
194+
alwaysAllowFollowupQuestions: true,
195+
followupAutoApproveTimeoutMs: 0,
192196
allowedCommands: ["*"],
193197

194198
browserToolEnabled: false,

src/core/webview/ClineProvider.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,6 +1411,8 @@ export class ClineProvider
14111411
codebaseIndexConfig,
14121412
codebaseIndexModels,
14131413
profileThresholds,
1414+
alwaysAllowFollowupQuestions,
1415+
followupAutoApproveTimeoutMs,
14141416
} = await this.getState()
14151417

14161418
const telemetryKey = process.env.POSTHOG_API_KEY
@@ -1521,6 +1523,8 @@ export class ClineProvider
15211523
profileThresholds: profileThresholds ?? {},
15221524
cloudApiUrl: getRooCodeApiUrl(),
15231525
hasOpenedModeSelector: this.getGlobalState("hasOpenedModeSelector") ?? false,
1526+
alwaysAllowFollowupQuestions: alwaysAllowFollowupQuestions ?? false,
1527+
followupAutoApproveTimeoutMs: followupAutoApproveTimeoutMs ?? 10000,
15241528
}
15251529
}
15261530

@@ -1601,6 +1605,8 @@ export class ClineProvider
16011605
alwaysAllowMcp: stateValues.alwaysAllowMcp ?? false,
16021606
alwaysAllowModeSwitch: stateValues.alwaysAllowModeSwitch ?? false,
16031607
alwaysAllowSubtasks: stateValues.alwaysAllowSubtasks ?? false,
1608+
alwaysAllowFollowupQuestions: stateValues.alwaysAllowFollowupQuestions ?? false,
1609+
followupAutoApproveTimeoutMs: stateValues.followupAutoApproveTimeoutMs ?? 10000,
16041610
allowedMaxRequests: stateValues.allowedMaxRequests,
16051611
autoCondenseContext: stateValues.autoCondenseContext ?? true,
16061612
autoCondenseContextPercent: stateValues.autoCondenseContextPercent ?? 100,

src/core/webview/webviewMessageHandler.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,14 @@ export const webviewMessageHandler = async (
11001100
await updateGlobalState("maxWorkspaceFiles", fileCount)
11011101
await provider.postStateToWebview()
11021102
break
1103+
case "alwaysAllowFollowupQuestions":
1104+
await updateGlobalState("alwaysAllowFollowupQuestions", message.bool ?? false)
1105+
await provider.postStateToWebview()
1106+
break
1107+
case "followupAutoApproveTimeoutMs":
1108+
await updateGlobalState("followupAutoApproveTimeoutMs", message.value)
1109+
await provider.postStateToWebview()
1110+
break
11031111
case "browserToolEnabled":
11041112
await updateGlobalState("browserToolEnabled", message.bool ?? true)
11051113
await provider.postStateToWebview()

src/shared/WebviewMessage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ export interface WebviewMessage {
3737
| "alwaysAllowWriteOutsideWorkspace"
3838
| "alwaysAllowWriteProtected"
3939
| "alwaysAllowExecute"
40+
| "alwaysAllowFollowupQuestions"
41+
| "followupAutoApproveTimeoutMs"
4042
| "webviewDidLaunch"
4143
| "newTask"
4244
| "askResponse"

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
2525
alwaysAllowModeSwitch,
2626
alwaysAllowSubtasks,
2727
alwaysApproveResubmit,
28+
alwaysAllowFollowupQuestions,
2829
allowedMaxRequests,
2930
setAlwaysAllowReadOnly,
3031
setAlwaysAllowWrite,
@@ -34,6 +35,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
3435
setAlwaysAllowModeSwitch,
3536
setAlwaysAllowSubtasks,
3637
setAlwaysApproveResubmit,
38+
setAlwaysAllowFollowupQuestions,
3739
setAllowedMaxRequests,
3840
} = useExtensionState()
3941

@@ -68,6 +70,9 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
6870
case "alwaysApproveResubmit":
6971
setAlwaysApproveResubmit(value)
7072
break
73+
case "alwaysAllowFollowupQuestions":
74+
setAlwaysAllowFollowupQuestions(value)
75+
break
7176
}
7277
},
7378
[
@@ -79,6 +84,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
7984
setAlwaysAllowModeSwitch,
8085
setAlwaysAllowSubtasks,
8186
setAlwaysApproveResubmit,
87+
setAlwaysAllowFollowupQuestions,
8288
],
8389
)
8490

@@ -94,6 +100,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
94100
alwaysAllowModeSwitch: alwaysAllowModeSwitch,
95101
alwaysAllowSubtasks: alwaysAllowSubtasks,
96102
alwaysApproveResubmit: alwaysApproveResubmit,
103+
alwaysAllowFollowupQuestions: alwaysAllowFollowupQuestions,
97104
}),
98105
[
99106
alwaysAllowReadOnly,
@@ -104,6 +111,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
104111
alwaysAllowModeSwitch,
105112
alwaysAllowSubtasks,
106113
alwaysApproveResubmit,
114+
alwaysAllowFollowupQuestions,
107115
],
108116
)
109117

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

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,13 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
8888
alwaysAllowMcp,
8989
allowedCommands,
9090
writeDelayMs,
91+
followupAutoApproveTimeoutMs,
9192
mode,
9293
setMode,
9394
autoApprovalEnabled,
9495
alwaysAllowModeSwitch,
9596
alwaysAllowSubtasks,
97+
alwaysAllowFollowupQuestions,
9698
customModes,
9799
telemetrySetting,
98100
hasSystemPromptOverride,
@@ -879,6 +881,10 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
879881
return false
880882
}
881883

884+
if (message.ask === "followup") {
885+
return alwaysAllowFollowupQuestions
886+
}
887+
882888
if (message.ask === "browser_action_launch") {
883889
return alwaysAllowBrowser
884890
}
@@ -957,6 +963,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
957963
alwaysAllowMcp,
958964
isMcpToolAlwaysAllowed,
959965
alwaysAllowModeSwitch,
966+
alwaysAllowFollowupQuestions,
960967
alwaysAllowSubtasks,
961968
],
962969
)
@@ -1270,19 +1277,35 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
12701277

12711278
const autoApprove = async () => {
12721279
if (lastMessage?.ask && isAutoApproved(lastMessage)) {
1273-
if (lastMessage.ask === "tool" && isWriteToolAction(lastMessage)) {
1280+
// Special handling for follow-up questions
1281+
if (lastMessage.ask === "followup") {
1282+
const followUpData = JSON.parse(lastMessage.text || "{}")
1283+
if (followUpData && followUpData.suggest && followUpData.suggest.length > 0) {
1284+
// Wait for the configured timeout before auto-selecting the first suggestion
1285+
await new Promise<void>((resolve) => {
1286+
autoApproveTimeoutRef.current = setTimeout(resolve, followupAutoApproveTimeoutMs)
1287+
})
1288+
1289+
// Get the first suggestion
1290+
const firstSuggestion = followUpData.suggest[0]
1291+
const suggestionText =
1292+
typeof firstSuggestion === "string" ? firstSuggestion : firstSuggestion.answer
1293+
1294+
// Handle the suggestion click
1295+
handleSuggestionClickInRow(suggestionText)
1296+
return
1297+
}
1298+
} else if (lastMessage.ask === "tool" && isWriteToolAction(lastMessage)) {
12741299
await new Promise<void>((resolve) => {
12751300
autoApproveTimeoutRef.current = setTimeout(resolve, writeDelayMs)
12761301
})
12771302
}
12781303

1279-
if (autoApproveTimeoutRef.current === null || autoApproveTimeoutRef.current) {
1280-
vscode.postMessage({ type: "askResponse", askResponse: "yesButtonClicked" })
1304+
vscode.postMessage({ type: "askResponse", askResponse: "yesButtonClicked" })
12811305

1282-
setSendingDisabled(true)
1283-
setClineAsk(undefined)
1284-
setEnableButtons(false)
1285-
}
1306+
setSendingDisabled(true)
1307+
setClineAsk(undefined)
1308+
setEnableButtons(false)
12861309
}
12871310
}
12881311
autoApprove()
@@ -1303,6 +1326,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
13031326
alwaysAllowWrite,
13041327
alwaysAllowWriteOutsideWorkspace,
13051328
alwaysAllowExecute,
1329+
followupAutoApproveTimeoutMs,
13061330
alwaysAllowMcp,
13071331
messages,
13081332
allowedCommands,
@@ -1311,6 +1335,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
13111335
lastMessage,
13121336
writeDelayMs,
13131337
isWriteToolAction,
1338+
alwaysAllowFollowupQuestions,
1339+
handleSuggestionClickInRow,
13141340
])
13151341

13161342
// Function to handle mode switching

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

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { useCallback } from "react"
1+
import { useCallback, useEffect, useState } from "react"
22
import { Edit } from "lucide-react"
33

44
import { Button, StandardTooltip } from "@/components/ui"
55
import { vscode } from "@/utils/vscode"
66

77
import { useAppTranslation } from "@src/i18n/TranslationContext"
8+
import { useExtensionState } from "@src/context/ExtensionStateContext"
89

910
interface SuggestionItem {
1011
answer: string
@@ -18,7 +19,47 @@ interface FollowUpSuggestProps {
1819
}
1920

2021
export const FollowUpSuggest = ({ suggestions = [], onSuggestionClick, ts = 1 }: FollowUpSuggestProps) => {
22+
const { autoApprovalEnabled, alwaysAllowFollowupQuestions, followupAutoApproveTimeoutMs } = useExtensionState()
23+
const [countdown, setCountdown] = useState<number | null>(null)
24+
const [suggestionSelected, setSuggestionSelected] = useState(false)
2125
const { t } = useAppTranslation()
26+
27+
// Start countdown timer when auto-approval is enabled for follow-up questions
28+
useEffect(() => {
29+
// Only start countdown if auto-approval is enabled for follow-up questions and no suggestion has been selected
30+
if (autoApprovalEnabled && alwaysAllowFollowupQuestions && suggestions.length > 0 && !suggestionSelected) {
31+
// Start with the configured timeout in seconds
32+
const timeoutMs =
33+
typeof followupAutoApproveTimeoutMs === "number" && !isNaN(followupAutoApproveTimeoutMs)
34+
? followupAutoApproveTimeoutMs
35+
: 10000
36+
37+
// Convert milliseconds to seconds for the countdown
38+
setCountdown(Math.floor(timeoutMs / 1000))
39+
40+
// Update countdown every second
41+
const intervalId = setInterval(() => {
42+
setCountdown((prevCountdown) => {
43+
if (prevCountdown === null || prevCountdown <= 1) {
44+
clearInterval(intervalId)
45+
return null
46+
}
47+
return prevCountdown - 1
48+
})
49+
}, 1000)
50+
51+
// Clean up interval on unmount
52+
return () => clearInterval(intervalId)
53+
} else {
54+
setCountdown(null)
55+
}
56+
}, [
57+
autoApprovalEnabled,
58+
alwaysAllowFollowupQuestions,
59+
suggestions,
60+
followupAutoApproveTimeoutMs,
61+
suggestionSelected,
62+
])
2263
const handleSuggestionClick = useCallback(
2364
(suggestion: string | SuggestionItem, event: React.MouseEvent) => {
2465
const suggestionText = typeof suggestion === "string" ? suggestion : suggestion.answer
@@ -32,6 +73,11 @@ export const FollowUpSuggest = ({ suggestions = [], onSuggestionClick, ts = 1 }:
3273
})
3374
}
3475

76+
// Mark a suggestion as selected if it's not a shift-click (which just copies to input)
77+
if (!event.shiftKey) {
78+
setSuggestionSelected(true)
79+
}
80+
3581
onSuggestionClick?.(suggestionText, event)
3682
},
3783
[onSuggestionClick],
@@ -44,9 +90,10 @@ export const FollowUpSuggest = ({ suggestions = [], onSuggestionClick, ts = 1 }:
4490

4591
return (
4692
<div className="flex mb-2 flex-col h-full gap-2">
47-
{suggestions.map((suggestion) => {
93+
{suggestions.map((suggestion, index) => {
4894
const suggestionText = typeof suggestion === "string" ? suggestion : suggestion.answer
4995
const mode = typeof suggestion === "object" ? suggestion.mode : undefined
96+
const isFirstSuggestion = index === 0
5097

5198
return (
5299
<div key={`${suggestionText}-${ts}`} className="w-full relative group">
@@ -56,6 +103,13 @@ export const FollowUpSuggest = ({ suggestions = [], onSuggestionClick, ts = 1 }:
56103
onClick={(event) => handleSuggestionClick(suggestion, event)}
57104
aria-label={suggestionText}>
58105
{suggestionText}
106+
{isFirstSuggestion && countdown !== null && !suggestionSelected && (
107+
<span
108+
className="ml-2 px-1.5 py-0.5 text-xs rounded-full bg-vscode-badge-background text-vscode-badge-foreground"
109+
title={t("chat:followUpSuggest.autoSelectCountdown", { count: countdown })}>
110+
{countdown}s
111+
</span>
112+
)}
59113
</Button>
60114
{mode && (
61115
<div className="absolute bottom-0 right-0 text-[10px] bg-vscode-badge-background text-vscode-badge-foreground px-1 py-0.5 border border-vscode-badge-background flex items-center gap-0.5">

webview-ui/src/components/settings/AutoApproveSettings.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ type AutoApproveSettingsProps = HTMLAttributes<HTMLDivElement> & {
2525
alwaysAllowModeSwitch?: boolean
2626
alwaysAllowSubtasks?: boolean
2727
alwaysAllowExecute?: boolean
28+
alwaysAllowFollowupQuestions?: boolean
29+
followupAutoApproveTimeoutMs?: number
2830
allowedCommands?: string[]
2931
setCachedStateField: SetCachedStateField<
3032
| "alwaysAllowReadOnly"
@@ -40,6 +42,8 @@ type AutoApproveSettingsProps = HTMLAttributes<HTMLDivElement> & {
4042
| "alwaysAllowModeSwitch"
4143
| "alwaysAllowSubtasks"
4244
| "alwaysAllowExecute"
45+
| "alwaysAllowFollowupQuestions"
46+
| "followupAutoApproveTimeoutMs"
4347
| "allowedCommands"
4448
>
4549
}
@@ -58,6 +62,8 @@ export const AutoApproveSettings = ({
5862
alwaysAllowModeSwitch,
5963
alwaysAllowSubtasks,
6064
alwaysAllowExecute,
65+
alwaysAllowFollowupQuestions,
66+
followupAutoApproveTimeoutMs = 10000,
6167
allowedCommands,
6268
setCachedStateField,
6369
...props
@@ -95,6 +101,7 @@ export const AutoApproveSettings = ({
95101
alwaysAllowModeSwitch={alwaysAllowModeSwitch}
96102
alwaysAllowSubtasks={alwaysAllowSubtasks}
97103
alwaysAllowExecute={alwaysAllowExecute}
104+
alwaysAllowFollowupQuestions={alwaysAllowFollowupQuestions}
98105
onToggle={(key, value) => setCachedStateField(key, value)}
99106
/>
100107

@@ -202,6 +209,33 @@ export const AutoApproveSettings = ({
202209
</div>
203210
)}
204211

212+
{alwaysAllowFollowupQuestions && (
213+
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
214+
<div className="flex items-center gap-4 font-bold">
215+
<span className="codicon codicon-question" />
216+
<div>{t("settings:autoApprove.followupQuestions.label")}</div>
217+
</div>
218+
<div>
219+
<div className="flex items-center gap-2">
220+
<Slider
221+
min={1000}
222+
max={30000}
223+
step={1000}
224+
value={[followupAutoApproveTimeoutMs]}
225+
onValueChange={([value]) =>
226+
setCachedStateField("followupAutoApproveTimeoutMs", value)
227+
}
228+
data-testid="followup-timeout-slider"
229+
/>
230+
<span className="w-20">{followupAutoApproveTimeoutMs / 1000}s</span>
231+
</div>
232+
<div className="text-vscode-descriptionForeground text-sm mt-1">
233+
{t("settings:autoApprove.followupQuestions.timeoutLabel")}
234+
</div>
235+
</div>
236+
</div>
237+
)}
238+
205239
{alwaysAllowExecute && (
206240
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
207241
<div className="flex items-center gap-4 font-bold">

0 commit comments

Comments
 (0)