Просмотр исходного кода

Merge pull request #1494 from qdaxb/support_tool_progress_status

support tool progress status
Matt Rubens 10 месяцев назад
Родитель
Сommit
e2bdee3366

+ 25 - 1
src/core/Cline.ts

@@ -48,6 +48,7 @@ import {
 	ClineSay,
 	ClineSayBrowserAction,
 	ClineSayTool,
+	ToolProgressStatus,
 } from "../shared/ExtensionMessage"
 import { getApiMetrics } from "../shared/getApiMetrics"
 import { HistoryItem } from "../shared/HistoryItem"
@@ -408,6 +409,7 @@ export class Cline {
 		type: ClineAsk,
 		text?: string,
 		partial?: boolean,
+		progressStatus?: ToolProgressStatus,
 	): Promise<{ response: ClineAskResponse; text?: string; images?: string[] }> {
 		// If this Cline instance was aborted by the provider, then the only thing keeping us alive is a promise still running in the background, in which case we don't want to send its result to the webview as it is attached to a new instance of Cline now. So we can safely ignore the result of any active promises, and this class will be deallocated. (Although we set Cline = undefined in provider, that simply removes the reference to this instance, but the instance is still alive until this promise resolves or rejects.)
 		if (this.abort) {
@@ -423,6 +425,7 @@ export class Cline {
 					// existing partial message, so update it
 					lastMessage.text = text
 					lastMessage.partial = partial
+					lastMessage.progressStatus = progressStatus
 					// todo be more efficient about saving and posting only new data or one whole message at a time so ignore partial for saves, and only post parts of partial message instead of whole array in new listener
 					// await this.saveClineMessages()
 					// await this.providerRef.deref()?.postStateToWebview()
@@ -460,6 +463,8 @@ export class Cline {
 					// lastMessage.ts = askTs
 					lastMessage.text = text
 					lastMessage.partial = false
+					lastMessage.progressStatus = progressStatus
+
 					await this.saveClineMessages()
 					// await this.providerRef.deref()?.postStateToWebview()
 					await this.providerRef
@@ -511,6 +516,7 @@ export class Cline {
 		images?: string[],
 		partial?: boolean,
 		checkpoint?: Record<string, unknown>,
+		progressStatus?: ToolProgressStatus,
 	): Promise<undefined> {
 		if (this.abort) {
 			throw new Error(`Task: ${this.taskNumber} Roo Code instance aborted (#2)`)
@@ -526,6 +532,7 @@ export class Cline {
 					lastMessage.text = text
 					lastMessage.images = images
 					lastMessage.partial = partial
+					lastMessage.progressStatus = progressStatus
 					await this.providerRef
 						.deref()
 						?.postMessageToWebview({ type: "partialMessage", partialMessage: lastMessage })
@@ -545,6 +552,7 @@ export class Cline {
 					lastMessage.text = text
 					lastMessage.images = images
 					lastMessage.partial = false
+					lastMessage.progressStatus = progressStatus
 
 					// instead of streaming partialMessage events, we do a save and post like normal to persist to disk
 					await this.saveClineMessages()
@@ -1703,8 +1711,16 @@ export class Cline {
 						try {
 							if (block.partial) {
 								// update gui message
+								let toolProgressStatus
+								if (this.diffStrategy && this.diffStrategy.getProgressStatus) {
+									toolProgressStatus = this.diffStrategy.getProgressStatus(block)
+								}
+
 								const partialMessage = JSON.stringify(sharedMessageProps)
-								await this.ask("tool", partialMessage, block.partial).catch(() => {})
+
+								await this.ask("tool", partialMessage, block.partial, toolProgressStatus).catch(
+									() => {},
+								)
 								break
 							} else {
 								if (!relPath) {
@@ -1799,6 +1815,14 @@ export class Cline {
 									diff: diffContent,
 								} satisfies ClineSayTool)
 
+								let toolProgressStatus
+								if (this.diffStrategy && this.diffStrategy.getProgressStatus) {
+									toolProgressStatus = this.diffStrategy.getProgressStatus(block, diffResult)
+								}
+								await this.ask("tool", completeMessage, block.partial, toolProgressStatus).catch(
+									() => {},
+								)
+
 								const didApprove = await askApproval("tool", completeMessage)
 								if (!didApprove) {
 									await this.diffViewProvider.revertChanges() // This likely handles closing the diff view

+ 23 - 0
src/core/diff/strategies/multi-search-replace.ts

@@ -1,6 +1,8 @@
 import { DiffStrategy, DiffResult } from "../types"
 import { addLineNumbers, everyLineHasLineNumbers, stripLineNumbers } from "../../../integrations/misc/extract-text"
 import { distance } from "fastest-levenshtein"
+import { ToolProgressStatus } from "../../../shared/ExtensionMessage"
+import { ToolUse } from "../../assistant-message"
 
 const BUFFER_LINES = 40 // Number of extra context lines to show before and after matches
 
@@ -362,4 +364,25 @@ Only use a single line of '=======' between search and replacement content, beca
 			failParts: diffResults,
 		}
 	}
+
+	getProgressStatus(toolUse: ToolUse, result?: DiffResult): ToolProgressStatus {
+		const diffContent = toolUse.params.diff
+		if (diffContent) {
+			if (toolUse.partial) {
+				if (diffContent.length < 1000 || (diffContent.length / 50) % 10 === 0) {
+					return { text: `progressing ${(diffContent.match(/SEARCH/g) || []).length} blocks...` }
+				}
+			} else if (result) {
+				const searchBlockCount = (diffContent.match(/SEARCH/g) || []).length
+				if (result.failParts) {
+					return {
+						text: `progressed ${searchBlockCount - result.failParts.length}/${searchBlockCount} blocks.`,
+					}
+				} else {
+					return { text: `progressed ${searchBlockCount} blocks.` }
+				}
+			}
+		}
+		return {}
+	}
 }

+ 5 - 0
src/core/diff/types.ts

@@ -2,6 +2,9 @@
  * Interface for implementing different diff strategies
  */
 
+import { ToolProgressStatus } from "../../shared/ExtensionMessage"
+import { ToolUse } from "../assistant-message"
+
 export type DiffResult =
 	| { success: true; content: string; failParts?: DiffResult[] }
 	| ({
@@ -34,4 +37,6 @@ export interface DiffStrategy {
 	 * @returns A DiffResult object containing either the successful result or error details
 	 */
 	applyDiff(originalContent: string, diffContent: string, startLine?: number, endLine?: number): Promise<DiffResult>
+
+	getProgressStatus?(toolUse: ToolUse, result?: any): ToolProgressStatus
 }

+ 5 - 0
src/shared/ExtensionMessage.ts

@@ -155,6 +155,7 @@ export interface ClineMessage {
 	reasoning?: string
 	conversationHistoryIndex?: number
 	checkpoint?: Record<string, unknown>
+	progressStatus?: ToolProgressStatus
 }
 
 export type ClineAsk =
@@ -274,3 +275,7 @@ export interface HumanRelayCancelMessage {
 }
 
 export type ClineApiReqCancelReason = "streaming_failed" | "user_cancelled"
+
+export type ToolProgressStatus = {
+	text?: string
+}

+ 1 - 0
webview-ui/src/components/chat/ChatRow.tsx

@@ -258,6 +258,7 @@ export const ChatRowContent = ({
 							<span style={{ fontWeight: "bold" }}>Roo wants to edit this file:</span>
 						</div>
 						<CodeAccordian
+							progressStatus={message.progressStatus}
 							isLoading={message.partial}
 							diff={tool.diff!}
 							path={tool.path!}

+ 13 - 0
webview-ui/src/components/common/CodeAccordian.tsx

@@ -1,6 +1,7 @@
 import { memo, useMemo } from "react"
 import { getLanguageFromPath } from "../../utils/getLanguageFromPath"
 import CodeBlock, { CODE_BLOCK_BG_COLOR } from "./CodeBlock"
+import { ToolProgressStatus } from "../../../../src/shared/ExtensionMessage"
 
 interface CodeAccordianProps {
 	code?: string
@@ -12,6 +13,7 @@ interface CodeAccordianProps {
 	isExpanded: boolean
 	onToggleExpand: () => void
 	isLoading?: boolean
+	progressStatus?: ToolProgressStatus
 }
 
 /*
@@ -32,6 +34,7 @@ const CodeAccordian = ({
 	isExpanded,
 	onToggleExpand,
 	isLoading,
+	progressStatus,
 }: CodeAccordianProps) => {
 	const inferredLanguage = useMemo(
 		() => code && (language ?? (path ? getLanguageFromPath(path) : undefined)),
@@ -95,6 +98,16 @@ const CodeAccordian = ({
 						</>
 					)}
 					<div style={{ flexGrow: 1 }}></div>
+					{progressStatus && progressStatus.text && (
+						<span
+							style={{
+								color: "var(--vscode-descriptionForeground)",
+								borderTop: isExpanded ? "1px solid var(--vscode-editorGroup-border)" : "none",
+								marginLeft: "auto", // Right-align the text
+							}}>
+							{progressStatus.text}
+						</span>
+					)}
 					<span className={`codicon codicon-chevron-${isExpanded ? "up" : "down"}`}></span>
 				</div>
 			)}