mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-02 21:19:12 +00:00
Show connection lost toast if disconnect while processing chat request
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import "../globals.css";
|
import "../globals.css";
|
||||||
|
import { Toaster } from "@/components/ui/toaster";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Khoj AI - Chat",
|
title: "Khoj AI - Chat",
|
||||||
@@ -39,6 +40,7 @@ export default function ChildLayout({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{children}
|
{children}
|
||||||
|
<Toaster />
|
||||||
<script
|
<script
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: `window.EXCALIDRAW_ASSET_PATH = 'https://assets.khoj.dev/@excalidraw/excalidraw/dist/';`,
|
__html: `window.EXCALIDRAW_ASSET_PATH = 'https://assets.khoj.dev/@excalidraw/excalidraw/dist/';`,
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import { Separator } from "@/components/ui/separator";
|
|||||||
import { KhojLogoType } from "../components/logo/khojLogo";
|
import { KhojLogoType } from "../components/logo/khojLogo";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Joystick } from "@phosphor-icons/react";
|
import { Joystick } from "@phosphor-icons/react";
|
||||||
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import { ChatSidebar } from "../components/chatSidebar/chatSidebar";
|
import { ChatSidebar } from "../components/chatSidebar/chatSidebar";
|
||||||
|
|
||||||
interface ChatBodyDataProps {
|
interface ChatBodyDataProps {
|
||||||
@@ -224,11 +225,17 @@ export default function Chat() {
|
|||||||
const isMobileWidth = useIsMobileWidth();
|
const isMobileWidth = useIsMobileWidth();
|
||||||
const [isChatSideBarOpen, setIsChatSideBarOpen] = useState(false);
|
const [isChatSideBarOpen, setIsChatSideBarOpen] = useState(false);
|
||||||
const [socketUrl, setSocketUrl] = useState<string | null>(null);
|
const [socketUrl, setSocketUrl] = useState<string | null>(null);
|
||||||
|
// track whether we've already shown a toast for the current disconnect cycle to avoid duplicates
|
||||||
|
const disconnectToastShownRef = useRef(false);
|
||||||
|
// Track whether the websocket is closing due to an intentional action (page refresh/navigation or idle timeout)
|
||||||
|
const intentionalCloseRef = useRef(false);
|
||||||
|
|
||||||
const disconnectFromServer = useCallback(() => {
|
const disconnectFromServer = useCallback(() => {
|
||||||
if (idleTimerRef.current) {
|
if (idleTimerRef.current) {
|
||||||
clearTimeout(idleTimerRef.current);
|
clearTimeout(idleTimerRef.current);
|
||||||
}
|
}
|
||||||
|
// Mark as intentional so onClose does not show transient network error banner
|
||||||
|
intentionalCloseRef.current = true;
|
||||||
setSocketUrl(null);
|
setSocketUrl(null);
|
||||||
console.log("WebSocket disconnected due to inactivity.");
|
console.log("WebSocket disconnected due to inactivity.");
|
||||||
}, []);
|
}, []);
|
||||||
@@ -241,6 +248,7 @@ export default function Chat() {
|
|||||||
idleTimerRef.current = setTimeout(disconnectFromServer, idleTimeout);
|
idleTimerRef.current = setTimeout(disconnectFromServer, idleTimeout);
|
||||||
}, [disconnectFromServer]);
|
}, [disconnectFromServer]);
|
||||||
|
|
||||||
|
const { toast } = useToast();
|
||||||
const { sendMessage, lastMessage } = useWebSocket(socketUrl, {
|
const { sendMessage, lastMessage } = useWebSocket(socketUrl, {
|
||||||
share: true,
|
share: true,
|
||||||
shouldReconnect: (closeEvent) => true,
|
shouldReconnect: (closeEvent) => true,
|
||||||
@@ -254,12 +262,37 @@ export default function Chat() {
|
|||||||
onOpen: () => {
|
onOpen: () => {
|
||||||
console.log("WebSocket connection established.");
|
console.log("WebSocket connection established.");
|
||||||
resetIdleTimer();
|
resetIdleTimer();
|
||||||
|
// Reset disconnect toast guard so future disconnects can notify again
|
||||||
|
disconnectToastShownRef.current = false;
|
||||||
|
// Reset intentional close flag after a successful open
|
||||||
|
intentionalCloseRef.current = false;
|
||||||
},
|
},
|
||||||
onClose: () => {
|
onClose: (event) => {
|
||||||
console.log("WebSocket connection closed.");
|
console.log("WebSocket connection closed.");
|
||||||
if (idleTimerRef.current) {
|
if (idleTimerRef.current) {
|
||||||
clearTimeout(idleTimerRef.current);
|
clearTimeout(idleTimerRef.current);
|
||||||
}
|
}
|
||||||
|
// Suppress notice if:
|
||||||
|
// - Intentional close (page refresh/navigation or idle management)
|
||||||
|
// - Normal closure (1000) or Going Away (1001 - typical on page reload)
|
||||||
|
// - No query to process
|
||||||
|
if (
|
||||||
|
!intentionalCloseRef.current &&
|
||||||
|
event?.code !== 1000 &&
|
||||||
|
event?.code !== 1001 &&
|
||||||
|
queryToProcess
|
||||||
|
) {
|
||||||
|
if (!disconnectToastShownRef.current) {
|
||||||
|
toast({
|
||||||
|
title: "Network issue",
|
||||||
|
description:
|
||||||
|
"Connection lost. Please check your network and try again when ready.",
|
||||||
|
variant: "destructive",
|
||||||
|
duration: 6000,
|
||||||
|
});
|
||||||
|
disconnectToastShownRef.current = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
// Mark any in-progress streamed message as completed so UI updates (stop spinner, show send icon)
|
// Mark any in-progress streamed message as completed so UI updates (stop spinner, show send icon)
|
||||||
setMessages((prev) => {
|
setMessages((prev) => {
|
||||||
if (!prev || prev.length === 0) return prev;
|
if (!prev || prev.length === 0) return prev;
|
||||||
@@ -288,9 +321,28 @@ export default function Chat() {
|
|||||||
});
|
});
|
||||||
setProcessQuerySignal(false);
|
setProcessQuerySignal(false);
|
||||||
setQueryToProcess("");
|
setQueryToProcess("");
|
||||||
|
if (!intentionalCloseRef.current && !disconnectToastShownRef.current) {
|
||||||
|
toast({
|
||||||
|
title: "Network error",
|
||||||
|
description:
|
||||||
|
"Connection lost. Please check your network and try again when ready.",
|
||||||
|
variant: "destructive",
|
||||||
|
duration: 5000,
|
||||||
|
});
|
||||||
|
disconnectToastShownRef.current = true;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle page unload / refresh: mark intentional so we don't show a toast
|
||||||
|
useEffect(() => {
|
||||||
|
const handleBeforeUnload = () => {
|
||||||
|
intentionalCloseRef.current = true;
|
||||||
|
};
|
||||||
|
window.addEventListener("beforeunload", handleBeforeUnload);
|
||||||
|
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lastMessage !== null) {
|
if (lastMessage !== null) {
|
||||||
resetIdleTimer();
|
resetIdleTimer();
|
||||||
|
|||||||
Reference in New Issue
Block a user