diff --git a/.changeset/khaki-pets-approve.md b/.changeset/khaki-pets-approve.md new file mode 100644 index 0000000..b9dceb5 --- /dev/null +++ b/.changeset/khaki-pets-approve.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Add a button to delete user messages diff --git a/README.md b/README.md index 125c9e3..bcf8ff2 100644 --- a/README.md +++ b/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 diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 0b77c27..218c0e8 100644 --- a/src/core/Cline.ts +++ b/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)) { diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 604bac6..2d584eb 100644 --- a/src/core/webview/ClineProvider.ts +++ b/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() diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 5ae5c02..6d4e4c0 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -47,6 +47,7 @@ export interface WebviewMessage { | "enhancePrompt" | "enhancedPrompt" | "draggedImages" + | "deleteMessage" text?: string disabled?: boolean askResponse?: ClineAskResponse diff --git a/webview-ui/src/components/chat/BrowserSessionRow.tsx b/webview-ui/src/components/chat/BrowserSessionRow.tsx index b5b20d1..682cfd2 100644 --- a/webview-ui/src/components/chat/BrowserSessionRow.tsx +++ b/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 { 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} /> ) diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 6d9042f..0253850 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/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 {} @@ -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}> -
+
{icon} {title} - {/* Need to render this everytime since it affects height of row by 2px */} 0 ? 1 : 0 }}> ${Number(cost || 0)?.toFixed(4)} @@ -570,7 +571,29 @@ export const ChatRowContent = ({ whiteSpace: "pre-line", wordWrap: "break-word", }}> - {highlightMentions(message.text)} +
+ {highlightMentions(message.text)} + { + e.stopPropagation(); + vscode.postMessage({ + type: "deleteMessage", + value: message.ts + }); + }} + > + + +
{message.images && message.images.length > 0 && ( )} diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index f4be356..db11547 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/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(() => {