feat: add edit functionality for AI-generated responses

- Add edit button to AI text messages in ChatRow component
- Implement edit mode UI with textarea and controls for AI messages
- Update ChatView to pass editable prop for AI text responses
- Handle AI message edits in webviewMessageHandler
- Preserve message history when editing AI responses

Fixes #8395
This commit is contained in:
Roo Code 2025-09-30 00:10:55 +00:00
parent 702b269a1b
commit 28476572fe
5 changed files with 112 additions and 28 deletions

View file

@ -1530,12 +1530,54 @@ export const webviewMessageHandler = async (
message.value &&
message.editedMessageContent
) {
await handleMessageModificationsOperation(
message.value,
"edit",
message.editedMessageContent,
message.images,
)
// Check if this is an AI message edit
const currentCline = provider.getCurrentTask()
if (currentCline) {
const messageIndex = currentCline.clineMessages.findIndex(
(msg: ClineMessage) => msg.ts === message.value,
)
if (messageIndex !== -1) {
const targetMessage = currentCline.clineMessages[messageIndex]
// If this is an AI text message, handle it differently
if (targetMessage.say === "text" && !targetMessage.partial) {
// For AI messages, we need to update the message directly
// and update the API conversation history
targetMessage.text = message.editedMessageContent
if (message.images) {
targetMessage.images = message.images
}
// Save the updated messages
await saveTaskMessages({
messages: currentCline.clineMessages,
taskId: currentCline.taskId,
globalStoragePath: provider.contextProxy.globalStorageUri.fsPath,
})
// Also update the API conversation history if this message exists there
const apiIndex = currentCline.apiConversationHistory.findIndex(
(msg: ApiMessage) => msg.ts === message.value,
)
if (apiIndex !== -1) {
// Update the content for assistant messages in API history
// Note: ApiMessage type doesn't support images property directly
currentCline.apiConversationHistory[apiIndex].content = message.editedMessageContent
}
// Update the UI to reflect the changes
await provider.postStateToWebview()
} else {
// For user feedback messages, use the existing edit flow
await handleMessageModificationsOperation(
message.value,
"edit",
message.editedMessageContent,
message.images,
)
}
}
}
}
break
}

1
tmp/Roo-Code Submodule

@ -0,0 +1 @@
Subproject commit 8111da66bd59ca8d500e5eae23b24a0419ed7345

1
tmp/Roo-Code-rc Submodule

@ -0,0 +1 @@
Subproject commit 7b7bb49572975c4aeff2381a0ebea99b3aa4542c

View file

@ -1118,19 +1118,54 @@ export const ChatRowContent = ({
return null // we should never see this message type
case "text":
return (
<div>
<div className="group">
<div style={headerStyle}>
<MessageCircle className="w-4 shrink-0" aria-label="Speech bubble icon" />
<span style={{ fontWeight: "bold" }}>{t("chat:text.rooSaid")}</span>
{/* Add edit button for AI responses */}
{!isStreaming && !message.partial && editable && (
<div
className="cursor-pointer shrink-0 opacity-0 group-hover:opacity-100 transition-opacity ml-auto"
onClick={(e) => {
e.stopPropagation()
handleEditClick()
}}>
<Edit className="w-4 shrink-0" aria-label="Edit AI response icon" />
</div>
)}
</div>
<div className="pl-6">
<Markdown markdown={message.text} partial={message.partial} />
{message.images && message.images.length > 0 && (
<div style={{ marginTop: "10px" }}>
{message.images.map((image, index) => (
<ImageBlock key={index} imageData={image} />
))}
{isEditing ? (
<div className="flex flex-col gap-2">
<ChatTextArea
inputValue={editedContent}
setInputValue={setEditedContent}
sendingDisabled={false}
selectApiConfigDisabled={true}
placeholderText={t("chat:editMessage.placeholder")}
selectedImages={editImages}
setSelectedImages={setEditImages}
onSend={handleSaveEdit}
onSelectImages={handleSelectImages}
shouldDisableImages={!model?.supportsImages}
mode={editMode}
setMode={setEditMode}
modeShortcutText=""
isEditMode={true}
onCancel={handleCancelEdit}
/>
</div>
) : (
<>
<Markdown markdown={message.text} partial={message.partial} />
{message.images && message.images.length > 0 && (
<div style={{ marginTop: "10px" }}>
{message.images.map((image, index) => (
<ImageBlock key={index} imageData={image} />
))}
</div>
)}
</>
)}
</div>
</div>

View file

@ -1542,22 +1542,27 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
onFollowUpUnmount={handleFollowUpUnmount}
isFollowUpAnswered={messageOrGroup.isAnswered === true || messageOrGroup.ts === currentFollowUpTs}
editable={
messageOrGroup.type === "ask" &&
messageOrGroup.ask === "tool" &&
(() => {
let tool: any = {}
try {
tool = JSON.parse(messageOrGroup.text || "{}")
} catch (_) {
if (messageOrGroup.text?.includes("updateTodoList")) {
tool = { tool: "updateTodoList" }
// Allow editing of user feedback messages
messageOrGroup.say === "user_feedback" ||
// Allow editing of AI text responses
(messageOrGroup.say === "text" && !messageOrGroup.partial) ||
// Allow editing of updateTodoList tool messages when buttons are enabled
(messageOrGroup.type === "ask" &&
messageOrGroup.ask === "tool" &&
(() => {
let tool: any = {}
try {
tool = JSON.parse(messageOrGroup.text || "{}")
} catch (_) {
if (messageOrGroup.text?.includes("updateTodoList")) {
tool = { tool: "updateTodoList" }
}
}
}
if (tool.tool === "updateTodoList" && alwaysAllowUpdateTodoList) {
return false
}
return tool.tool === "updateTodoList" && enableButtons && !!primaryButtonText
})()
if (tool.tool === "updateTodoList" && alwaysAllowUpdateTodoList) {
return false
}
return tool.tool === "updateTodoList" && enableButtons && !!primaryButtonText
})())
}
/>
)