From ce0c1821d0c075a2354ec5de59d73d3a74d250c2 Mon Sep 17 00:00:00 2001 From: Will Li Date: Thu, 10 Jul 2025 17:10:12 -0700 Subject: [PATCH] temp push --- src/core/task/Task.ts | 145 ++++++++- src/core/webview/ClineProvider.ts | 22 +- .../__tests__/webviewMessageHandler.spec.ts | 10 +- src/core/webview/webviewMessageHandler.ts | 131 +++++++- src/shared/ExtensionMessage.ts | 1 + src/shared/WebviewMessage.ts | 1 + webview-ui/src/App.tsx | 129 +++++--- webview-ui/src/components/chat/ChatRow.tsx | 2 +- .../chat/CheckpointRestoreDialog.tsx | 83 +++++ .../CheckpointRestoreDialog.spec.tsx | 297 ++++++++++++++++++ webview-ui/src/i18n/locales/en/common.json | 5 + 11 files changed, 767 insertions(+), 59 deletions(-) create mode 100644 webview-ui/src/components/chat/CheckpointRestoreDialog.tsx create mode 100644 webview-ui/src/components/chat/__tests__/CheckpointRestoreDialog.spec.tsx diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 31260cd6fa..389c395e99 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -177,6 +177,7 @@ export class Task extends EventEmitter { // LLM Messages & Chat Messages apiConversationHistory: ApiMessage[] = [] clineMessages: ClineMessage[] = [] + public pendingUserMessageCheckpoint?: Record // Ask private askResponse?: ClineAskResponse @@ -351,11 +352,30 @@ export class Task extends EventEmitter { // Cline Messages private async getSavedClineMessages(): Promise { - return readTaskMessages({ taskId: this.taskId, globalStoragePath: this.globalStoragePath }) + const messages = await readTaskMessages({ taskId: this.taskId, globalStoragePath: this.globalStoragePath }) + console.log("[Task#getSavedClineMessages] Loaded messages from disk:", messages.length) + const messagesWithCheckpoints = messages.filter((m) => m.checkpoint) + console.log("[Task#getSavedClineMessages] Messages with checkpoints:", messagesWithCheckpoints.length) + if (messagesWithCheckpoints.length > 0) { + console.log("[Task#getSavedClineMessages] Sample checkpoint:", messagesWithCheckpoints[0].checkpoint) + } + return messages } private async addToClineMessages(message: ClineMessage) { + console.log("[Task#addToClineMessages] Adding message:", JSON.stringify(message, null, 2)) this.clineMessages.push(message) + + // Verify the message was added correctly + const addedMessage = this.clineMessages[this.clineMessages.length - 1] + console.log("[Task#addToClineMessages] Verified added message:", { + ts: addedMessage.ts, + type: addedMessage.type, + say: addedMessage.say, + hasCheckpoint: !!addedMessage.checkpoint, + checkpoint: addedMessage.checkpoint, + }) + const provider = this.providerRef.deref() await provider?.postStateToWebview() this.emit("message", { action: "created", message }) @@ -532,6 +552,39 @@ export class Task extends EventEmitter { } async handleWebviewAskResponse(askResponse: ClineAskResponse, text?: string, images?: string[]) { + // Save checkpoint BEFORE setting the response to ensure it's ready when the user_feedback message is created + if (this.enableCheckpoints && askResponse === "messageResponse") { + console.log("[Task#handleWebviewAskResponse] Saving checkpoint for user message") + try { + const checkpointResult = await this.checkpointSave(true) // Force checkpoint save + console.log("[Task#handleWebviewAskResponse] Checkpoint result:", checkpointResult) + if (checkpointResult?.commit) { + // Store checkpoint data temporarily to be used when creating the user_feedback message + this.pendingUserMessageCheckpoint = { + hash: checkpointResult.commit, + timestamp: Date.now(), + type: "user_message", + } + console.log( + "[Task#handleWebviewAskResponse] Set pendingUserMessageCheckpoint:", + this.pendingUserMessageCheckpoint, + ) + } else { + console.log("[Task#handleWebviewAskResponse] No commit in checkpoint result") + } + } catch (error) { + console.error("[Task#handleWebviewAskResponse] Failed to save checkpoint after user message:", error) + } + } else { + console.log( + "[Task#handleWebviewAskResponse] Skipping checkpoint save - enableCheckpoints:", + this.enableCheckpoints, + "askResponse:", + askResponse, + ) + } + + // Now set the response, which will trigger the ask promise to resolve this.askResponse = askResponse this.askResponseText = text this.askResponseImages = images @@ -705,15 +758,49 @@ export class Task extends EventEmitter { this.lastMessageTs = sayTs } - await this.addToClineMessages({ - ts: sayTs, - type: "say", - say: type, - text, - images, - checkpoint, - contextCondense, - }) + if (type === "user_feedback") { + // Automatically use and clear the pending checkpoint for user_feedback messages + const feedbackCheckpoint = checkpoint || this.pendingUserMessageCheckpoint + this.pendingUserMessageCheckpoint = undefined // Clear it after use + + console.log("[Task#say] Adding user_feedback message with checkpoint:", feedbackCheckpoint) + console.log( + "[Task#say] Full message object:", + JSON.stringify( + { + ts: sayTs, + type: "say", + say: type, + text, + images, + checkpoint: feedbackCheckpoint, + contextCondense, + }, + null, + 2, + ), + ) + + await this.addToClineMessages({ + ts: sayTs, + type: "say", + say: type, + text, + images, + checkpoint: feedbackCheckpoint, + contextCondense, + }) + } else { + await this.addToClineMessages({ + ts: sayTs, + type: "say", + say: type, + text, + images, + checkpoint, + contextCondense, + }) + } } } @@ -740,6 +827,7 @@ export class Task extends EventEmitter { this.apiConversationHistory = [] await this.providerRef.deref()?.postStateToWebview() + // Checkpoint will be saved in handleWebviewAskResponse before this message is created await this.say("text", task, images) this.isInitialized = true @@ -783,6 +871,18 @@ export class Task extends EventEmitter { private async resumeTaskFromHistory() { const modifiedClineMessages = await this.getSavedClineMessages() + // Debug: Check if any messages have checkpoints + const messagesWithCheckpoints = modifiedClineMessages.filter((m) => m.checkpoint) + console.log("[Task#resumeTaskFromHistory] Total messages loaded:", modifiedClineMessages.length) + console.log("[Task#resumeTaskFromHistory] Messages with checkpoints:", messagesWithCheckpoints.length) + messagesWithCheckpoints.forEach((msg, idx) => { + console.log(`[Task#resumeTaskFromHistory] Message ${idx} with checkpoint:`, { + ts: msg.ts, + say: msg.say, + checkpoint: msg.checkpoint, + }) + }) + // Remove any resume messages that may have been added before const lastRelevantMessageIndex = findLastIndex( modifiedClineMessages, @@ -836,11 +936,23 @@ export class Task extends EventEmitter { let responseText: string | undefined let responseImages: string[] | undefined if (response === "messageResponse") { + // The checkpoint was already saved in handleWebviewAskResponse and attached to pendingUserMessageCheckpoint + // The say method will automatically handle it for user_feedback messages + console.log("[Task#resumeTaskFromHistory] Adding user_feedback message") await this.say("user_feedback", text, images) + + // Verify the message was added with checkpoint + const lastMessage = this.clineMessages[this.clineMessages.length - 1] + console.log("[Task#resumeTaskFromHistory] Last message after say:", { + ts: lastMessage?.ts, + say: lastMessage?.say, + hasCheckpoint: !!lastMessage?.checkpoint, + checkpoint: lastMessage?.checkpoint, + }) + responseText = text responseImages = images } - // 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: ApiMessage[] = await this.getSavedApiConversationHistory() @@ -1171,8 +1283,19 @@ export class Task extends EventEmitter { ], ) + // The say method will automatically handle the pending checkpoint for user_feedback messages + console.log("[Task] Tool approval - About to say user_feedback") await this.say("user_feedback", text, images) + // Verify the message was added with checkpoint + const lastMessage = this.clineMessages[this.clineMessages.length - 1] + console.log("[Task] Tool approval - Last message after say:", { + ts: lastMessage?.ts, + say: lastMessage?.say, + hasCheckpoint: !!lastMessage?.checkpoint, + checkpoint: lastMessage?.checkpoint, + }) + // Track consecutive mistake errors in telemetry. TelemetryService.instance.captureConsecutiveMistakeError(this.taskId) } diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 4a934e9fa0..1b7662c720 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -569,6 +569,10 @@ export class ClineProvider `[subtasks] ${cline.parentTask ? "child" : "parent"} task ${cline.taskId}.${cline.instanceId} instantiated`, ) + // Don't save checkpoint here - it will be saved in handleWebviewAskResponse + // when the user message is actually sent. The checkpoint service might not + // be initialized yet at this point. + return cline } @@ -1442,7 +1446,23 @@ export class ClineProvider currentTaskItem: this.getCurrentCline()?.taskId ? (taskHistory || []).find((item: HistoryItem) => item.id === this.getCurrentCline()?.taskId) : undefined, - clineMessages: this.getCurrentCline()?.clineMessages || [], + clineMessages: (() => { + const messages = this.getCurrentCline()?.clineMessages || [] + const messagesWithCheckpoints = messages.filter((m) => m.checkpoint) + console.log("[ClineProvider#getStateToPostToWebview] Total messages:", messages.length) + console.log( + "[ClineProvider#getStateToPostToWebview] Messages with checkpoints:", + messagesWithCheckpoints.length, + ) + if (messagesWithCheckpoints.length > 0) { + console.log("[ClineProvider#getStateToPostToWebview] Sample message with checkpoint:", { + ts: messagesWithCheckpoints[0].ts, + say: messagesWithCheckpoints[0].say, + checkpoint: messagesWithCheckpoints[0].checkpoint, + }) + } + return messages + })(), taskHistory: (taskHistory || []) .filter((item: HistoryItem) => item.ts && item.task) .sort((a: HistoryItem, b: HistoryItem) => b.ts - a.ts), diff --git a/src/core/webview/__tests__/webviewMessageHandler.spec.ts b/src/core/webview/__tests__/webviewMessageHandler.spec.ts index d03ffc9365..fdfcaf53bf 100644 --- a/src/core/webview/__tests__/webviewMessageHandler.spec.ts +++ b/src/core/webview/__tests__/webviewMessageHandler.spec.ts @@ -565,7 +565,10 @@ describe("webviewMessageHandler - message dialog preferences", () => { describe("deleteMessage", () => { it("should show dialog when skipDeleteMessageConfirmation is false", async () => { vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(false) - vi.mocked(mockClineProvider.getCurrentCline).mockReturnValue({} as any) // Mock current cline exists + vi.mocked(mockClineProvider.getCurrentCline).mockReturnValue({ + clineMessages: [], + apiConversationHistory: [], + } as any) // Mock current cline with empty arrays await webviewMessageHandler(mockClineProvider, { type: "deleteMessage", @@ -607,7 +610,10 @@ describe("webviewMessageHandler - message dialog preferences", () => { describe("submitEditedMessage", () => { it("should show dialog when skipEditMessageConfirmation is false", async () => { vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(false) - vi.mocked(mockClineProvider.getCurrentCline).mockReturnValue({} as any) // Mock current cline exists + vi.mocked(mockClineProvider.getCurrentCline).mockReturnValue({ + clineMessages: [], + apiConversationHistory: [], + } as any) // Mock current cline with empty arrays await webviewMessageHandler(mockClineProvider, { type: "submitEditedMessage", diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index b19951e154..dcfe141653 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -106,10 +106,46 @@ export const webviewMessageHandler = async ( // Directly handle the deletion without showing dialog await handleDeleteMessageConfirm(messageTs) } else { + // Check if the message has a checkpoint + const currentCline = provider.getCurrentCline() + let hasCheckpoint = false + if (currentCline) { + // Debug: Log all messages to understand the state + console.log("[webviewMessageHandler] Total messages:", currentCline.clineMessages.length) + console.log("[webviewMessageHandler] Looking for message with ts:", messageTs) + console.log( + "[webviewMessageHandler] All messages with timestamps:", + currentCline.clineMessages.map((m, idx) => ({ + index: idx, + ts: m.ts, + say: m.say, + hasCheckpoint: !!m.checkpoint, + checkpoint: m.checkpoint, + })), + ) + + const { messageIndex } = findMessageIndices(messageTs, currentCline) + console.log("[webviewMessageHandler] Checking for checkpoint at messageIndex:", messageIndex) + if (messageIndex !== -1) { + const targetMessage = currentCline.clineMessages[messageIndex] + console.log("[webviewMessageHandler] Target message:", JSON.stringify(targetMessage, null, 2)) + console.log("[webviewMessageHandler] Target message checkpoint:", targetMessage?.checkpoint) + hasCheckpoint = !!( + targetMessage?.checkpoint && + typeof targetMessage.checkpoint === "object" && + "hash" in targetMessage.checkpoint + ) + console.log("[webviewMessageHandler] hasCheckpoint:", hasCheckpoint) + } else { + console.log("[webviewMessageHandler] Message not found! Looking for ts:", messageTs) + } + } + // Send message to webview to show delete confirmation dialog await provider.postMessageToWebview({ type: "showDeleteMessageDialog", messageTs, + hasCheckpoint, }) } } @@ -117,7 +153,7 @@ export const webviewMessageHandler = async ( /** * Handles confirmed message deletion from webview dialog */ - const handleDeleteMessageConfirm = async (messageTs: number): Promise => { + const handleDeleteMessageConfirm = async (messageTs: number, restoreCheckpoint?: boolean): Promise => { // Only proceed if we have a current cline if (provider.getCurrentCline()) { const currentCline = provider.getCurrentCline()! @@ -125,6 +161,22 @@ export const webviewMessageHandler = async ( if (messageIndex !== -1) { try { + // If checkpoint restoration is requested, restore to the checkpoint first + if (restoreCheckpoint) { + const targetMessage = currentCline.clineMessages[messageIndex] + if ( + targetMessage?.checkpoint && + typeof targetMessage.checkpoint === "object" && + "hash" in targetMessage.checkpoint + ) { + await currentCline.checkpointRestore({ + ts: targetMessage.ts!, + commitHash: targetMessage.checkpoint.hash as string, + mode: "restore", + }) + } + } + const { historyItem } = await provider.getTaskWithId(currentCline.taskId) // Delete this message and all subsequent messages @@ -149,15 +201,62 @@ export const webviewMessageHandler = async ( // Check if user has opted to skip the confirmation const skipEditMessageConfirmation = getGlobalState("skipEditMessageConfirmation") + // Always check if the message has a checkpoint first + const currentCline = provider.getCurrentCline() + let hasCheckpoint = false + if (currentCline) { + console.log( + "[webviewMessageHandler] Edit - Total messages in currentCline:", + currentCline.clineMessages.length, + ) + console.log("[webviewMessageHandler] Edit - Looking for messageTs:", messageTs) + + // Log all messages with their timestamps and checkpoint status + currentCline.clineMessages.forEach((msg, idx) => { + console.log( + `[webviewMessageHandler] Edit - Message ${idx}: ts=${msg.ts}, type=${msg.type}, say=${msg.say}, hasCheckpoint=${!!msg.checkpoint}, checkpoint=${JSON.stringify(msg.checkpoint)}`, + ) + }) + + const { messageIndex } = findMessageIndices(messageTs, currentCline) + console.log("[webviewMessageHandler] Edit - Checking for checkpoint at messageIndex:", messageIndex) + if (messageIndex !== -1) { + const targetMessage = currentCline.clineMessages[messageIndex] + console.log("[webviewMessageHandler] Edit - Target message:", JSON.stringify(targetMessage, null, 2)) + console.log("[webviewMessageHandler] Edit - Target message checkpoint:", targetMessage?.checkpoint) + hasCheckpoint = !!( + targetMessage?.checkpoint && + typeof targetMessage.checkpoint === "object" && + "hash" in targetMessage.checkpoint + ) + console.log("[webviewMessageHandler] Edit - hasCheckpoint:", hasCheckpoint) + } else { + console.log("[webviewMessageHandler] Edit - Message not found in clineMessages!") + } + } else { + console.log("[webviewMessageHandler] Edit - No currentCline available!") + } + if (skipEditMessageConfirmation) { - // Directly handle the edit without showing dialog - await handleEditMessageConfirm(messageTs, editedContent) + // If there's a checkpoint, show the checkpoint dialog even when skipping confirmation + if (hasCheckpoint) { + await provider.postMessageToWebview({ + type: "showEditMessageDialog", + messageTs, + text: editedContent, + hasCheckpoint, + }) + } else { + // No checkpoint, directly handle the edit without showing dialog + await handleEditMessageConfirm(messageTs, editedContent, false) + } } else { // Send message to webview to show edit confirmation dialog await provider.postMessageToWebview({ type: "showEditMessageDialog", messageTs, text: editedContent, + hasCheckpoint, }) } } @@ -165,7 +264,11 @@ export const webviewMessageHandler = async ( /** * Handles confirmed message editing from webview dialog */ - const handleEditMessageConfirm = async (messageTs: number, editedContent: string): Promise => { + const handleEditMessageConfirm = async ( + messageTs: number, + editedContent: string, + restoreCheckpoint?: boolean, + ): Promise => { // Only proceed if we have a current cline if (provider.getCurrentCline()) { const currentCline = provider.getCurrentCline()! @@ -175,6 +278,22 @@ export const webviewMessageHandler = async ( if (messageIndex !== -1) { try { + // If checkpoint restoration is requested, restore to the checkpoint first + if (restoreCheckpoint) { + const targetMessage = currentCline.clineMessages[messageIndex] + if ( + targetMessage?.checkpoint && + typeof targetMessage.checkpoint === "object" && + "hash" in targetMessage.checkpoint + ) { + await currentCline.checkpointRestore({ + ts: targetMessage.ts!, + commitHash: targetMessage.checkpoint.hash as string, + mode: "restore", + }) + } + } + // Edit this message and delete subsequent await removeMessagesThisAndSubsequent(currentCline, messageIndex, apiConversationHistoryIndex) @@ -1503,12 +1622,12 @@ export const webviewMessageHandler = async ( break case "deleteMessageConfirm": if (message.messageTs) { - await handleDeleteMessageConfirm(message.messageTs) + await handleDeleteMessageConfirm(message.messageTs, message.restoreCheckpoint) } break case "editMessageConfirm": if (message.messageTs && message.text) { - await handleEditMessageConfirm(message.messageTs, message.text) + await handleEditMessageConfirm(message.messageTs, message.text, message.restoreCheckpoint) } break case "getListApiConfiguration": diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index e19d2368d7..956507a650 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -160,6 +160,7 @@ export interface ExtensionMessage { rulesFolderPath?: string settings?: any messageTs?: number + hasCheckpoint?: boolean } export type ExtensionState = Pick< diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 30fae8bd7b..f8f6ed97d9 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -230,6 +230,7 @@ export interface WebviewMessage { hasSystemPromptOverride?: boolean terminalOperation?: "continue" | "abort" messageTs?: number + restoreCheckpoint?: boolean historyPreviewCollapsed?: boolean filters?: { type?: string; search?: string; tags?: string[] } url?: string // For openExternal diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index 4e6d0a4694..873396cd4f 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -18,7 +18,8 @@ import McpView from "./components/mcp/McpView" import { MarketplaceView } from "./components/marketplace/MarketplaceView" import ModesView from "./components/modes/ModesView" import { HumanRelayDialog } from "./components/human-relay/HumanRelayDialog" -import { DeleteMessageDialog, EditMessageDialog } from "./components/chat/MessageModificationConfirmationDialog" +import { CheckpointRestoreDialog } from "./components/chat/CheckpointRestoreDialog" +import { MessageModificationConfirmationDialog } from "./components/chat/MessageModificationConfirmationDialog" import { AccountView } from "./components/account/AccountView" import { useAddNonInteractiveClickListener } from "./components/ui/hooks/useNonInteractiveClick" import { TooltipProvider } from "./components/ui/tooltip" @@ -74,19 +75,23 @@ const App = () => { const [deleteMessageDialogState, setDeleteMessageDialogState] = useState<{ isOpen: boolean messageTs: number + hasCheckpoint: boolean }>({ isOpen: false, messageTs: 0, + hasCheckpoint: false, }) const [editMessageDialogState, setEditMessageDialogState] = useState<{ isOpen: boolean messageTs: number text: string + hasCheckpoint: boolean }>({ isOpen: false, messageTs: 0, text: "", + hasCheckpoint: false, }) const settingsRef = useRef(null) @@ -153,7 +158,11 @@ const App = () => { messageTs: message.messageTs, }) } else { - setDeleteMessageDialogState({ isOpen: true, messageTs: message.messageTs }) + setDeleteMessageDialogState({ + isOpen: true, + messageTs: message.messageTs, + hasCheckpoint: message.hasCheckpoint || false, + }) } } @@ -167,7 +176,12 @@ const App = () => { text: message.text, }) } else { - setEditMessageDialogState({ isOpen: true, messageTs: message.messageTs, text: message.text }) + setEditMessageDialogState({ + isOpen: true, + messageTs: message.messageTs, + text: message.text, + hasCheckpoint: message.hasCheckpoint || false, + }) } } @@ -257,45 +271,84 @@ const App = () => { onSubmit={(requestId, text) => vscode.postMessage({ type: "humanRelayResponse", requestId, text })} onCancel={(requestId) => vscode.postMessage({ type: "humanRelayCancel", requestId })} /> - setDeleteMessageDialogState((prev) => ({ ...prev, isOpen: open }))} - onConfirm={(dontShowAgain) => { - // Save the preference if checkbox was checked - if (dontShowAgain) { - setSkipDeleteMessageConfirmation(true) + {deleteMessageDialogState.hasCheckpoint ? ( + setDeleteMessageDialogState((prev) => ({ ...prev, isOpen: open }))} + onConfirm={(restoreCheckpoint: boolean) => { vscode.postMessage({ - type: "skipDeleteMessageConfirmation", - bool: true, + type: "deleteMessageConfirm", + messageTs: deleteMessageDialogState.messageTs, + restoreCheckpoint, }) - } - vscode.postMessage({ - type: "deleteMessageConfirm", - messageTs: deleteMessageDialogState.messageTs, - }) - setDeleteMessageDialogState((prev) => ({ ...prev, isOpen: false })) - }} - /> - setEditMessageDialogState((prev) => ({ ...prev, isOpen: open }))} - onConfirm={(dontShowAgain) => { - // Save the preference if checkbox was checked - if (dontShowAgain) { - setSkipEditMessageConfirmation(true) + setDeleteMessageDialogState((prev) => ({ ...prev, isOpen: false })) + }} + /> + ) : ( + setDeleteMessageDialogState((prev) => ({ ...prev, isOpen: open }))} + onConfirm={(dontShowAgain: boolean) => { + // Save the preference if checkbox was checked + if (dontShowAgain) { + setSkipDeleteMessageConfirmation(true) + vscode.postMessage({ + type: "skipDeleteMessageConfirmation", + bool: true, + }) + } vscode.postMessage({ - type: "skipEditMessageConfirmation", - bool: true, + type: "deleteMessageConfirm", + messageTs: deleteMessageDialogState.messageTs, + restoreCheckpoint: false, }) - } - vscode.postMessage({ - type: "editMessageConfirm", - messageTs: editMessageDialogState.messageTs, - text: editMessageDialogState.text, - }) - setEditMessageDialogState((prev) => ({ ...prev, isOpen: false })) - }} - /> + setDeleteMessageDialogState((prev) => ({ ...prev, isOpen: false })) + }} + /> + )} + {editMessageDialogState.hasCheckpoint ? ( + setEditMessageDialogState((prev) => ({ ...prev, isOpen: open }))} + onConfirm={(restoreCheckpoint: boolean) => { + vscode.postMessage({ + type: "editMessageConfirm", + messageTs: editMessageDialogState.messageTs, + text: editMessageDialogState.text, + restoreCheckpoint, + }) + setEditMessageDialogState((prev) => ({ ...prev, isOpen: false })) + }} + /> + ) : ( + setEditMessageDialogState((prev) => ({ ...prev, isOpen: open }))} + onConfirm={(dontShowAgain: boolean) => { + // Save the preference if checkbox was checked + if (dontShowAgain) { + setSkipEditMessageConfirmation(true) + vscode.postMessage({ + type: "skipEditMessageConfirmation", + bool: true, + }) + } + vscode.postMessage({ + type: "editMessageConfirm", + messageTs: editMessageDialogState.messageTs, + text: editMessageDialogState.text, + restoreCheckpoint: false, + }) + setEditMessageDialogState((prev) => ({ ...prev, isOpen: false })) + }} + /> + )} ) } diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index d467424ec9..60786945df 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -1073,7 +1073,7 @@ export const ChatRowContent = ({