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

Merge pull request #1526 from qdaxb/fix_progress_status

Fix progress status
Matt Rubens 9 месяцев назад
Родитель
Сommit
ff54c63ffa

+ 29 - 4
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()
@@ -1394,8 +1402,12 @@ export class Cline {
 					isCheckpointPossible = true
 				}
 
-				const askApproval = async (type: ClineAsk, partialMessage?: string) => {
-					const { response, text, images } = await this.ask(type, partialMessage, false)
+				const askApproval = async (
+					type: ClineAsk,
+					partialMessage?: string,
+					progressStatus?: ToolProgressStatus,
+				) => {
+					const { response, text, images } = await this.ask(type, partialMessage, false, progressStatus)
 					if (response !== "yesButtonClicked") {
 						// Handle both messageResponse and noButtonClicked with text
 						if (text) {
@@ -1703,8 +1715,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,7 +1819,12 @@ export class Cline {
 									diff: diffContent,
 								} satisfies ClineSayTool)
 
-								const didApprove = await askApproval("tool", completeMessage)
+								let toolProgressStatus
+								if (this.diffStrategy && this.diffStrategy.getProgressStatus) {
+									toolProgressStatus = this.diffStrategy.getProgressStatus(block, diffResult)
+								}
+
+								const didApprove = await askApproval("tool", completeMessage, toolProgressStatus)
 								if (!didApprove) {
 									await this.diffViewProvider.revertChanges() // This likely handles closing the diff view
 									break

+ 25 - 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,27 @@ 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) {
+			const icon = "diff-multiple"
+			const searchBlockCount = (diffContent.match(/SEARCH/g) || []).length
+			if (toolUse.partial) {
+				if (diffContent.length < 1000 || (diffContent.length / 50) % 10 === 0) {
+					return { icon, text: `${searchBlockCount}` }
+				}
+			} else if (result) {
+				if (result.failParts?.length) {
+					return {
+						icon,
+						text: `${searchBlockCount - result.failParts.length}/${searchBlockCount}`,
+					}
+				} else {
+					return { icon, text: `${searchBlockCount}` }
+				}
+			}
+		}
+		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
 }

+ 1 - 0
src/exports/roo-code.d.ts

@@ -92,6 +92,7 @@ export interface ClineMessage {
 	reasoning?: string
 	conversationHistoryIndex?: number
 	checkpoint?: Record<string, unknown>
+	progressStatus?: ToolProgressStatus
 }
 
 export interface ClineProvider {

+ 5 - 0
src/shared/ExtensionMessage.ts

@@ -231,3 +231,8 @@ export interface HumanRelayCancelMessage {
 }
 
 export type ClineApiReqCancelReason = "streaming_failed" | "user_cancelled"
+
+export type ToolProgressStatus = {
+	icon?: string
+	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!}

+ 11 - 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,14 @@ const CodeAccordian = ({
 						</>
 					)}
 					<div style={{ flexGrow: 1 }}></div>
+					{progressStatus && progressStatus.text && (
+						<>
+							{progressStatus.icon && <span className={`codicon codicon-${progressStatus.icon} mr-1`} />}
+							<span className="mr-1 ml-auto text-vscode-descriptionForeground">
+								{progressStatus.text}
+							</span>
+						</>
+					)}
 					<span className={`codicon codicon-chevron-${isExpanded ? "up" : "down"}`}></span>
 				</div>
 			)}