Skip to content

Commit d3a03ef

Browse files
committed
edit support
1 parent c9c8525 commit d3a03ef

File tree

3 files changed

+120
-6
lines changed

3 files changed

+120
-6
lines changed

src/App.jsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,27 @@ function App() {
102102
worker.current.postMessage({ type: "interrupt" });
103103
}
104104

105+
function onEditMessage(messageIndex, newContent) {
106+
// Update the message content
107+
const updatedMessages = [...messages];
108+
updatedMessages[messageIndex] = { ...updatedMessages[messageIndex], content: newContent };
109+
110+
// Remove all messages after the edited message
111+
const messagesUpToEdit = updatedMessages.slice(0, messageIndex + 1);
112+
113+
// Set the updated messages
114+
setMessages(messagesUpToEdit);
115+
116+
// Clear any states related to generation
117+
setTps(null);
118+
setNumTokens(null);
119+
120+
// If the model is ready, start generation with the new message
121+
if (status === "ready") {
122+
setIsRunning(true);
123+
}
124+
}
125+
105126
function handleModelChange(modelId) {
106127
if (modelId === selectedModel) return;
107128
if (isRunning || status === "loading") return; // Prevent model switching during text generation or loading
@@ -380,6 +401,7 @@ function App() {
380401
isRunning={isRunning}
381402
loading={status === "loading"}
382403
selectedModel={selectedModel}
404+
onEditMessage={onEditMessage}
383405
/>
384406
{messages.length === 0 && (status === "ready" || status === null) && (
385407
<div>

src/components/Chat.jsx

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
33
import { oneDark, oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism';
44

55
import CopyIcon from "./icons/CopyIcon";
6+
import PencilIcon from "./icons/PencilIcon";
67
import ThinkBlock from "./ThinkBlock";
78
import { AVAILABLE_MODELS } from "./ModelSelector";
89

@@ -196,9 +197,11 @@ function MarkdownWithSyntaxHighlighting({ content, isDark, isGenerating = false,
196197
);
197198
}
198199

199-
export default function Chat({ messages, isRunning, loading = false, selectedModel }) {
200+
export default function Chat({ messages, isRunning, loading = false, selectedModel, onEditMessage }) {
200201
const empty = messages.length === 0;
201202
const [copiedMessageIndex, setCopiedMessageIndex] = useState(null);
203+
const [editingMessageIndex, setEditingMessageIndex] = useState(null);
204+
const [editingMessageText, setEditingMessageText] = useState("");
202205

203206
// Detect if dark theme is active
204207
const isDark = useMemo(() => {
@@ -221,6 +224,25 @@ export default function Chat({ messages, isRunning, loading = false, selectedMod
221224
}
222225
};
223226

227+
// Edit message functions
228+
const startEditing = (messageIndex, messageText) => {
229+
setEditingMessageIndex(messageIndex);
230+
setEditingMessageText(messageText);
231+
};
232+
233+
const cancelEditing = () => {
234+
setEditingMessageIndex(null);
235+
setEditingMessageText("");
236+
};
237+
238+
const submitEdit = () => {
239+
if (editingMessageText.trim() && onEditMessage) {
240+
onEditMessage(editingMessageIndex, editingMessageText.trim());
241+
setEditingMessageIndex(null);
242+
setEditingMessageText("");
243+
}
244+
};
245+
224246
useEffect(() => {
225247
// Handle MathJax
226248
if (window.MathJax) {
@@ -235,7 +257,7 @@ export default function Chat({ messages, isRunning, loading = false, selectedMod
235257
} ${empty ? "flex flex-col items-center justify-end" : "space-y-4"}`}
236258
>
237259
{messages.map((msg, i) => (
238-
<div key={`message-${i}`} className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}>
260+
<div key={`message-${i}`} className={`flex ${msg.role === "user" ? (editingMessageIndex === i ? "justify-start" : "justify-end") : "justify-start"}`}>
239261
{msg.role === "assistant" ? (
240262
<div className="relative group w-full max-w-none">
241263
<div className="min-h-6 text-gray-800 dark:text-gray-200 overflow-wrap-anywhere w-full">
@@ -268,10 +290,62 @@ export default function Chat({ messages, isRunning, loading = false, selectedMod
268290
)}
269291
</div>
270292
) : (
271-
<div className="bg-gray-100 dark:bg-gray-600 text-gray-800 dark:text-gray-200 rounded-2xl px-4 py-2 max-w-xs sm:max-w-sm md:max-w-lg lg:max-w-xl">
272-
<div className="min-h-6 break-words">
273-
{msg.content}
274-
</div>
293+
<div className={`relative group ${editingMessageIndex === i ? 'w-full' : ''}`}>
294+
{editingMessageIndex === i ? (
295+
// Editing mode
296+
<div className="bg-gray-100 dark:bg-gray-600 rounded-2xl px-4 py-2 w-full">
297+
<textarea
298+
value={editingMessageText}
299+
onChange={(e) => setEditingMessageText(e.target.value)}
300+
className="w-full min-h-[60px] bg-transparent border-none outline-none text-gray-800 dark:text-gray-200 resize-none"
301+
autoFocus
302+
onKeyDown={(e) => {
303+
if (e.key === 'Enter' && !e.shiftKey) {
304+
e.preventDefault();
305+
submitEdit();
306+
} else if (e.key === 'Escape') {
307+
cancelEditing();
308+
}
309+
}}
310+
/>
311+
<div className="flex gap-2 mt-2">
312+
<button
313+
onClick={cancelEditing}
314+
className="px-2 py-1 text-xs bg-gray-200 dark:bg-gray-500 text-gray-700 dark:text-gray-200 rounded hover:bg-gray-300 dark:hover:bg-gray-400 transition-colors"
315+
>
316+
Cancel
317+
</button>
318+
<button
319+
onClick={submitEdit}
320+
className="px-2 py-1 text-xs bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors"
321+
disabled={!editingMessageText.trim()}
322+
>
323+
Edit
324+
</button>
325+
</div>
326+
</div>
327+
) : (
328+
// Normal display mode
329+
<div>
330+
<div className="bg-gray-100 dark:bg-gray-600 text-gray-800 dark:text-gray-200 rounded-2xl px-4 py-2 max-w-xs sm:max-w-sm md:max-w-lg lg:max-w-xl">
331+
<div className="min-h-6 break-words">
332+
{msg.content}
333+
</div>
334+
</div>
335+
{!isRunning && (
336+
<div className="flex justify-end mt-1 opacity-0 group-hover:opacity-100 transition-opacity">
337+
<button
338+
onClick={() => startEditing(i, msg.content)}
339+
className="flex items-center gap-1 px-2 py-1 text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-700 rounded transition-colors"
340+
title="Edit message"
341+
>
342+
<PencilIcon className="h-3 w-3" />
343+
Edit
344+
</button>
345+
</div>
346+
)}
347+
</div>
348+
)}
275349
</div>
276350
)}
277351
</div>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export default function PencilIcon({ className }) {
2+
return (
3+
<svg
4+
className={className}
5+
fill="none"
6+
stroke="currentColor"
7+
viewBox="0 0 24 24"
8+
xmlns="http://www.w3.org/2000/svg"
9+
>
10+
<path
11+
strokeLinecap="round"
12+
strokeLinejoin="round"
13+
strokeWidth={2}
14+
d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"
15+
/>
16+
</svg>
17+
);
18+
}

0 commit comments

Comments
 (0)