Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
36efb5a
Add Comet Debugger Mode feature and related components
JetoPistola Oct 28, 2025
0296fa0
Add Opik icon to AppDebugInfo component
JetoPistola Nov 7, 2025
2aad3c7
Enhance Switch Component with Extra Small Size Variant
JetoPistola Nov 7, 2025
0572251
Enhance AppNetworkStatus component with styled RTT display
JetoPistola Nov 7, 2025
d52576e
Add cometDebuggerModeEnabled toggle to ServiceTogglesConfig
JetoPistola Nov 7, 2025
3540363
Update AppDebugInfo component to conditionally render OPIK version di…
JetoPistola Nov 7, 2025
aa1d272
Update end-to-end workflows and configuration for Comet Debugger Mode
JetoPistola Nov 7, 2025
2a9ce72
[OPIK-2848] Fix Tailwind CSS classnames order
JetoPistola Nov 7, 2025
4325754
Refactor UserMenu component to use logical AND for APP_VERSION check
JetoPistola Nov 7, 2025
576b15d
Wrap CometIcon in a span within AppNetworkStatus component for improv…
JetoPistola Nov 7, 2025
4dbd0e5
Revision 2: Add keyboard shortcut for debugger mode and remove menu t…
JetoPistola Nov 9, 2025
51f169b
Revision 2: Remove COMET_DEBUGGER_MODE from feature toggles
JetoPistola Nov 9, 2025
ba43032
Revision 3: Revert to ThemeToggle and remove redundant empty line
JetoPistola Nov 9, 2025
d7f810e
Revision 4: Use absolute imports for ThemeToggle component
JetoPistola Nov 9, 2025
e2842f6
Revision 5: Remove redundant DebugStore and use simple localStorage s…
JetoPistola Nov 9, 2025
c9ce853
Revision 2: Refactor AppDebugInfo to use react-hotkeys-hook and make …
JetoPistola Nov 10, 2025
2bb63cb
Revision 3: Move AppNetworkStatus to layout components
JetoPistola Nov 10, 2025
8c1312e
Revision 4: Move AppDebugInfo to layout components
JetoPistola Nov 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions apps/opik-frontend/src/api/debug/useIsAlive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import api from "@/api/api";
import { useQuery } from "@tanstack/react-query";

const PING_FETCHING_TIMEOUT_SECONDS = 5;
const CONNECTED_PING_REFETCH_INTERVAL_SECONDS = 10;
const DISCONNECTED_PING_REFETCH_INTERVAL_SECONDS = 5;

interface IsAlivePingResponse {
healthy: boolean;
rtt: number;
}

const getPing = async (): Promise<IsAlivePingResponse> => {
const startTime = performance.now();

const { data } = await api.get<IsAlivePingResponse>("/is-alive/ping", {
timeout: PING_FETCHING_TIMEOUT_SECONDS * 1000,
});

const endTime = performance.now();

const rtt = endTime - startTime;

return { ...data, rtt };
};

