mirror of
https://github.com/khoj-ai/khoj.git
synced 2025-02-17 08:04:21 +00: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;
|
isMobileWidth?: boolean;
|
||||||
isLoggedIn: boolean;
|
isLoggedIn: boolean;
|
||||||
setImages: (images: string[]) => void;
|
setImages: (images: string[]) => void;
|
||||||
|
setTriggeredAbort: (triggeredAbort: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChatBodyData(props: ChatBodyDataProps) {
|
function ChatBodyData(props: ChatBodyDataProps) {
|
||||||
|
@ -160,6 +161,7 @@ function ChatBodyData(props: ChatBodyDataProps) {
|
||||||
setUploadedFiles={props.setUploadedFiles}
|
setUploadedFiles={props.setUploadedFiles}
|
||||||
ref={chatInputRef}
|
ref={chatInputRef}
|
||||||
isResearchModeEnabled={isInResearchMode}
|
isResearchModeEnabled={isInResearchMode}
|
||||||
|
setTriggeredAbort={props.setTriggeredAbort}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -178,6 +180,10 @@ export default function Chat() {
|
||||||
const [uploadedFiles, setUploadedFiles] = useState<AttachedFileText[] | undefined>(undefined);
|
const [uploadedFiles, setUploadedFiles] = useState<AttachedFileText[] | undefined>(undefined);
|
||||||
const [images, setImages] = useState<string[]>([]);
|
const [images, setImages] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const [abortMessageStreamController, setAbortMessageStreamController] =
|
||||||
|
useState<AbortController | null>(null);
|
||||||
|
const [triggeredAbort, setTriggeredAbort] = useState(false);
|
||||||
|
|
||||||
const locationData = useIPLocationData() || {
|
const locationData = useIPLocationData() || {
|
||||||
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
};
|
};
|
||||||
|
@ -202,6 +208,15 @@ export default function Chat() {
|
||||||
welcomeConsole();
|
welcomeConsole();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (triggeredAbort) {
|
||||||
|
abortMessageStreamController?.abort();
|
||||||
|
handleAbortedMessage();
|
||||||
|
setTriggeredAbort(false);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
[triggeredAbort];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (queryToProcess) {
|
if (queryToProcess) {
|
||||||
const newStreamMessage: StreamMessage = {
|
const newStreamMessage: StreamMessage = {
|
||||||
|
@ -218,6 +233,7 @@ export default function Chat() {
|
||||||
};
|
};
|
||||||
setMessages((prevMessages) => [...prevMessages, newStreamMessage]);
|
setMessages((prevMessages) => [...prevMessages, newStreamMessage]);
|
||||||
setProcessQuerySignal(true);
|
setProcessQuerySignal(true);
|
||||||
|
setAbortMessageStreamController(new AbortController());
|
||||||
}
|
}
|
||||||
}, [queryToProcess]);
|
}, [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() {
|
async function chat() {
|
||||||
localStorage.removeItem("message");
|
localStorage.removeItem("message");
|
||||||
if (!queryToProcess || !conversationId) return;
|
if (!queryToProcess || !conversationId) return;
|
||||||
|
@ -308,6 +335,7 @@ export default function Chat() {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(chatAPIBody),
|
body: JSON.stringify(chatAPIBody),
|
||||||
|
signal: abortMessageStreamController?.signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -321,14 +349,18 @@ export default function Chat() {
|
||||||
|
|
||||||
// Render error message as current message
|
// Render error message as current message
|
||||||
const errorMessage = (err as Error).message;
|
const errorMessage = (err as Error).message;
|
||||||
|
const errorName = (err as Error).name;
|
||||||
if (errorMessage.includes("Error in input stream"))
|
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?`;
|
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) {
|
else if (response.status === 429) {
|
||||||
"detail" in apiError
|
"detail" in apiError
|
||||||
? (currentMessage.rawResponse = `${apiError.detail}`)
|
? (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?`);
|
: (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?`;
|
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
|
// Complete message streaming teardown properly
|
||||||
currentMessage.completed = true;
|
currentMessage.completed = true;
|
||||||
|
@ -388,6 +420,7 @@ export default function Chat() {
|
||||||
isMobileWidth={isMobileWidth}
|
isMobileWidth={isMobileWidth}
|
||||||
onConversationIdChange={handleConversationIdChange}
|
onConversationIdChange={handleConversationIdChange}
|
||||||
setImages={setImages}
|
setImages={setImages}
|
||||||
|
setTriggeredAbort={setTriggeredAbort}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -76,6 +76,7 @@ interface ChatInputProps {
|
||||||
isLoggedIn: boolean;
|
isLoggedIn: boolean;
|
||||||
agentColor?: string;
|
agentColor?: string;
|
||||||
isResearchModeEnabled?: boolean;
|
isResearchModeEnabled?: boolean;
|
||||||
|
setTriggeredAbort: (value: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChatInputArea = forwardRef<HTMLTextAreaElement, ChatInputProps>((props, ref) => {
|
export const ChatInputArea = forwardRef<HTMLTextAreaElement, ChatInputProps>((props, ref) => {
|
||||||
|
@ -678,20 +679,33 @@ export const ChatInputArea = forwardRef<HTMLTextAreaElement, ChatInputProps>((pr
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
{props.sendDisabled ? (
|
||||||
variant="default"
|
<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`}
|
variant="default"
|
||||||
onClick={() => {
|
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`}
|
||||||
setMessage("Listening...");
|
onClick={() => {
|
||||||
setRecording(!recording);
|
props.setTriggeredAbort(true);
|
||||||
}}
|
}}
|
||||||
disabled={props.sendDisabled}
|
>
|
||||||
>
|
<Stop weight="fill" className="w-6 h-6" />
|
||||||
<Microphone weight="fill" className="w-6 h-6" />
|
</Button>
|
||||||
</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`}
|
||||||
|
onClick={() => {
|
||||||
|
setMessage("Listening...");
|
||||||
|
setRecording(!recording);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Microphone weight="fill" className="w-6 h-6" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
Click to transcribe your message with voice.
|
{props.sendDisabled
|
||||||
|
? "Click here to stop the streaming."
|
||||||
|
: "Click to transcribe your message with voice."}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
@ -699,7 +713,6 @@ export const ChatInputArea = forwardRef<HTMLTextAreaElement, ChatInputProps>((pr
|
||||||
<Button
|
<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`}
|
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}
|
onClick={onSendMessage}
|
||||||
disabled={props.sendDisabled}
|
|
||||||
>
|
>
|
||||||
<ArrowUp className="w-6 h-6" weight="bold" />
|
<ArrowUp className="w-6 h-6" weight="bold" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -312,6 +312,7 @@ function ChatBodyData(props: ChatBodyDataProps) {
|
||||||
setUploadedFiles={props.setUploadedFiles}
|
setUploadedFiles={props.setUploadedFiles}
|
||||||
agentColor={agents.find((agent) => agent.slug === selectedAgent)?.color}
|
agentColor={agents.find((agent) => agent.slug === selectedAgent)?.color}
|
||||||
ref={chatInputRef}
|
ref={chatInputRef}
|
||||||
|
setTriggeredAbort={() => {}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -394,6 +395,7 @@ function ChatBodyData(props: ChatBodyDataProps) {
|
||||||
setUploadedFiles={props.setUploadedFiles}
|
setUploadedFiles={props.setUploadedFiles}
|
||||||
agentColor={agents.find((agent) => agent.slug === selectedAgent)?.color}
|
agentColor={agents.find((agent) => agent.slug === selectedAgent)?.color}
|
||||||
ref={chatInputRef}
|
ref={chatInputRef}
|
||||||
|
setTriggeredAbort={() => {}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -101,6 +101,7 @@ function ChatBodyData(props: ChatBodyDataProps) {
|
||||||
agentColor={agentMetadata?.color}
|
agentColor={agentMetadata?.color}
|
||||||
isMobileWidth={props.isMobileWidth}
|
isMobileWidth={props.isMobileWidth}
|
||||||
setUploadedFiles={props.setUploadedFiles}
|
setUploadedFiles={props.setUploadedFiles}
|
||||||
|
setTriggeredAbort={() => {}}
|
||||||
ref={chatInputRef}
|
ref={chatInputRef}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Add table
Reference in a new issue