mirror of
https://github.com/khoj-ai/khoj.git
synced 2024-11-23 23:48:56 +01:00
Add support for interrupting messages after they've been sent.
This commit is contained in:
parent
d607ad7a27
commit
4a1b1e8b9a
4 changed files with 63 additions and 14 deletions
|
@ -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}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
@ -178,6 +180,10 @@ export default function Chat() {
|
|||
const [uploadedFiles, setUploadedFiles] = useState<AttachedFileText[] | undefined>(undefined);
|
||||
const [images, setImages] = useState<string[]>([]);
|
||||
|
||||
const [abortMessageStreamController, setAbortMessageStreamController] =
|
||||
useState<AbortController | null>(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}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
|
|
|
@ -76,6 +76,7 @@ interface ChatInputProps {
|
|||
isLoggedIn: boolean;
|
||||
agentColor?: string;
|
||||
isResearchModeEnabled?: boolean;
|
||||
setTriggeredAbort: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export const ChatInputArea = forwardRef<HTMLTextAreaElement, ChatInputProps>((props, ref) => {
|
||||
|
@ -678,6 +679,17 @@ export const ChatInputArea = forwardRef<HTMLTextAreaElement, ChatInputProps>((pr
|
|||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
{props.sendDisabled ? (
|
||||
<Button
|
||||
variant="default"
|
||||
className={`${props.agentColor ? convertToBGClass(props.agentColor) : "bg-orange-300 hover:bg-orange-500"} rounded-full p-1 m-2 h-auto text-3xl transition transform md:hover:-translate-y-1`}
|
||||
onClick={() => {
|
||||
props.setTriggeredAbort(true);
|
||||
}}
|
||||
>
|
||||
<Stop weight="fill" className="w-6 h-6" />
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="default"
|
||||
className={`${!message || recording || "hidden"} ${props.agentColor ? convertToBGClass(props.agentColor) : "bg-orange-300 hover:bg-orange-500"} rounded-full p-1 m-2 h-auto text-3xl transition transform md:hover:-translate-y-1`}
|
||||
|
@ -685,13 +697,15 @@ export const ChatInputArea = forwardRef<HTMLTextAreaElement, ChatInputProps>((pr
|
|||
setMessage("Listening...");
|
||||
setRecording(!recording);
|
||||
}}
|
||||
disabled={props.sendDisabled}
|
||||
>
|
||||
<Microphone weight="fill" className="w-6 h-6" />
|
||||
</Button>
|
||||
)}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Click to transcribe your message with voice.
|
||||
{props.sendDisabled
|
||||
? "Click here to stop the streaming."
|
||||
: "Click to transcribe your message with voice."}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
@ -699,7 +713,6 @@ export const ChatInputArea = forwardRef<HTMLTextAreaElement, ChatInputProps>((pr
|
|||
<Button
|
||||
className={`${(!message || recording) && "hidden"} ${props.agentColor ? convertToBGClass(props.agentColor) : "bg-orange-300 hover:bg-orange-500"} rounded-full p-1 m-2 h-auto text-3xl transition transform md:hover:-translate-y-1`}
|
||||
onClick={onSendMessage}
|
||||
disabled={props.sendDisabled}
|
||||
>
|
||||
<ArrowUp className="w-6 h-6" weight="bold" />
|
||||
</Button>
|
||||
|
|
|
@ -312,6 +312,7 @@ function ChatBodyData(props: ChatBodyDataProps) {
|
|||
setUploadedFiles={props.setUploadedFiles}
|
||||
agentColor={agents.find((agent) => agent.slug === selectedAgent)?.color}
|
||||
ref={chatInputRef}
|
||||
setTriggeredAbort={() => {}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
@ -394,6 +395,7 @@ function ChatBodyData(props: ChatBodyDataProps) {
|
|||
setUploadedFiles={props.setUploadedFiles}
|
||||
agentColor={agents.find((agent) => agent.slug === selectedAgent)?.color}
|
||||
ref={chatInputRef}
|
||||
setTriggeredAbort={() => {}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -101,6 +101,7 @@ function ChatBodyData(props: ChatBodyDataProps) {
|
|||
agentColor={agentMetadata?.color}
|
||||
isMobileWidth={props.isMobileWidth}
|
||||
setUploadedFiles={props.setUploadedFiles}
|
||||
setTriggeredAbort={() => {}}
|
||||
ref={chatInputRef}
|
||||
/>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue