edit message

This commit is contained in:
Mckay Wrigley 2023-03-25 05:49:41 -06:00
parent e30336c00e
commit a03d8b2ba9
4 changed files with 149 additions and 10 deletions

View File

@ -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}
/>
)}
</>

View File

@ -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(() => {

View File

@ -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="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]}

View File

@ -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>