Explorar el Código

Add a button to delete user messages

Matt Rubens hace 1 año
padre
commit
90ed3a4582

+ 5 - 0
.changeset/khaki-pets-approve.md

@@ -0,0 +1,5 @@
+---
+"roo-cline": patch
+---
+
+Add a button to delete user messages

+ 1 - 0
README.md

@@ -5,6 +5,7 @@ A fork of Cline, an autonomous coding agent, with some additional experimental f
 ## Experimental Features
 
 - Drag and drop images into chats
+- Delete messages from chats
 - "Enhance prompt" button (OpenRouter models only for now)
 - Sound effects for feedback
 - Option to use browsers of different sizes and adjust screenshot quality

+ 10 - 9
src/core/Cline.ts

@@ -70,7 +70,7 @@ export class Cline {
 	diffStrategy?: DiffStrategy
 	diffEnabled: boolean = false
 
-	apiConversationHistory: Anthropic.MessageParam[] = []
+	apiConversationHistory: (Anthropic.MessageParam & { ts?: number })[] = []
 	clineMessages: ClineMessage[] = []
 	private askResponse?: ClineAskResponse
 	private askResponseText?: string
@@ -165,11 +165,12 @@ export class Cline {
 	}
 
 	private async addToApiConversationHistory(message: Anthropic.MessageParam) {
-		this.apiConversationHistory.push(message)
+		const messageWithTs = { ...message, ts: Date.now() }
+		this.apiConversationHistory.push(messageWithTs)
 		await this.saveApiConversationHistory()
 	}
 
-	private async overwriteApiConversationHistory(newHistory: Anthropic.MessageParam[]) {
+	async overwriteApiConversationHistory(newHistory: Anthropic.MessageParam[]) {
 		this.apiConversationHistory = newHistory
 		await this.saveApiConversationHistory()
 	}
@@ -205,7 +206,7 @@ export class Cline {
 		await this.saveClineMessages()
 	}
 
-	private async overwriteClineMessages(newMessages: ClineMessage[]) {
+	public async overwriteClineMessages(newMessages: ClineMessage[]) {
 		this.clineMessages = newMessages
 		await this.saveClineMessages()
 	}
@@ -460,6 +461,11 @@ export class Cline {
 		await this.overwriteClineMessages(modifiedClineMessages)
 		this.clineMessages = await this.getSavedClineMessages()
 
+		// need to make sure that the api conversation history can be resumed by the api, even if it goes out of sync with cline messages
+
+		let existingApiConversationHistory: Anthropic.Messages.MessageParam[] =
+		await this.getSavedApiConversationHistory()
+
 		// Now present the cline messages to the user and ask if they want to resume
 
 		const lastClineMessage = this.clineMessages
@@ -493,11 +499,6 @@ export class Cline {
 			responseImages = images
 		}
 
-		// need to make sure that the api conversation history can be resumed by the api, even if it goes out of sync with cline messages
-
-		let existingApiConversationHistory: Anthropic.Messages.MessageParam[] =
-			await this.getSavedApiConversationHistory()
-
 		// v2.0 xml tags refactor caveat: since we don't use tools anymore, we need to replace all tool use blocks with a text block since the API disallows conversations with tool uses and no tool schema
 		const conversationWithoutToolBlocks = existingApiConversationHistory.map((message) => {
 			if (Array.isArray(message.content)) {

+ 22 - 0
src/core/webview/ClineProvider.ts

@@ -642,6 +642,28 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 						await this.updateGlobalState("writeDelayMs", message.value)
 						await this.postStateToWebview()
 						break
+					case "deleteMessage": {
+						const answer = await vscode.window.showInformationMessage(
+							"Are you sure you want to delete this message and all subsequent messages?",
+							{ modal: true },
+							"Yes",
+							"No"
+						)
+						if (answer === "Yes" && this.cline && typeof message.value === 'number' && message.value) {
+							const timeCutoff = message.value - 1000; // 1 second buffer before the message to delete
+							const messageIndex = this.cline.clineMessages.findIndex(msg =>  msg.ts && msg.ts >= timeCutoff)
+							const apiConversationHistoryIndex = this.cline.apiConversationHistory.findIndex(msg => msg.ts && msg.ts >= timeCutoff)
+							if (messageIndex !== -1) {
+								const { historyItem } = await this.getTaskWithId(this.cline.taskId)
+								await this.cline.overwriteClineMessages(this.cline.clineMessages.slice(0, messageIndex))
+								if (apiConversationHistoryIndex !== -1) {
+									await this.cline.overwriteApiConversationHistory(this.cline.apiConversationHistory.slice(0, apiConversationHistoryIndex))
+								}
+								await this.initClineWithHistoryItem(historyItem)
+							}
+						}
+						break
+					}
 					case "screenshotQuality":
 						await this.updateGlobalState("screenshotQuality", message.value)
 						await this.postStateToWebview()

+ 1 - 0
src/shared/WebviewMessage.ts

@@ -47,6 +47,7 @@ export interface WebviewMessage {
 		| "enhancePrompt"
 		| "enhancedPrompt"
 		| "draggedImages"
+		| "deleteMessage"
 	text?: string
 	disabled?: boolean
 	askResponse?: ClineAskResponse

+ 4 - 0
webview-ui/src/components/chat/BrowserSessionRow.tsx

@@ -20,6 +20,7 @@ interface BrowserSessionRowProps {
 	lastModifiedMessage?: ClineMessage
 	isLast: boolean
 	onHeightChange: (isTaller: boolean) => void
+	isStreaming: boolean
 }
 
 const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
@@ -408,6 +409,7 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
 interface BrowserSessionRowContentProps extends Omit<BrowserSessionRowProps, "messages"> {
 	message: ClineMessage
 	setMaxActionHeight: (height: number) => void
+	isStreaming: boolean
 }
 
 const BrowserSessionRowContent = ({
@@ -417,6 +419,7 @@ const BrowserSessionRowContent = ({
 	lastModifiedMessage,
 	isLast,
 	setMaxActionHeight,
+	isStreaming,
 }: BrowserSessionRowContentProps) => {
 	const headerStyle: React.CSSProperties = {
 		display: "flex",
@@ -443,6 +446,7 @@ const BrowserSessionRowContent = ({
 								}}
 								lastModifiedMessage={lastModifiedMessage}
 								isLast={isLast}
+								isStreaming={isStreaming}
 							/>
 						</div>
 					)

+ 27 - 4
webview-ui/src/components/chat/ChatRow.tsx

@@ -1,4 +1,4 @@
-import { VSCodeBadge, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
+import { VSCodeBadge, VSCodeButton, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
 import deepEqual from "fast-deep-equal"
 import React, { memo, useEffect, useMemo, useRef } from "react"
 import { useSize } from "react-use"
@@ -27,6 +27,7 @@ interface ChatRowProps {
 	lastModifiedMessage?: ClineMessage
 	isLast: boolean
 	onHeightChange: (isTaller: boolean) => void
+	isStreaming: boolean
 }
 
 interface ChatRowContentProps extends Omit<ChatRowProps, "onHeightChange"> {}
@@ -75,6 +76,7 @@ export const ChatRowContent = ({
 	onToggleExpand,
 	lastModifiedMessage,
 	isLast,
+	isStreaming,
 }: ChatRowContentProps) => {
 	const { mcpServers } = useExtensionState()
 	const [cost, apiReqCancelReason, apiReqStreamingFailedMessage] = useMemo(() => {
@@ -475,10 +477,9 @@ export const ChatRowContent = ({
 									msUserSelect: "none",
 								}}
 								onClick={onToggleExpand}>
-								<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
+								<div style={{ display: "flex", alignItems: "center", gap: "10px", flexGrow: 1 }}>
 									{icon}
 									{title}
-									{/* Need to render this everytime since it affects height of row by 2px */}
 									<VSCodeBadge style={{ opacity: cost != null && cost > 0 ? 1 : 0 }}>
 										${Number(cost || 0)?.toFixed(4)}
 									</VSCodeBadge>
@@ -570,7 +571,29 @@ export const ChatRowContent = ({
 								whiteSpace: "pre-line",
 								wordWrap: "break-word",
 							}}>
-							<span style={{ display: "block" }}>{highlightMentions(message.text)}</span>
+							<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: "10px" }}>
+								<span style={{ display: "block", flexGrow: 1 }}>{highlightMentions(message.text)}</span>
+								<VSCodeButton
+									appearance="icon"
+									style={{
+										padding: "3px",
+										flexShrink: 0,
+										height: "24px",
+										marginTop: "-6px",
+										marginRight: "-6px"
+									}}
+									disabled={isStreaming}
+									onClick={(e) => {
+										e.stopPropagation();
+										vscode.postMessage({
+											type: "deleteMessage",
+											value: message.ts
+										});
+									}}
+								>
+									<span className="codicon codicon-trash"></span>
+								</VSCodeButton>
+							</div>
 							{message.images && message.images.length > 0 && (
 								<Thumbnails images={message.images} style={{ marginTop: "8px" }} />
 							)}

+ 3 - 1
webview-ui/src/components/chat/ChatView.tsx

@@ -787,6 +787,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
 						isLast={index === groupedMessages.length - 1}
 						lastModifiedMessage={modifiedMessages.at(-1)}
 						onHeightChange={handleRowHeightChange}
+						isStreaming={isStreaming}
 						// Pass handlers for each message in the group
 						isExpanded={(messageTs: number) => expandedRows[messageTs] ?? false}
 						onToggleExpand={(messageTs: number) => {
@@ -809,10 +810,11 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
 					lastModifiedMessage={modifiedMessages.at(-1)}
 					isLast={index === groupedMessages.length - 1}
 					onHeightChange={handleRowHeightChange}
+					isStreaming={isStreaming}
 				/>
 			)
 		},
-		[expandedRows, modifiedMessages, groupedMessages.length, toggleRowExpansion, handleRowHeightChange],
+		[expandedRows, modifiedMessages, groupedMessages.length, handleRowHeightChange, isStreaming, toggleRowExpansion],
 	)
 
 	useEffect(() => {