Skip to content

Commit 8922010

Browse files
committed
Merge branch 'dev-production-milestone#1' of https://github.com/TensorBlock/TensorBlock-Studio into dev-production-milestone#1
2 parents 85be27c + 6c5be53 commit 8922010

File tree

5 files changed

+649
-25
lines changed

5 files changed

+649
-25
lines changed

src/components/chat/ChatMessageArea.tsx

Lines changed: 118 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ interface ChatMessageAreaProps {
1111
onSendMessage: (content: string) => void;
1212
onSendStreamingMessage?: (content: string) => void;
1313
onStopStreaming?: () => void;
14+
onRegenerateResponse?: () => void;
15+
onDeleteMessage?: (messageId: string) => void;
16+
onEditMessage?: (messageId: string, newContent: string) => void;
1417
isStreamingSupported?: boolean;
1518
isCurrentlyStreaming?: boolean;
1619
}
@@ -22,12 +25,17 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
2225
onSendMessage,
2326
onSendStreamingMessage,
2427
onStopStreaming,
28+
onRegenerateResponse,
29+
onDeleteMessage,
30+
onEditMessage,
2531
isStreamingSupported = false,
2632
isCurrentlyStreaming = false,
2733
}) => {
2834
const [input, setInput] = useState('');
2935
const messagesEndRef = useRef<HTMLDivElement>(null);
3036
const [hoveredMessageId, setHoveredMessageId] = useState<string | null>(null);
37+
const [editingMessageId, setEditingMessageId] = useState<string | null>(null);
38+
const [editingContent, setEditingContent] = useState('');
3139

3240
// Scroll to bottom when messages change
3341
useEffect(() => {
@@ -54,6 +62,61 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
5462
}
5563
};
5664

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+
57120
// Handle copy message action
58121
const handleCopyMessage = (content: string) => {
59122
navigator.clipboard.writeText(content)
@@ -85,6 +148,7 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
85148
<div className="flex-1 p-4 space-y-4 overflow-y-auto">
86149
{activeConversation.messages.filter(m => m.role !== 'system').map((message) => {
87150
const isUserMessage = message.role === 'user';
151+
const isEditing = editingMessageId === message.id;
88152

89153
// Define actions based on message type (user or AI)
90154
const toolboxActions: ToolboxAction[] = isUserMessage
@@ -93,7 +157,7 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
93157
id: 'edit',
94158
icon: Pencil,
95159
label: 'Edit',
96-
onClick: () => handleActionError('edit message'),
160+
onClick: () => handleEditMessage(message.id, message.content),
97161
},
98162
{
99163
id: 'copy',
@@ -105,7 +169,7 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
105169
id: 'delete',
106170
icon: Trash2,
107171
label: 'Delete',
108-
onClick: () => handleActionError('delete message'),
172+
onClick: () => handleDeleteMessage(message.id),
109173
}
110174
]
111175
: [
@@ -125,13 +189,13 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
125189
id: 'regenerate',
126190
icon: RotateCcw,
127191
label: 'Regenerate',
128-
onClick: () => handleActionError('regenerate response'),
192+
onClick: () => handleRegenerateResponse(),
129193
},
130194
{
131195
id: 'delete',
132196
icon: Trash2,
133197
label: 'Delete',
134-
onClick: () => handleActionError('delete message'),
198+
onClick: () => handleDeleteMessage(message.id),
135199
}
136200
];
137201

@@ -142,27 +206,56 @@ export const ChatMessageArea: React.FC<ChatMessageAreaProps> = ({
142206
onMouseEnter={() => setHoveredMessageId(message.id)}
143207
onMouseLeave={() => setHoveredMessageId(null)}
144208
>
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+
)}
166259
</div>
167260
);
168261
})}

src/pages/ChatPage.tsx

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,74 @@ export const ChatPage: React.FC<ChatPageProps> = ({
289289
}
290290
};
291291

292+
// Handle regenerating the last AI response
293+
const handleRegenerateResponse = async () => {
294+
if (!isServiceInitialized || !chatServiceRef.current) return;
295+
296+
try {
297+
// Use the new regenerateLastMessage method
298+
await chatServiceRef.current.regenerateLastMessage((updatedConversation) => {
299+
setConversations(updatedConversation);
300+
});
301+
} catch (error) {
302+
console.error('Error regenerating response:', error);
303+
}
304+
};
305+
306+
// Handle deleting a message
307+
const handleDeleteMessage = async (messageId: string) => {
308+
if (!isServiceInitialized || !chatServiceRef.current) return;
309+
310+
try {
311+
// Delete the message
312+
await chatServiceRef.current.deleteMessage(messageId, (updatedConversation) => {
313+
setConversations(updatedConversation);
314+
});
315+
} catch (error) {
316+
console.error('Error deleting message:', error);
317+
}
318+
};
319+
320+
// Handle editing a message
321+
const handleEditMessage = async (messageId: string, newContent: string) => {
322+
if (!isServiceInitialized || !chatServiceRef.current || !activeConversation) return;
323+
324+
try {
325+
// Find the message being edited
326+
const message = activeConversation.messages.find(m => m.id === messageId);
327+
328+
if (message && message.role === 'user') {
329+
// Delete this message and all subsequent messages
330+
const messageIndex = activeConversation.messages.findIndex(m => m.id === messageId);
331+
332+
// If this is not the last message, we need to delete all subsequent messages
333+
if (messageIndex < activeConversation.messages.length - 1) {
334+
// Delete from last to first to avoid index shifting issues
335+
for (let i = activeConversation.messages.length - 1; i > messageIndex; i--) {
336+
const msgToDelete = activeConversation.messages[i];
337+
await chatServiceRef.current.deleteMessage(msgToDelete.id, (updatedConversation) => {
338+
setConversations(updatedConversation);
339+
});
340+
}
341+
}
342+
343+
// Then delete the edited message itself
344+
await chatServiceRef.current.deleteMessage(messageId, (updatedConversation) => {
345+
setConversations(updatedConversation);
346+
});
347+
348+
// Finally, send the new message
349+
if (isStreamingSupported && useStreaming) {
350+
await handleSendStreamingMessage(newContent);
351+
} else {
352+
await handleSendMessage(newContent);
353+
}
354+
}
355+
} catch (error) {
356+
console.error('Error editing message:', error);
357+
}
358+
};
359+
292360
return (
293361
<div className="flex flex-col h-full bg-white">
294362

@@ -317,6 +385,9 @@ export const ChatPage: React.FC<ChatPageProps> = ({
317385
onSendMessage={handleSendMessage}
318386
onSendStreamingMessage={handleSendStreamingMessage}
319387
onStopStreaming={handleStopStreaming}
388+
onRegenerateResponse={handleRegenerateResponse}
389+
onDeleteMessage={handleDeleteMessage}
390+
onEditMessage={handleEditMessage}
320391
isStreamingSupported={isStreamingSupported && useStreaming}
321392
isCurrentlyStreaming={chatServiceRef.current?.isCurrentlyStreaming() || false}
322393
/>

0 commit comments

Comments
 (0)