export const usePingBackend = (isNetworkOnline: boolean) =>
useQuery({
queryKey: ["backend-ping"],
queryFn: getPing,
enabled: isNetworkOnline,
retryDelay: 1000,
refetchInterval: (query) => {
const { error: isError, data } = query.state;
const isConnected = !isError && data?.healthy;

return isConnected
? CONNECTED_PING_REFETCH_INTERVAL_SECONDS * 1000
: DISCONNECTED_PING_REFETCH_INTERVAL_SECONDS * 1000;
},
refetchOnReconnect: true,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useEffect, useState } from "react";
import { toast } from "@/components/ui/use-toast";
import { APP_VERSION } from "@/constants/app";
import AppNetworkStatus from "@/plugins/comet/AppNetworkStatus";
import OpikIcon from "@/icons/opik.svg?react";
import copy from "clipboard-copy";
import { Copy } from "lucide-react";

const COMET_DEBUGGER_MODE_KEY = "comet-debugger-mode"; // Same in EM

const AppDebugInfo = () => {
const [showAppDebugInfo, setShowAppDebugInfo] = useState(
() => localStorage.getItem(COMET_DEBUGGER_MODE_KEY) === "true",
);

// Keyboard shortcut handler for debugger mode: Meta/Ctrl + c + .
useEffect(() => {
let isWaitingForPeriod = false;

const handleKeyDown = (event: KeyboardEvent) => {
const isMetaOrCtrl = event.metaKey || event.ctrlKey;
const isCKey = event.key === "c" || event.key === "C";
const isPeriodKey = event.key === "." || event.key === ">";

// First part: Meta/Ctrl + c - start waiting for period
if (isMetaOrCtrl && isCKey && !isWaitingForPeriod) {
isWaitingForPeriod = true;
return;
}

// Second part: Period while still holding Meta/Ctrl - complete sequence
if (isMetaOrCtrl && isPeriodKey && isWaitingForPeriod) {
event.preventDefault();
event.stopPropagation();
isWaitingForPeriod = false;

// Toggle debugger mode
setShowAppDebugInfo((prev) => {
const newValue = !prev;
localStorage.setItem(COMET_DEBUGGER_MODE_KEY, String(newValue));
return newValue;
});
return;
}

// If waiting for period and any other key is pressed, reset sequence
if (isWaitingForPeriod) {
isWaitingForPeriod = false;
}
};

window.addEventListener("keydown", handleKeyDown);

return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, []);

return (
showAppDebugInfo && (
<>
<div className="flex items-center">
<AppNetworkStatus />
</div>

{APP_VERSION && (
<div
className="flex items-center gap-2"
onClick={() => {
copy(APP_VERSION);
toast({ description: "Successfully copied version" });
}}
>
<span className="comet-body-s-accented flex items-center gap-2 truncate">
<OpikIcon className="size-5" />
OPIK VERSION {APP_VERSION}
</span>
<Copy className="size-4 shrink-0" />
</div>
)}
</>
)
);
};

export default AppDebugInfo;
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import useAppStore from "@/store/AppStore";
import usePluginsStore from "@/store/PluginsStore";
import { Link, Outlet } from "@tanstack/react-router";
import Logo from "@/components/layout/Logo/Logo";
import AppDebugInfo from "../AppDebugInfo/AppDebugInfo";
import ThemeToggle from "@/components/layout/ThemeToggle/ThemeToggle";

export const PartialPageLayout = ({
Expand Down Expand Up @@ -30,6 +31,7 @@ export const PartialPageLayout = ({
</Link>
</div>

<AppDebugInfo />
{UserMenu ? <UserMenu /> : <ThemeToggle />}
</nav>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";
import Breadcrumbs from "@/components/layout/Breadcrumbs/Breadcrumbs";
import usePluginsStore from "@/store/PluginsStore";
import AppDebugInfo from "../AppDebugInfo/AppDebugInfo";
import ThemeToggle from "../ThemeToggle/ThemeToggle";

const TopBar = () => {
Expand All @@ -12,6 +12,7 @@ const TopBar = () => {
<Breadcrumbs />
</div>

<AppDebugInfo />
{UserMenu ? <UserMenu /> : <ThemeToggle />}
</nav>
);
Expand Down
2 changes: 2 additions & 0 deletions apps/opik-frontend/src/components/ui/switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const switchVariants = cva(
size: {
default: "h-6 w-11",
sm: "h-5 w-9",
xs: "h-4 w-7",
},
},
defaultVariants: {
Expand All @@ -27,6 +28,7 @@ const switchThumbVariants = cva(
default:
"size-5 data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0",
sm: "size-4 data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0",
xs: "size-3 data-[state=checked]:translate-x-3 data-[state=unchecked]:translate-x-0",
},
},
defaultVariants: {
Expand Down
21 changes: 21 additions & 0 deletions apps/opik-frontend/src/hooks/useIsNetworkOnline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useState, useEffect } from "react";

const useIsNetworkOnline = () => {
const [isNetworkOnline, setIsNetworkOnline] = useState(navigator.onLine);

useEffect(() => {
const updateNetworkStatus = () => setIsNetworkOnline(navigator.onLine);

window.addEventListener("online", updateNetworkStatus);
window.addEventListener("offline", updateNetworkStatus);

return () => {
window.removeEventListener("online", updateNetworkStatus);
window.removeEventListener("offline", updateNetworkStatus);
};
}, []);

return isNetworkOnline;
};

export default useIsNetworkOnline;
1 change: 1 addition & 0 deletions apps/opik-frontend/src/icons/comet.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions apps/opik-frontend/src/icons/opik.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions apps/opik-frontend/src/plugins/comet/AppNetworkStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { cn } from "@/lib/utils";

import CometIcon from "@/icons/comet.svg?react";
import { usePingBackend } from "@/api/debug/useIsAlive";
import useIsNetworkOnline from "@/hooks/useIsNetworkOnline";
import { WifiOffIcon, WifiIcon, SatelliteDishIcon } from "lucide-react";
import TooltipWrapper from "@/components/shared/TooltipWrapper/TooltipWrapper";

const AppNetworkStatus = () => {
const isNetworkOnline = useIsNetworkOnline();
const { data: pingResponse, isError } = usePingBackend(isNetworkOnline);
const rtt = pingResponse?.rtt;
const rttInSeconds = rtt ? (rtt / 1000).toFixed(2) : null;
const isConnectedToCometServer =
isNetworkOnline && !isError && pingResponse?.healthy;

return (
<div className="flex items-center gap-2">
{isConnectedToCometServer && (
<div className="flex items-center gap-2">
<SatelliteDishIcon className="size-5" />
<TooltipWrapper content="Round-trip time (RTT) to ping Comet server">
<span className="comet-body-s-accented">RTT: {rttInSeconds}s</span>
</TooltipWrapper>
</div>
)}
{isNetworkOnline && (
<div className="relative flex flex-col items-center justify-center">
<div
className={cn(
"absolute -top-2.5 left-1.75 size-2 rounded-full",
isConnectedToCometServer ? "bg-green-500" : "bg-red-500",
)}
/>
<TooltipWrapper
content={
isConnectedToCometServer
? "Connected to Comet server"
: "Not connected to Comet server"
}
>
<span>
<CometIcon className="size-5" />
</span>
</TooltipWrapper>
</div>
)}
<div className="relative flex flex-col items-center justify-center">
<div
className={cn(
"absolute -top-2.5 left-1.75 size-2 rounded-full",
isNetworkOnline ? "bg-green-500" : "bg-red-500",
)}
/>
<TooltipWrapper
content={
isNetworkOnline
? "Connected to network"
: "Not connected to network"
}
>
{isNetworkOnline ? (
<WifiIcon className="size-5" />
) : (
<WifiOffIcon className="size-5" />
)}
</TooltipWrapper>
</div>
</div>
);
};

export default AppNetworkStatus;
4 changes: 2 additions & 2 deletions apps/opik-frontend/src/plugins/comet/UserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ const UserMenu = () => {
<LogOut className="mr-2 size-4" />
<span>Logout</span>
</DropdownMenuItem>
{APP_VERSION ? (
{APP_VERSION && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem
Expand All @@ -433,7 +433,7 @@ const UserMenu = () => {
<Copy className="ml-2 size-3 shrink-0" />
</DropdownMenuItem>
</>
) : null}
)}
</DropdownMenuContent>
</DropdownMenu>
);
Expand Down
Loading