diff --git a/src/interface/web/app/chat/page.tsx b/src/interface/web/app/chat/page.tsx index 966cdd17a..56c53d1b6 100644 --- a/src/interface/web/app/chat/page.tsx +++ b/src/interface/web/app/chat/page.tsx @@ -38,6 +38,7 @@ interface ChatBodyDataProps { isMobileWidth?: boolean; isLoggedIn: boolean; setImages: (images: string[]) => void; + setTriggeredAbort: (triggeredAbort: boolean) => void; } function ChatBodyData(props: ChatBodyDataProps) { @@ -160,6 +161,7 @@ function ChatBodyData(props: ChatBodyDataProps) { setUploadedFiles={props.setUploadedFiles} ref={chatInputRef} isResearchModeEnabled={isInResearchMode} + setTriggeredAbort={props.setTriggeredAbort} /> @@ -178,6 +180,10 @@ export default function Chat() { const [uploadedFiles, setUploadedFiles] = useState(undefined); const [images, setImages] = useState([]); + const [abortMessageStreamController, setAbortMessageStreamController] = + useState(null); + const [triggeredAbort, setTriggeredAbort] = useState(false); + const locationData = useIPLocationData() || { timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, }; @@ -202,6 +208,15 @@ export default function Chat() { welcomeConsole(); }, []); + useEffect(() => { + if (triggeredAbort) { + abortMessageStreamController?.abort(); + handleAbortedMessage(); + setTriggeredAbort(false); + } + }), + [triggeredAbort]; + useEffect(() => { if (queryToProcess) { const newStreamMessage: StreamMessage = { @@ -218,6 +233,7 @@ export default function Chat() { }; setMessages((prevMessages) => [...prevMessages, newStreamMessage]); setProcessQuerySignal(true); + setAbortMessageStreamController(new AbortController()); } }, [queryToProcess]); @@ -283,6 +299,17 @@ export default function Chat() { } } + function handleAbortedMessage() { + const currentMessage = messages.find((message) => !message.completed); + if (!currentMessage) return; + + currentMessage.rawResponse = `I've stopped processing this message. If you'd like to continue, please send another message.`; + currentMessage.completed = true; + setMessages([...messages]); + setQueryToProcess(""); + setProcessQuerySignal(false); + } + async function chat() { localStorage.removeItem("message"); if (!queryToProcess || !conversationId) return; @@ -308,6 +335,7 @@ export default function Chat() { "Content-Type": "application/json", }, body: JSON.stringify(chatAPIBody), + signal: abortMessageStreamController?.signal, }); try { @@ -321,14 +349,18 @@ export default function Chat() { // Render error message as current message const errorMessage = (err as Error).message; + const errorName = (err as Error).name; if (errorMessage.includes("Error in input stream")) currentMessage.rawResponse = `Woops! The connection broke while I was writing my thoughts down. Maybe try again in a bit or dislike this message if the issue persists?`; else if (response.status === 429) { "detail" in apiError ? (currentMessage.rawResponse = `${apiError.detail}`) : (currentMessage.rawResponse = `I'm a bit overwhelmed at the moment. Could you try again in a bit or dislike this message if the issue persists?`); - } else + } else if (errorName === "AbortError") { + currentMessage.rawResponse = `I've stopped processing this message. If you'd like to continue, please send the message again.`; + } else { currentMessage.rawResponse = `Umm, not sure what just happened. I see this error message: ${errorMessage}. Could you try again or dislike this message if the issue persists?`; + } // Complete message streaming teardown properly currentMessage.completed = true; @@ -388,6 +420,7 @@ export default function Chat() { isMobileWidth={isMobileWidth} onConversationIdChange={handleConversationIdChange} setImages={setImages} + setTriggeredAbort={setTriggeredAbort} /> diff --git a/src/interface/web/app/components/chatInputArea/chatInputArea.tsx b/src/interface/web/app/components/chatInputArea/chatInputArea.tsx index af54257d5..74be18379 100644 --- a/src/interface/web/app/components/chatInputArea/chatInputArea.tsx +++ b/src/interface/web/app/components/chatInputArea/chatInputArea.tsx @@ -76,6 +76,7 @@ interface ChatInputProps { isLoggedIn: boolean; agentColor?: string; isResearchModeEnabled?: boolean; + setTriggeredAbort: (value: boolean) => void; } export const ChatInputArea = forwardRef((props, ref) => { @@ -678,20 +679,33 @@ export const ChatInputArea = forwardRef((pr - + {props.sendDisabled ? ( + + ) : ( + + )} - Click to transcribe your message with voice. + {props.sendDisabled + ? "Click here to stop the streaming." + : "Click to transcribe your message with voice."} @@ -699,7 +713,6 @@ export const ChatInputArea = forwardRef((pr diff --git a/src/interface/web/app/page.tsx b/src/interface/web/app/page.tsx index 98d3a47ef..ba4984910 100644 --- a/src/interface/web/app/page.tsx +++ b/src/interface/web/app/page.tsx @@ -312,6 +312,7 @@ function ChatBodyData(props: ChatBodyDataProps) { setUploadedFiles={props.setUploadedFiles} agentColor={agents.find((agent) => agent.slug === selectedAgent)?.color} ref={chatInputRef} + setTriggeredAbort={() => {}} /> )} @@ -394,6 +395,7 @@ function ChatBodyData(props: ChatBodyDataProps) { setUploadedFiles={props.setUploadedFiles} agentColor={agents.find((agent) => agent.slug === selectedAgent)?.color} ref={chatInputRef} + setTriggeredAbort={() => {}} /> diff --git a/src/interface/web/app/share/chat/page.tsx b/src/interface/web/app/share/chat/page.tsx index 2a956083a..06a75303d 100644 --- a/src/interface/web/app/share/chat/page.tsx +++ b/src/interface/web/app/share/chat/page.tsx @@ -101,6 +101,7 @@ function ChatBodyData(props: ChatBodyDataProps) { agentColor={agentMetadata?.color} isMobileWidth={props.isMobileWidth} setUploadedFiles={props.setUploadedFiles} + setTriggeredAbort={() => {}} ref={chatInputRef} />