Skip to content

Commit 2847657

Browse files
committed
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
1 parent 702b269 commit 2847657

File tree

5 files changed

+112
-28
lines changed

5 files changed

+112
-28
lines changed

src/core/webview/webviewMessageHandler.ts

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1530,12 +1530,54 @@ export const webviewMessageHandler = async (
15301530
message.value &&
15311531
message.editedMessageContent
15321532
) {
1533-
await handleMessageModificationsOperation(
1534-
message.value,
1535-
"edit",
1536-
message.editedMessageContent,
1537-
message.images,
1538-
)
1533+
// Check if this is an AI message edit
1534+
const currentCline = provider.getCurrentTask()
1535+
if (currentCline) {
1536+
const messageIndex = currentCline.clineMessages.findIndex(
1537+
(msg: ClineMessage) => msg.ts === message.value,
1538+
)
1539+
if (messageIndex !== -1) {
1540+
const targetMessage = currentCline.clineMessages[messageIndex]
1541+
1542+
// If this is an AI text message, handle it differently
1543+
if (targetMessage.say === "text" && !targetMessage.partial) {
1544+
// For AI messages, we need to update the message directly
1545+
// and update the API conversation history
1546+
targetMessage.text = message.editedMessageContent
1547+
if (message.images) {
1548+
targetMessage.images = message.images
1549+
}
1550+
1551+
// Save the updated messages
1552+
await saveTaskMessages({
1553+
messages: currentCline.clineMessages,
1554+
taskId: currentCline.taskId,
1555+
globalStoragePath: provider.contextProxy.globalStorageUri.fsPath,
1556+
})
1557+
1558+
// Also update the API conversation history if this message exists there
1559+
const apiIndex = currentCline.apiConversationHistory.findIndex(
1560+
(msg: ApiMessage) => msg.ts === message.value,
1561+
)
1562+
if (apiIndex !== -1) {
1563+
// Update the content for assistant messages in API history
1564+
// Note: ApiMessage type doesn't support images property directly
1565+
currentCline.apiConversationHistory[apiIndex].content = message.editedMessageContent
1566+
}
1567+
1568+
// Update the UI to reflect the changes
1569+
await provider.postStateToWebview()
1570+
} else {
1571+
// For user feedback messages, use the existing edit flow
1572+
await handleMessageModificationsOperation(
1573+
message.value,
1574+
"edit",
1575+
message.editedMessageContent,
1576+
message.images,
1577+
)
1578+
}
1579+
}
1580+
}
15391581
}
15401582
break
15411583
}

tmp/Roo-Code

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit 8111da66bd59ca8d500e5eae23b24a0419ed7345

tmp/Roo-Code-rc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit 7b7bb49572975c4aeff2381a0ebea99b3aa4542c

webview-ui/src/components/chat/ChatRow.tsx

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,19 +1118,54 @@ export const ChatRowContent = ({
11181118
return null // we should never see this message type
11191119
case "text":
11201120
return (
1121-
<div>
1121+
<div className="group">
11221122
<div style={headerStyle}>
11231123
<MessageCircle className="w-4 shrink-0" aria-label="Speech bubble icon" />
11241124
<span style={{ fontWeight: "bold" }}>{t("chat:text.rooSaid")}</span>
1125+
{/* Add edit button for AI responses */}
1126+
{!isStreaming && !message.partial && editable && (
1127+
<div
1128+
className="cursor-pointer shrink-0 opacity-0 group-hover:opacity-100 transition-opacity ml-auto"
1129+
onClick={(e) => {
1130+
e.stopPropagation()
1131+
handleEditClick()
1132+
}}>
1133+
<Edit className="w-4 shrink-0" aria-label="Edit AI response icon" />
1134+
</div>
1135+
)}
11251136
</div>
11261137
<div className="pl-6">
1127-
<Markdown markdown={message.text} partial={message.partial} />
1128-
{message.images && message.images.length > 0 && (
1129-
<div style={{ marginTop: "10px" }}>
1130-
{message.images.map((image, index) => (
1131-
<ImageBlock key={index} imageData={image} />
1132-
))}
1138+
{isEditing ? (
1139+
<div className="flex flex-col gap-2">
1140+
<ChatTextArea
1141+
inputValue={editedContent}
1142+
setInputValue={setEditedContent}
1143+
sendingDisabled={false}
1144+
selectApiConfigDisabled={true}
1145+
placeholderText={t("chat:editMessage.placeholder")}
1146+
selectedImages={editImages}
1147+
setSelectedImages={setEditImages}
1148+
onSend={handleSaveEdit}
1149+
onSelectImages={handleSelectImages}
1150+
shouldDisableImages={!model?.supportsImages}
1151+
mode={editMode}
1152+
setMode={setEditMode}
1153+
modeShortcutText=""
1154+
isEditMode={true}
1155+
onCancel={handleCancelEdit}
1156+
/>
11331157
</div>
1158+
) : (
1159+
<>
1160+
<Markdown markdown={message.text} partial={message.partial} />
1161+
{message.images && message.images.length > 0 && (
1162+
<div style={{ marginTop: "10px" }}>
1163+
{message.images.map((image, index) => (
1164+
<ImageBlock key={index} imageData={image} />
1165+
))}
1166+
</div>
1167+
)}
1168+
</>
11341169
)}
11351170
</div>
11361171
</div>

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1542,22 +1542,27 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
15421542
onFollowUpUnmount={handleFollowUpUnmount}
15431543
isFollowUpAnswered={messageOrGroup.isAnswered === true || messageOrGroup.ts === currentFollowUpTs}
15441544
editable={
1545-
messageOrGroup.type === "ask" &&
1546-
messageOrGroup.ask === "tool" &&
1547-
(() => {
1548-
let tool: any = {}
1549-
try {
1550-
tool = JSON.parse(messageOrGroup.text || "{}")
1551-
} catch (_) {
1552-
if (messageOrGroup.text?.includes("updateTodoList")) {
1553-
tool = { tool: "updateTodoList" }
1545+
// Allow editing of user feedback messages
1546+
messageOrGroup.say === "user_feedback" ||
1547+
// Allow editing of AI text responses
1548+
(messageOrGroup.say === "text" && !messageOrGroup.partial) ||
1549+
// Allow editing of updateTodoList tool messages when buttons are enabled
1550+
(messageOrGroup.type === "ask" &&
1551+
messageOrGroup.ask === "tool" &&
1552+
(() => {
1553+
let tool: any = {}
1554+
try {
1555+
tool = JSON.parse(messageOrGroup.text || "{}")
1556+
} catch (_) {
1557+
if (messageOrGroup.text?.includes("updateTodoList")) {
1558+
tool = { tool: "updateTodoList" }
1559+
}
15541560
}
1555-
}
1556-
if (tool.tool === "updateTodoList" && alwaysAllowUpdateTodoList) {
1557-
return false
1558-
}
1559-
return tool.tool === "updateTodoList" && enableButtons && !!primaryButtonText
1560-
})()
1561+
if (tool.tool === "updateTodoList" && alwaysAllowUpdateTodoList) {
1562+
return false
1563+
}
1564+
return tool.tool === "updateTodoList" && enableButtons && !!primaryButtonText
1565+
})())
15611566
}
15621567
/>
15631568
)

0 commit comments

Comments
 (0)