diff --git a/src/assets/RegenerateIcon.tsx b/src/assets/RegenerateIcon.tsx new file mode 100644 index 0000000..ce6274b --- /dev/null +++ b/src/assets/RegenerateIcon.tsx @@ -0,0 +1,20 @@ +import * as React from "react"; +import { SVGProps } from "react"; + +const RegenerateIcon = (props: SVGProps) => ( + + + +); + +export default RegenerateIcon; diff --git a/src/components/chat/ChatScreen.tsx b/src/components/chat/ChatScreen.tsx index 6861ad1..a575dfb 100644 --- a/src/components/chat/ChatScreen.tsx +++ b/src/components/chat/ChatScreen.tsx @@ -32,6 +32,7 @@ type ChatProps = { loading: boolean; streamLoading: boolean; stopGenerating: () => void; + setMessages: React.Dispatch>; }; const blippy = authorsConfig[0]; @@ -45,6 +46,7 @@ const ChatScreen = ({ loading, streamLoading, stopGenerating, + setMessages, }: ChatProps) => { const messageListRef = useRef(null); const textAreaRef = useRef(null); @@ -127,6 +129,33 @@ const ChatScreen = ({ startChat(question, author.slug, { startChat: true }); }; + const handleRegenerateResponse = (messageId: string) => { + // Find the index of the API message to regenerate + const apiMessageIndex = messages.findIndex( + (msg) => msg.uniqueId === messageId && msg.type === "apiMessage" + ); + + if (apiMessageIndex === -1) return; + + // Find the previous user message + let userMessageIndex = apiMessageIndex - 1; + while (userMessageIndex >= 0 && messages[userMessageIndex].type !== "userMessage") { + userMessageIndex--; + } + + if (userMessageIndex === -1) return; + + const userMessage = messages[userMessageIndex].message; + + // Remove the API message from messages array + setMessages((prevMessages) => + prevMessages.filter((msg) => msg.uniqueId !== messageId) + ); + + // Regenerate the response + startChat(userMessage, author.slug, { startChat: true }); + }; + useEffect(() => { const messageListBox = messageListRef.current; if (!messageListBox) return; @@ -288,6 +317,7 @@ const ChatScreen = ({ author={author.name} content={message} handleFollowUpQuestion={handleFollowUpQuestion} + handleRegenerateResponse={handleRegenerateResponse} /> {isApiMessage && ( )} diff --git a/src/components/message/markdownWrapper/markdownWrapper.tsx b/src/components/message/markdownWrapper/markdownWrapper.tsx index 3b3503e..6a3c7d8 100644 --- a/src/components/message/markdownWrapper/markdownWrapper.tsx +++ b/src/components/message/markdownWrapper/markdownWrapper.tsx @@ -4,6 +4,7 @@ import atomDark from "react-syntax-highlighter/dist/cjs/styles/prism/atom-dark"; import styles from ".././message.module.css"; import Image from "next/image"; import CopyIcon from "@/assets/CopyIcon"; +import RegenerateIcon from "@/assets/RegenerateIcon"; import { useState } from "react"; import rehypeInlineCodeProperty from "../../../utils/rehypeInlineCodeProperty"; import { useCopyToClipboard } from "usehooks-ts"; @@ -62,6 +63,29 @@ export const CopyResponseButton = ({ ); }; + +export const RegenerateButton = ({ + onRegenerate, + isDisabled, +}: { + onRegenerate: () => void; + isDisabled?: boolean; +}) => { + return ( + + Regenerate + + + ); +}; + const MarkdownWrapper = ({ text, className, diff --git a/src/components/message/message.tsx b/src/components/message/message.tsx index 562aa92..6a602fc 100644 --- a/src/components/message/message.tsx +++ b/src/components/message/message.tsx @@ -8,6 +8,7 @@ import { separateLinksFromApiMessage } from "@/utils/links"; import MarkdownWrapper, { CopyButton, CopyResponseButton, + RegenerateButton, } from "./markdownWrapper/markdownWrapper"; type MessageType = @@ -64,12 +65,14 @@ const MessageBox = ({ loading, streamLoading, handleFollowUpQuestion, + handleRegenerateResponse, }: { content: Message; author: string; loading?: boolean; streamLoading?: boolean; handleFollowUpQuestion: (question: string) => void; + handleRegenerateResponse?: (messageId: string) => void; }) => { const { message, type } = content; @@ -96,15 +99,19 @@ const MessageBox = ({ ) : ( )} @@ -166,8 +173,15 @@ const ClickableQuestions = ({ const MessageContent = ({ message, type, + uniqueId, handleFollowUpQuestion, -}: Message & { handleFollowUpQuestion: (question: string) => void }) => { + handleRegenerateResponse, + isLoading, +}: Message & { + handleFollowUpQuestion: (question: string) => void; + handleRegenerateResponse?: (messageId: string) => void; + isLoading?: boolean; +}) => { if (!message?.trim()) return null; const { messageBody, messageLinks, messageQuestions, isErrorMessage } = separateLinksFromApiMessage(message); @@ -225,9 +239,14 @@ const MessageContent = ({ )} {showCopyIcon && ( - - + + {handleRegenerateResponse && ( + handleRegenerateResponse(uniqueId)} + isDisabled={isLoading} + /> + )} )} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 3c74b6e..02daa95 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -285,6 +285,7 @@ export default function Home() { loading={loading} streamLoading={streamLoading} stopGenerating={abortGeneration} + setMessages={setMessages} /> ) : (