edit message
This commit is contained in:
		
							parent
							
								
									e30336c00e
								
							
						
					
					
						commit
						a03d8b2ba9
					
				|  | @ -20,10 +20,13 @@ interface Props { | |||
|   onSend: (message: Message, isResend: boolean) => void; | ||||
|   onUpdateConversation: (conversation: Conversation, data: KeyValuePair) => void; | ||||
|   onAcceptEnv: (accept: boolean) => void; | ||||
|   onEditMessage: (message: Message, messageIndex: number) => void; | ||||
|   onDeleteMessage: (message: Message, messageIndex: number) => void; | ||||
|   onRegenerate: () => void; | ||||
|   stopConversationRef: MutableRefObject<boolean>; | ||||
| } | ||||
| 
 | ||||
| export const Chat: FC<Props> = ({ conversation, models, apiKey, isUsingEnv, messageIsStreaming, modelError, messageError, loading, lightMode, onSend, onUpdateConversation, onAcceptEnv, stopConversationRef }) => { | ||||
| export const Chat: FC<Props> = ({ conversation, models, apiKey, isUsingEnv, messageIsStreaming, modelError, messageError, loading, lightMode, onSend, onUpdateConversation, onAcceptEnv, onEditMessage, onDeleteMessage, onRegenerate, stopConversationRef }) => { | ||||
|   const [currentMessage, setCurrentMessage] = useState<Message>(); | ||||
|   const [autoScrollEnabled, setAutoScrollEnabled] = useState(true); | ||||
| 
 | ||||
|  | @ -122,7 +125,10 @@ export const Chat: FC<Props> = ({ conversation, models, apiKey, isUsingEnv, mess | |||
|                   <ChatMessage | ||||
|                     key={index} | ||||
|                     message={message} | ||||
|                     messageIndex={index} | ||||
|                     lightMode={lightMode} | ||||
|                     onEditMessage={onEditMessage} | ||||
|                     onDeleteMessage={onDeleteMessage} | ||||
|                   /> | ||||
|                 ))} | ||||
| 
 | ||||
|  | @ -149,11 +155,12 @@ export const Chat: FC<Props> = ({ conversation, models, apiKey, isUsingEnv, mess | |||
|               stopConversationRef={stopConversationRef} | ||||
|               textareaRef={textareaRef} | ||||
|               messageIsStreaming={messageIsStreaming} | ||||
|               model={conversation.model} | ||||
|               onSend={(message) => { | ||||
|                 setCurrentMessage(message); | ||||
|                 onSend(message, false); | ||||
|               }} | ||||
|               model={conversation.model} | ||||
|               onRegenerate={onRegenerate} | ||||
|             /> | ||||
|           )} | ||||
|         </> | ||||
|  |  | |||
|  | @ -1,11 +1,12 @@ | |||
| import { Message, OpenAIModel, OpenAIModelID } from "@/types"; | ||||
| import { IconPlayerStop, IconSend } from "@tabler/icons-react"; | ||||
| import { FC, KeyboardEvent, MutableRefObject, useEffect, useRef, useState } from "react"; | ||||
| import { FC, KeyboardEvent, MutableRefObject, useEffect, useState } from "react"; | ||||
| 
 | ||||
| interface Props { | ||||
|   messageIsStreaming: boolean; | ||||
|   onSend: (message: Message) => void; | ||||
|   model: OpenAIModel; | ||||
|   onSend: (message: Message) => void; | ||||
|   onRegenerate: () => void; | ||||
|   stopConversationRef: MutableRefObject<boolean>; | ||||
|   textareaRef: MutableRefObject<HTMLTextAreaElement | null>; | ||||
| } | ||||
|  | @ -67,7 +68,6 @@ export const ChatInput: FC<Props> = ({ onSend, messageIsStreaming, model, stopCo | |||
|     } | ||||
|   }, [content]); | ||||
| 
 | ||||
| 
 | ||||
