From 6cb512d9cfcaaad920da98b18cf9b48905bdbf70 Mon Sep 17 00:00:00 2001 From: Debanjum Date: Wed, 21 May 2025 13:28:12 -0700 Subject: [PATCH] Support natural interrupt and send query behavior from web app - Just send your new query. If a query was running previously it'd be interrupted and new query would start processing. This improves on the previous 2 click interrupt and send ux. - Utilizes partial research for interrupted query, so you can now redirect khoj's research direction. This is useful if you need to share more details, change khoj's research direction in anyway or complete research. Khoj's train of thought can be helpful for this. --- src/interface/web/app/chat/page.tsx | 16 +++++++++++++--- .../components/chatInputArea/chatInputArea.tsx | 16 +++++++++++----- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/interface/web/app/chat/page.tsx b/src/interface/web/app/chat/page.tsx index b672091d..ecd10a4f 100644 --- a/src/interface/web/app/chat/page.tsx +++ b/src/interface/web/app/chat/page.tsx @@ -49,6 +49,7 @@ interface ChatBodyDataProps { isChatSideBarOpen: boolean; setIsChatSideBarOpen: (open: boolean) => void; isActive?: boolean; + isParentProcessing?: boolean; } function ChatBodyData(props: ChatBodyDataProps) { @@ -166,7 +167,7 @@ function ChatBodyData(props: ChatBodyDataProps) { isLoggedIn={props.isLoggedIn} sendMessage={(message) => setMessage(message)} sendImage={(image) => setImages((prevImages) => [...prevImages, image])} - sendDisabled={processingMessage} + sendDisabled={props.isParentProcessing || false} chatOptionsData={props.chatOptionsData} conversationId={conversationId} isMobileWidth={props.isMobileWidth} @@ -203,6 +204,7 @@ export default function Chat() { const [abortMessageStreamController, setAbortMessageStreamController] = useState(null); const [triggeredAbort, setTriggeredAbort] = useState(false); + const [shouldSendWithInterrupt, setShouldSendWithInterrupt] = useState(false); const { locationData, locationDataError, locationDataLoading } = useIPLocationData() || { locationData: { @@ -239,6 +241,7 @@ export default function Chat() { if (triggeredAbort) { abortMessageStreamController?.abort(); handleAbortedMessage(); + setShouldSendWithInterrupt(true); setTriggeredAbort(false); } }, [triggeredAbort]); @@ -335,18 +338,21 @@ export default function Chat() { currentMessage.completed = true; setMessages([...messages]); - setQueryToProcess(""); setProcessQuerySignal(false); } async function chat() { localStorage.removeItem("message"); - if (!queryToProcess || !conversationId) return; + if (!queryToProcess || !conversationId) { + setProcessQuerySignal(false); + return; + } const chatAPI = "/api/chat?client=web"; const chatAPIBody = { q: queryToProcess, conversation_id: conversationId, stream: true, + interrupt: shouldSendWithInterrupt, ...(locationData && { city: locationData.city, region: locationData.region, @@ -358,6 +364,9 @@ export default function Chat() { ...(uploadedFiles && { files: uploadedFiles }), }; + // Reset the flag after using it + setShouldSendWithInterrupt(false); + const response = await fetch(chatAPI, { method: "POST", headers: { @@ -481,6 +490,7 @@ export default function Chat() { isChatSideBarOpen={isChatSideBarOpen} setIsChatSideBarOpen={setIsChatSideBarOpen} isActive={authenticatedData?.is_active} + isParentProcessing={processQuerySignal} /> diff --git a/src/interface/web/app/components/chatInputArea/chatInputArea.tsx b/src/interface/web/app/components/chatInputArea/chatInputArea.tsx index 5448d8ce..9c9a6214 100644 --- a/src/interface/web/app/components/chatInputArea/chatInputArea.tsx +++ b/src/interface/web/app/components/chatInputArea/chatInputArea.tsx @@ -195,6 +195,11 @@ export const ChatInputArea = forwardRef((pr return; } + // If currently processing, trigger abort first + if (props.sendDisabled) { + props.setTriggeredAbort(true); + } + let messageToSend = message.trim(); // Check if message starts with an explicit slash command const startsWithSlashCommand = @@ -657,7 +662,7 @@ export const ChatInputArea = forwardRef((pr