@@ -11,6 +11,9 @@ interface ChatMessageAreaProps {
11
11
onSendMessage : ( content : string ) => void ;
12
12
onSendStreamingMessage ?: ( content : string ) => void ;
13
13
onStopStreaming ?: ( ) => void ;
14
+ onRegenerateResponse ?: ( ) => void ;
15
+ onDeleteMessage ?: ( messageId : string ) => void ;
16
+ onEditMessage ?: ( messageId : string , newContent : string ) => void ;
14
17
isStreamingSupported ?: boolean ;
15
18
isCurrentlyStreaming ?: boolean ;
16
19
}
@@ -22,12 +25,17 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
22
25
onSendMessage,
23
26
onSendStreamingMessage,
24
27
onStopStreaming,
28
+ onRegenerateResponse,
29
+ onDeleteMessage,
30
+ onEditMessage,
25
31
isStreamingSupported = false ,
26
32
isCurrentlyStreaming = false ,
27
33
} ) => {
28
34
const [ input , setInput ] = useState ( '' ) ;
29
35
const messagesEndRef = useRef < HTMLDivElement > ( null ) ;
30
36
const [ hoveredMessageId , setHoveredMessageId ] = useState < string | null > ( null ) ;
37
+ const [ editingMessageId , setEditingMessageId ] = useState < string | null > ( null ) ;
38
+ const [ editingContent , setEditingContent ] = useState ( '' ) ;
31
39
32
40
// Scroll to bottom when messages change
33
41
useEffect ( ( ) => {
@@ -54,6 +62,61 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
54
62
}
55
63
} ;
56
64
65
+ // Handle regenerate response
66
+ const handleRegenerateResponse = ( ) => {
67
+ if ( onRegenerateResponse ) {
68
+ onRegenerateResponse ( ) ;
69
+ } else {
70
+ console . error ( 'Regenerate response function not provided' ) ;
71
+ }
72
+ } ;
73
+
74
+ // Handle delete message
75
+ const handleDeleteMessage = ( messageId : string ) => {
76
+ if ( onDeleteMessage ) {
77
+ onDeleteMessage ( messageId ) ;
78
+ } else {
79
+ console . error ( 'Delete message function not provided' ) ;
80
+ }
81
+ } ;
82
+
83
+ // Handle edit message
84
+ const handleEditMessage = ( messageId : string , content : string ) => {
85
+ setEditingMessageId ( messageId ) ;
86
+ setEditingContent ( content ) ;
87
+ } ;
88
+
89
+ // Handle save edit
90
+ const handleSaveEdit = ( ) => {
91
+ if ( editingMessageId && onEditMessage && editingContent . trim ( ) ) {
92
+ onEditMessage ( editingMessageId , editingContent ) ;
93
+ setEditingMessageId ( null ) ;
94
+ setEditingContent ( '' ) ;
95
+ }
96
+ } ;
97
+
98
+ // Handle send edited message
99
+ const handleSendEditedMessage = ( ) => {
100
+ if ( editingMessageId && editingContent . trim ( ) ) {
101
+ // First cancel the edit mode
102
+ setEditingMessageId ( null ) ;
103
+ setEditingContent ( '' ) ;
104
+
105
+ // Then send the edited content as a new message
106
+ if ( isStreamingSupported && onSendStreamingMessage ) {
107
+ onSendStreamingMessage ( editingContent ) ;
108
+ } else {
109
+ onSendMessage ( editingContent ) ;
110
+ }
111
+ }
112
+ } ;
113
+
114
+ // Handle cancel edit
115
+ const handleCancelEdit = ( ) => {
116
+ setEditingMessageId ( null ) ;
117
+ setEditingContent ( '' ) ;
118
+ } ;
119
+
57
120
// Handle copy message action
58
121
const handleCopyMessage = ( content : string ) => {
59
122
navigator . clipboard . writeText ( content )
@@ -85,6 +148,7 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
85
148
< div className = "flex-1 p-4 space-y-4 overflow-y-auto" >
86
149
{ activeConversation . messages . filter ( m => m . role !== 'system' ) . map ( ( message ) => {
87
150
const isUserMessage = message . role === 'user' ;
151
+ const isEditing = editingMessageId === message . id ;
88
152
89
153
// Define actions based on message type (user or AI)
90
154
const toolboxActions : ToolboxAction [ ] = isUserMessage
@@ -93,7 +157,7 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
93
157
id : 'edit' ,
94
158
icon : Pencil ,
95
159
label : 'Edit' ,
96
- onClick : ( ) => handleActionError ( 'edit message' ) ,
160
+ onClick : ( ) => handleEditMessage ( message . id , message . content ) ,
97
161
} ,
98
162
{
99
163
id : 'copy' ,
@@ -105,7 +169,7 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
105
169
id : 'delete' ,
106
170
icon : Trash2 ,
107
171
label : 'Delete' ,
108
- onClick : ( ) => handleActionError ( 'delete message' ) ,
172
+ onClick : ( ) => handleDeleteMessage ( message . id ) ,
109
173
}
110
174
]
111
175
: [
@@ -125,13 +189,13 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
125
189
id : 'regenerate' ,
126
190
icon : RotateCcw ,
127
191
label : 'Regenerate' ,
128
- onClick : ( ) => handleActionError ( 'regenerate response' ) ,
192
+ onClick : ( ) => handleRegenerateResponse ( ) ,
129
193
} ,
130
194
{
131
195
id : 'delete' ,
132
196
icon : Trash2 ,
133
197
label : 'Delete' ,
134
- onClick : ( ) => handleActionError ( 'delete message' ) ,
198
+ onClick : ( ) => handleDeleteMessage ( message . id ) ,
135
199
}
136
200
] ;
137
201
@@ -142,27 +206,56 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
142
206
onMouseEnter = { ( ) => setHoveredMessageId ( message . id ) }
143
207
onMouseLeave = { ( ) => setHoveredMessageId ( null ) }
144
208
>
145
- < div
146
- className = { `max-w-[80%] rounded-lg p-3 ${
147
- isUserMessage
148
- ? 'bg-blue-500 text-white rounded-tr-none'
149
- : 'bg-gray-200 text-gray-800 rounded-tl-none'
150
- } `}
151
- >
152
- { isUserMessage ? (
153
- < p className = "whitespace-pre-wrap" > { message . content } </ p >
154
- ) : (
155
- < MarkdownContent content = { message . content } />
156
- ) }
157
- </ div >
158
-
159
- { /* Message toolbox */ }
160
- < div className = "mt-1" >
161
- < MessageToolboxMenu
162
- actions = { toolboxActions }
163
- className = { `mr-1 ${ hoveredMessageId === message . id ? 'opacity-100' : 'opacity-0' } ` }
164
- />
165
- </ div >
209
+ { isEditing && isUserMessage ? (
210
+ < div className = "w-[80%]" >
211
+ < textarea
212
+ value = { editingContent }
213
+ onChange = { ( e ) => setEditingContent ( e . target . value ) }
214
+ className = "w-full px-3 py-2 border border-blue-400 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
215
+ rows = { 3 }
216
+ autoFocus
217
+ />
218
+ < div className = "flex justify-end mt-2 space-x-2" >
219
+ < button
220
+ onClick = { handleCancelEdit }
221
+ className = "px-3 py-1 text-sm text-gray-600 bg-gray-100 rounded hover:bg-gray-200"
222
+ >
223
+ Cancel
224
+ </ button >
225
+ < button
226
+ onClick = { handleSendEditedMessage }
227
+ className = "px-3 py-1 text-sm text-white bg-blue-500 rounded hover:bg-blue-600"
228
+ disabled = { ! editingContent . trim ( ) }
229
+ >
230
+ Send
231
+ </ button >
232
+ </ div >
233
+ </ div >
234
+ ) : (
235
+ < >
236
+ < div
237
+ className = { `max-w-[80%] rounded-lg p-3 ${
238
+ isUserMessage
239
+ ? 'bg-blue-500 text-white rounded-tr-none'
240
+ : 'bg-gray-200 text-gray-800 rounded-tl-none'
241
+ } `}
242
+ >
243
+ { isUserMessage ? (
244
+ < p className = "whitespace-pre-wrap" > { message . content } </ p >
245
+ ) : (
246
+ < MarkdownContent content = { message . content } />
247
+ ) }
248
+ </ div >
249
+
250
+ { /* Message toolbox */ }
251
+ < div className = "mt-1" >
252
+ < MessageToolboxMenu
253
+ actions = { toolboxActions }
254
+ className = { `mr-1 ${ hoveredMessageId === message . id ? 'opacity-100' : 'opacity-0' } ` }
255
+ />
256
+ </ div >
257
+ </ >
258
+ ) }
166
259
</ div >
167
260
) ;
168
261
} ) }
0 commit comments