|   function handleStopConversation() { | ||||
|     stopConversationRef.current = true; | ||||
|     setTimeout(() => { | ||||
|  |  | |||
|  | @ -1,26 +1,119 @@ | |||
| import { Message } from "@/types"; | ||||
| import { FC } from "react"; | ||||
| import { IconEdit } from "@tabler/icons-react"; | ||||
| import { FC, useEffect, useRef, useState } from "react"; | ||||
| import ReactMarkdown from "react-markdown"; | ||||
| import remarkGfm from "remark-gfm"; | ||||
| import { CodeBlock } from "../Markdown/CodeBlock"; | ||||
| 
 | ||||
| interface Props { | ||||
|   message: Message; | ||||
|   messageIndex: number; | ||||
|   lightMode: "light" | "dark"; | ||||
|   onEditMessage: (message: Message, messageIndex: number) => void; | ||||
|   onDeleteMessage: (message: Message, messageIndex: number) => void; | ||||
| } | ||||
| 
 | ||||
| export const ChatMessage: FC<Props> = ({ message, lightMode }) => { | ||||
| export const ChatMessage: FC<Props> = ({ message, messageIndex, lightMode, onEditMessage, onDeleteMessage }) => { | ||||
|   const [isEditing, setIsEditing] = useState<boolean>(false); | ||||
|   const [isHovering, setIsHovering] = useState<boolean>(false); | ||||
|   const [messageContent, setMessageContent] = useState(message.content); | ||||
| 
 | ||||
|   const textareaRef = useRef<HTMLTextAreaElement>(null); | ||||
| 
 | ||||
|   const toggleEditing = () => { | ||||
|     setIsEditing(!isEditing); | ||||
|   }; | ||||
| 
 | ||||
|   const handleInputChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => { | ||||
|     setMessageContent(event.target.value); | ||||
|     if (textareaRef.current) { | ||||
|       textareaRef.current.style.height = "inherit"; | ||||
|       textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   const handleEditMessage = () => { | ||||
|     onEditMessage({ ...message, content: messageContent }, messageIndex); | ||||
|     setIsEditing(false); | ||||
|   }; | ||||
| 
 | ||||
|   const handlePressEnter = (e: React.KeyboardEvent<HTMLTextAreaElement>) => { | ||||
|     if (e.key === "Enter" && !e.shiftKey) { | ||||
|       e.preventDefault(); | ||||
|       handleEditMessage(); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     if (textareaRef.current) { | ||||
|       textareaRef.current.style.height = "inherit"; | ||||
|       textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`; | ||||
|     } | ||||
|   }, [isEditing]); | ||||
| 
 | ||||
|   return ( | ||||
|     <div | ||||
|       className={`group ${message.role === "assistant" ? "text-gray-800 dark:text-gray-100 border-b border-black/10 dark:border-gray-900/50 bg-gray-50 dark:bg-[#444654]" : "text-gray-800 dark:text-gray-100 border-b border-black/10 dark:border-gray-900/50 bg-white dark:bg-[#343541]"}`} | ||||
|       style={{ overflowWrap: "anywhere" }} | ||||
|       onMouseEnter={() => setIsHovering(true)} | ||||
|       onMouseLeave={() => setIsHovering(false)} | ||||
|     > | ||||
|       <div className="text-base gap-4 md:gap-6 md:max-w-2xl lg:max-w-2xl xl:max-w-3xl p-4 md:py-6 flex lg:px-0 m-auto"> | ||||
|       <div className="text-base gap-4 md:gap-6 md:max-w-2xl lg:max-w-2xl xl:max-w-3xl p-4 md:py-6 flex lg:px-0 m-auto relative"> | ||||
|         <div className="font-bold min-w-[40px]">{message.role === "assistant" ? "AI:" : "You:"}</div> | ||||
| 
 | ||||
|         <div className="prose dark:prose-invert mt-[-2px]"> | ||||
|         <div className="prose dark:prose-invert mt-[-2px] w-full"> | ||||
|           {message.role === "user" ? ( | ||||
|             <div className="prose dark:prose-invert whitespace-pre-wrap">{message.content}</div> | ||||
|             <div className="flex w-full"> | ||||
|               {isEditing ? ( | ||||
|                 <div className="flex flex-col w-full"> | ||||
|                   <textarea | ||||
|                     ref={textareaRef} | ||||
|                     className="w-full dark:bg-[#343541] border-none resize-none outline-none whitespace-pre-wrap" | ||||
|                     value={messageContent} | ||||
|                     onChange={handleInputChange} | ||||
|                     onKeyDown={handlePressEnter} | ||||
|                     style={{ | ||||
|                       fontFamily: "inherit", | ||||
|                       fontSize: "inherit", | ||||
|                       lineHeight: "inherit", | ||||
|                       padding: "0", | ||||
|                       margin: "0", | ||||
|                       overflow: "hidden" | ||||
|                     }} | ||||
|                   /> | ||||
| 
 | ||||
|                   <div className="flex mt-10 justify-center space-x-4"> | ||||
|                     <button | ||||
|                       className="h-[40px] bg-blue-500 text-white rounded-md px-4 py-1 text-sm font-medium hover:bg-blue-600" | ||||
|                       onClick={handleEditMessage} | ||||
|                     > | ||||
|                       Save & Submit | ||||
|                     </button> | ||||
|                     <button | ||||
|                       className="h-[40px] border border-neutral-300 dark:border-neutral-700 rounded-md px-4 py-1 text-sm font-medium text-neutral-700 dark:text-neutral-300 hover:bg-neutral-100 dark:hover:bg-neutral-800" | ||||
|                       onClick={() => { | ||||
|                         setMessageContent(message.content); | ||||
|                         setIsEditing(false); | ||||
|                       }} | ||||
|                     > | ||||
|                       Cancel | ||||
|                     </button> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               ) : ( | ||||
|                 <div className="prose dark:prose-invert whitespace-pre-wrap">{message.content}</div> | ||||
|               )} | ||||
| 
 | ||||
|               {isHovering && !isEditing && ( | ||||
|                 <button className="absolute right-[-20px] top-[26px]"> | ||||
|                   <IconEdit | ||||
|                     size={20} | ||||
|                     className="text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300" | ||||
|                     onClick={toggleEditing} | ||||
|                   /> | ||||
|                 </button> | ||||
|               )} | ||||
|             </div> | ||||
|           ) : ( | ||||
|             <ReactMarkdown | ||||
|               remarkPlugins={[remarkGfm]} | ||||
|  |  | |||
|  | @ -24,6 +24,7 @@ export default function Home() { | |||
|   const [messageError, setMessageError] = useState<boolean>(false); | ||||
|   const [modelError, setModelError] = useState<boolean>(false); | ||||
|   const [isUsingEnv, setIsUsingEnv] = useState<boolean>(false); | ||||
|   const [currentMessage, setCurrentMessage] = useState<Message>(); | ||||
| 
 | ||||
|   const stopConversationRef = useRef<boolean>(false); | ||||
| 
 | ||||
|  | @ -352,6 +353,41 @@ export default function Home() { | |||
|     localStorage.removeItem("isUsingEnv"); | ||||
|   }; | ||||
| 
 | ||||
|   const handleEditMessage = (message: Message, messageIndex: number) => { | ||||
|     if (selectedConversation) { | ||||
|       const updatedMessages = selectedConversation.messages | ||||
|         .map((m, i) => { | ||||
|           if (i < messageIndex) { | ||||
|             return m; | ||||
|           } | ||||
|         }) | ||||
|         .filter((m) => m) as Message[]; | ||||
| 
 | ||||
|       const updatedConversation = { | ||||
|         ...selectedConversation, | ||||
|         messages: updatedMessages | ||||
|       }; | ||||
| 
 | ||||
|       const { single, all } = updateConversation(updatedConversation, conversations); | ||||
| 
 | ||||
|       setSelectedConversation(single); | ||||
|       setConversations(all); | ||||
| 
 | ||||
|       setCurrentMessage(message); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   const handleDeleteMessage = (message: Message, messageIndex: number) => {}; | ||||
| 
 | ||||
|   const handleRegenerate = () => {}; | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     if (currentMessage) { | ||||
|       handleSend(currentMessage, false); | ||||
|       setCurrentMessage(undefined); | ||||
|     } | ||||
|   }, [currentMessage]); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     if (window.innerWidth < 640) { | ||||
|       setShowSidebar(false); | ||||
|  | @ -491,6 +527,9 @@ export default function Home() { | |||
|               onSend={handleSend} | ||||
|               onUpdateConversation={handleUpdateConversation} | ||||
|               onAcceptEnv={handleEnvChange} | ||||
|               onEditMessage={handleEditMessage} | ||||
|               onDeleteMessage={handleDeleteMessage} | ||||
|               onRegenerate={handleRegenerate} | ||||
|               stopConversationRef={stopConversationRef} | ||||
|             /> | ||||
|           </article> | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue