mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-02 13:18:18 +00:00
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.
This commit is contained in:
@@ -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<AbortController | null>(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}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
@@ -195,6 +195,11 @@ export const ChatInputArea = forwardRef<HTMLTextAreaElement, ChatInputProps>((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<HTMLTextAreaElement, ChatInputProps>((pr
|
||||
<Button
|
||||
variant={"ghost"}
|
||||
className="!bg-none p-0 m-2 h-auto text-3xl rounded-full text-gray-300 hover:text-gray-500"
|
||||
disabled={props.sendDisabled || !props.isLoggedIn}
|
||||
disabled={!props.isLoggedIn}
|
||||
onClick={handleFileButtonClick}
|
||||
ref={fileInputButtonRef}
|
||||
>
|
||||
@@ -686,7 +691,8 @@ export const ChatInputArea = forwardRef<HTMLTextAreaElement, ChatInputProps>((pr
|
||||
e.key === "Enter" &&
|
||||
!e.shiftKey &&
|
||||
!props.isMobileWidth &&
|
||||
!props.sendDisabled
|
||||
!recording &&
|
||||
message
|
||||
) {
|
||||
setImageUploaded(false);
|
||||
setImagePaths([]);
|
||||
@@ -725,7 +731,7 @@ export const ChatInputArea = forwardRef<HTMLTextAreaElement, ChatInputProps>((pr
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
{props.sendDisabled ? (
|
||||
{props.sendDisabled && !message ? (
|
||||
<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`}
|
||||
@@ -758,8 +764,8 @@ export const ChatInputArea = forwardRef<HTMLTextAreaElement, ChatInputProps>((pr
|
||||
</TooltipProvider>
|
||||
)}
|
||||
<Button
|
||||
className={`${(!message || recording || props.sendDisabled) && "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`}
|
||||
disabled={props.sendDisabled || !props.isLoggedIn}
|
||||
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`}
|
||||
disabled={!message || recording || !props.isLoggedIn}
|
||||
onClick={onSendMessage}
|
||||
>
|
||||
<ArrowUp className="w-6 h-6" weight="bold" />
|
||||
|
||||
Reference in New Issue
Block a user