diff --git a/.gitignore b/.gitignore index c87c9b3..b167e2d 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts +.idea diff --git a/components/Chat/Chat.tsx b/components/Chat/Chat.tsx index a78f3cd..61c6510 100644 --- a/components/Chat/Chat.tsx +++ b/components/Chat/Chat.tsx @@ -1,5 +1,5 @@ import { Conversation, KeyValuePair, Message, OpenAIModel } from "@/types"; -import { FC, useEffect, useRef, useState } from "react"; +import { FC, MutableRefObject, useEffect, useRef, useState } from 'react'; import { ChatInput } from "./ChatInput"; import { ChatLoader } from "./ChatLoader"; import { ChatMessage } from "./ChatMessage"; @@ -17,9 +17,10 @@ interface Props { lightMode: "light" | "dark"; onSend: (message: Message, isResend: boolean) => void; onUpdateConversation: (conversation: Conversation, data: KeyValuePair) => void; + stopConversationRef: MutableRefObject } -export const Chat: FC = ({ conversation, models, messageIsStreaming, modelError, messageError, loading, lightMode, onSend, onUpdateConversation }) => { +export const Chat: FC = ({ conversation, models, messageIsStreaming, modelError, messageError, loading, lightMode, onSend, onUpdateConversation, stopConversationRef }) => { const [currentMessage, setCurrentMessage] = useState(); const messagesEndRef = useRef(null); @@ -96,6 +97,7 @@ export const Chat: FC = ({ conversation, models, messageIsStreaming, mode /> ) : ( { setCurrentMessage(message); diff --git a/components/Chat/ChatInput.tsx b/components/Chat/ChatInput.tsx index d1e8310..a498dab 100644 --- a/components/Chat/ChatInput.tsx +++ b/components/Chat/ChatInput.tsx @@ -1,14 +1,15 @@ import { Message, OpenAIModel, OpenAIModelID } from "@/types"; -import { IconSend } from "@tabler/icons-react"; -import { FC, KeyboardEvent, useEffect, useRef, useState } from "react"; +import { IconHandStop, IconSend } from "@tabler/icons-react"; +import { FC, KeyboardEvent, MutableRefObject, useEffect, useRef, useState } from "react"; interface Props { messageIsStreaming: boolean; onSend: (message: Message) => void; model: OpenAIModel; + stopConversationRef: MutableRefObject; } -export const ChatInput: FC = ({ onSend, messageIsStreaming, model }) => { +export const ChatInput: FC = ({ onSend, messageIsStreaming, model, stopConversationRef }) => { const [content, setContent] = useState(); const [isTyping, setIsTyping] = useState(false); @@ -67,6 +68,13 @@ export const ChatInput: FC = ({ onSend, messageIsStreaming, model }) => { } }, [content]); + function handleStopConversation() { + stopConversationRef.current = true; + setTimeout(() => { + stopConversationRef.current = false; + }, 1000); + } + return (
@@ -98,6 +106,17 @@ export const ChatInput: FC = ({ onSend, messageIsStreaming, model }) => { className="opacity-60" /> + {messageIsStreaming ? ( + + ) : null}
diff --git a/pages/index.tsx b/pages/index.tsx index 2eaf84f..d2f69fe 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -8,7 +8,7 @@ import { saveConversation, saveConversations, updateConversation } from "@/utils import { exportConversations, importConversations } from "@/utils/app/data"; import { IconArrowBarLeft, IconArrowBarRight } from "@tabler/icons-react"; import Head from "next/head"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from 'react'; export default function Home() { const [conversations, setConversations] = useState([]); @@ -21,6 +21,7 @@ export default function Home() { const [apiKey, setApiKey] = useState(""); const [messageError, setMessageError] = useState(false); const [modelError, setModelError] = useState(false); + const stopConversationRef = useRef(false); const handleSend = async (message: Message, isResend: boolean) => { if (selectedConversation) { @@ -53,11 +54,13 @@ export default function Home() { prompt: updatedConversation.prompt }; + const controller = new AbortController() const response = await fetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, + signal: controller.signal, body: JSON.stringify(chatBody) }); @@ -87,6 +90,11 @@ export default function Home() { let text = ""; while (!done) { + if (stopConversationRef.current === true) { + controller.abort(); + done = true; + break; + } const { value, done: doneReading } = await reader.read(); done = doneReading; const chunkValue = decoder.decode(value); @@ -385,6 +393,7 @@ export default function Home() { lightMode={lightMode} onSend={handleSend} onUpdateConversation={handleUpdateConversation} + stopConversationRef={stopConversationRef} />