Skip to content

Commit 6f14767

Browse files
committed
feat: add inline editing for queued messages
- Add ability to edit queued messages by clicking on them - Support Enter to save and Escape to cancel edits - Add textarea that auto-resizes based on content - Add hover effect to indicate messages are editable - Add translation for click to edit tooltip
1 parent b05afe6 commit 6f14767

File tree

3 files changed

+79
-27
lines changed

3 files changed

+79
-27
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1909,6 +1909,9 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
19091909
<QueuedMessages
19101910
queue={messageQueue}
19111911
onRemove={(index) => setMessageQueue((prev) => prev.filter((_, i) => i !== index))}
1912+
onUpdate={(index, newText) => {
1913+
setMessageQueue((prev) => prev.map((msg, i) => (i === index ? { ...msg, text: newText } : msg)))
1914+
}}
19121915
/>
19131916
<ChatTextArea
19141917
ref={textAreaRef}

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

Lines changed: 74 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from "react"
1+
import React, { useState } from "react"
22
import { useTranslation } from "react-i18next"
33
import Thumbnails from "../common/Thumbnails"
44
import { QueuedMessage } from "@roo-code/types"
@@ -8,45 +8,93 @@ import { Button } from "@src/components/ui"
88
interface QueuedMessagesProps {
99
queue: QueuedMessage[]
1010
onRemove: (index: number) => void
11+
onUpdate: (index: number, newText: string) => void
1112
}
1213

13-
const QueuedMessages: React.FC<QueuedMessagesProps> = ({ queue, onRemove }) => {
14+
const QueuedMessages: React.FC<QueuedMessagesProps> = ({ queue, onRemove, onUpdate }) => {
1415
const { t } = useTranslation("chat")
16+
const [editingStates, setEditingStates] = useState<Record<string, { isEditing: boolean; value: string }>>({})
1517

1618
if (queue.length === 0) {
1719
return null
1820
}
1921

22+
const getEditState = (messageId: string, currentText: string) => {
23+
return editingStates[messageId] || { isEditing: false, value: currentText }
24+
}
25+
26+
const setEditState = (messageId: string, isEditing: boolean, value?: string) => {
27+
setEditingStates((prev) => ({
28+
...prev,
29+
[messageId]: { isEditing, value: value ?? prev[messageId]?.value ?? "" },
30+
}))
31+
}
32+
33+
const handleSaveEdit = (index: number, messageId: string, newValue: string) => {
34+
onUpdate(index, newValue)
35+
setEditState(messageId, false)
36+
}
37+
2038
return (
2139
<div className="px-[15px] py-[10px] pr-[6px]" data-testid="queued-messages">
2240
<div className="text-vscode-descriptionForeground text-md mb-2">{t("queuedMessages.title")}</div>
2341
<div className="flex flex-col gap-2">
24-
{queue.map((message, index) => (
25-
<div
26-
key={message.id}
27-
className="bg-vscode-editor-background border rounded-xs p-1 overflow-hidden whitespace-pre-wrap">
28-
<div className="flex justify-between">
29-
<div className="flex-grow px-2 py-1 wrap-anywhere">
30-
<Mention text={message.text} withShadow />
31-
</div>
32-
<div className="flex">
33-
<Button
34-
variant="ghost"
35-
size="icon"
36-
className="shrink-0"
37-
onClick={(e) => {
38-
e.stopPropagation()
39-
onRemove(index)
40-
}}>
41-
<span className="codicon codicon-trash" />
42-
</Button>
42+
{queue.map((message, index) => {
43+
const editState = getEditState(message.id, message.text)
44+
45+
return (
46+
<div
47+
key={message.id}
48+
className="bg-vscode-editor-background border rounded-xs p-1 overflow-hidden whitespace-pre-wrap">
49+
<div className="flex justify-between">
50+
<div className="flex-grow px-2 py-1 wrap-anywhere">
51+
{editState.isEditing ? (
52+
<textarea
53+
value={editState.value}
54+
onChange={(e) => setEditState(message.id, true, e.target.value)}
55+
onBlur={() => handleSaveEdit(index, message.id, editState.value)}
56+
onKeyDown={(e) => {
57+
if (e.key === "Enter" && !e.shiftKey) {
58+
e.preventDefault()
59+
handleSaveEdit(index, message.id, editState.value)
60+
}
61+
if (e.key === "Escape") {
62+
setEditState(message.id, false, message.text)
63+
}
64+
}}
65+
className="w-full bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border rounded px-2 py-1 resize-none focus:outline-0 focus:ring-1 focus:ring-vscode-focusBorder"
66+
placeholder={t("chat:editMessage.placeholder")}
67+
autoFocus
68+
rows={Math.min(editState.value.split("\n").length, 10)}
69+
/>
70+
) : (
71+
<div
72+
onClick={() => setEditState(message.id, true, message.text)}
73+
className="cursor-pointer hover:bg-vscode-list-hoverBackground px-1 py-0.5 -mx-1 -my-0.5 rounded transition-colors"
74+
title={t("chat:queuedMessages.clickToEdit")}>
75+
<Mention text={message.text} withShadow />
76+
</div>
77+
)}
78+
</div>
79+
<div className="flex">
80+
<Button
81+
variant="ghost"
82+
size="icon"
83+
className="shrink-0"
84+
onClick={(e) => {
85+
e.stopPropagation()
86+
onRemove(index)
87+
}}>
88+
<span className="codicon codicon-trash" />
89+
</Button>
90+
</div>
4391
</div>
92+
{message.images && message.images.length > 0 && (
93+
<Thumbnails images={message.images} style={{ marginTop: "8px" }} />
94+
)}
4495
</div>
45-
{message.images && message.images.length > 0 && (
46-
<Thumbnails images={message.images} style={{ marginTop: "8px" }} />
47-
)}
48-
</div>
49-
))}
96+
)
97+
})}
5098
</div>
5199
</div>
52100
)

webview-ui/src/i18n/locales/en/chat.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@
351351
"triggerDescription": "Trigger the {{name}} command"
352352
},
353353
"queuedMessages": {
354-
"title": "Queued Messages:"
354+
"title": "Queued Messages:",
355+
"clickToEdit": "Click to edit message"
355356
}
356357
}

0 commit comments

Comments
 (0)