feat: add pause/resume button for AI streaming responses

- Add pauseTask and resumeTask message types to WebviewMessage interface
- Implement pause/resume handlers in webviewMessageHandler
- Add pauseTask() and resumeTask() methods to ClineProvider
- Update ChatView UI to show Pause/Resume button alongside Cancel during streaming
- Add localization strings for pause and resume buttons
- Utilize existing Task.isPaused property for pause state management

Fixes #8331
This commit is contained in:
Roo Code 2025-09-26 16:52:34 +00:00
parent 5e218febdb
commit f3a7a7d2af
6 changed files with 120 additions and 29 deletions

1
.tmp/Roo-Code Submodule

@ -0,0 +1 @@
Subproject commit 86debeef43acbea9bdc1aa4b38d514541e164c91

View file

@ -2650,12 +2650,34 @@ export class ClineProvider
}
}
public resumeTask(taskId: string): void {
// Use the existing showTaskWithId method which handles both current and
// historical tasks.
this.showTaskWithId(taskId).catch((error) => {
this.log(`Failed to resume task ${taskId}: ${error.message}`)
})
public async pauseTask(): Promise<void> {
const task = this.getCurrentTask()
if (!task) {
return
}
this.log(`[pauseTask] pausing task ${task.taskId}`)
// Set the task as paused
task.isPaused = true
// Update the UI to reflect the paused state
await this.postStateToWebview()
}
public async resumeTask(): Promise<void> {
const task = this.getCurrentTask()
if (!task) {
return
}
this.log(`[resumeTask] resuming task ${task.taskId}`)
// Set the task as not paused
task.isPaused = false
// Update the UI to reflect the resumed state
await this.postStateToWebview()
}
// Modes

View file

@ -1003,6 +1003,12 @@ export const webviewMessageHandler = async (
case "cancelTask":
await provider.cancelTask()
break
case "pauseTask":
await provider.pauseTask()
break
case "resumeTask":
await provider.resumeTask()
break
case "allowedCommands": {
// Validate and sanitize the commands array
const commands = message.commands ?? []

View file

@ -76,6 +76,8 @@ export interface WebviewMessage {
| "openFile"
| "openMention"
| "cancelTask"
| "pauseTask"
| "resumeTask"
| "updateVSCodeSetting"
| "getVSCodeSetting"
| "vsCodeSetting"

View file

@ -78,6 +78,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
ref,
) => {
const isMountedRef = useRef(true)
const [isPaused, setIsPaused] = useState(false)
const [audioBaseUri] = useState(() => {
const w = window as any
@ -459,6 +460,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
setEnableButtons(false)
setPrimaryButtonText(undefined)
setSecondaryButtonText(undefined)
setIsPaused(false)
}
}, [messages.length])
@ -578,6 +580,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
setSelectedImages([])
setClineAsk(undefined)
setEnableButtons(false)
setIsPaused(false)
// Do not reset mode here as it should persist.
// setPrimaryButtonText(undefined)
// setSecondaryButtonText(undefined)
@ -728,8 +731,14 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
const trimmedInput = text?.trim()
if (isStreaming) {
vscode.postMessage({ type: "cancelTask" })
setDidClickCancel(true)
// If paused, resume; otherwise pause
if (isPaused) {
vscode.postMessage({ type: "resumeTask" })
setIsPaused(false)
} else {
vscode.postMessage({ type: "pauseTask" })
setIsPaused(true)
}
return
}
@ -767,7 +776,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
setClineAsk(undefined)
setEnableButtons(false)
},
[clineAsk, startNewTask, isStreaming],
[clineAsk, startNewTask, isStreaming, isPaused],
)
const { info: model } = useSelectedModel(apiConfiguration)
@ -1945,26 +1954,69 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
</StandardTooltip>
)}
{(secondaryButtonText || isStreaming) && (
<StandardTooltip
content={
isStreaming
? t("chat:cancel.tooltip")
: secondaryButtonText === t("chat:startNewTask.title")
? t("chat:startNewTask.tooltip")
: secondaryButtonText === t("chat:reject.title")
? t("chat:reject.tooltip")
: secondaryButtonText === t("chat:terminate.title")
? t("chat:terminate.tooltip")
: undefined
}>
<VSCodeButton
appearance="secondary"
disabled={!enableButtons && !(isStreaming && !didClickCancel)}
className={isStreaming ? "flex-[2] ml-0" : "flex-1 ml-[6px]"}
onClick={() => handleSecondaryButtonClick(inputValue, selectedImages)}>
{isStreaming ? t("chat:cancel.title") : secondaryButtonText}
</VSCodeButton>
</StandardTooltip>
<>
{isStreaming && (
<StandardTooltip
content={
isPaused
? t("chat:resume.tooltip", {
defaultValue: "Resume the AI's response",
})
: t("chat:pause.tooltip", {
defaultValue: "Pause the AI's response",
})
}>
<VSCodeButton
appearance="secondary"
disabled={didClickCancel}
className="flex-1 mr-[6px]"
onClick={() =>
handleSecondaryButtonClick(inputValue, selectedImages)
}>
{isPaused
? t("chat:resume.title", { defaultValue: "Resume" })
: t("chat:pause.title", { defaultValue: "Pause" })}
</VSCodeButton>
</StandardTooltip>
)}
{isStreaming && (
<StandardTooltip content={t("chat:cancel.tooltip")}>
<VSCodeButton
appearance="secondary"
disabled={didClickCancel}
className="flex-1 ml-[6px]"
onClick={() => {
vscode.postMessage({ type: "cancelTask" })
setDidClickCancel(true)
setIsPaused(false)
}}>
{t("chat:cancel.title")}
</VSCodeButton>
</StandardTooltip>
)}
{!isStreaming && secondaryButtonText && (
<StandardTooltip
content={
secondaryButtonText === t("chat:startNewTask.title")
? t("chat:startNewTask.tooltip")
: secondaryButtonText === t("chat:reject.title")
? t("chat:reject.tooltip")
: secondaryButtonText === t("chat:terminate.title")
? t("chat:terminate.tooltip")
: undefined
}>
<VSCodeButton
appearance="secondary"
disabled={!enableButtons}
className="flex-1 ml-[6px]"
onClick={() =>
handleSecondaryButtonClick(inputValue, selectedImages)
}>
{secondaryButtonText}
</VSCodeButton>
</StandardTooltip>
)}
</>
)}
</>
)}

View file

@ -92,6 +92,14 @@
"title": "Cancel",
"tooltip": "Cancel the current operation"
},
"pause": {
"title": "Pause",
"tooltip": "Pause the AI's response"
},
"resume": {
"title": "Resume",
"tooltip": "Resume the AI's response"
},
"editMessage": {
"placeholder": "Edit your message..."
},