diff --git a/framework/go.mod b/framework/go.mod
index 0e7a47bee9..88654fd81b 100644
--- a/framework/go.mod
+++ b/framework/go.mod
@@ -1,6 +1,6 @@
module github.com/maximhq/bifrost/framework
-go 1.26.1
+go 1.26.2
require (
cloud.google.com/go/storage v1.61.3
diff --git a/plugins/compat/go.mod b/plugins/compat/go.mod
index 9d0466d0a5..b4deca7657 100644
--- a/plugins/compat/go.mod
+++ b/plugins/compat/go.mod
@@ -1,6 +1,6 @@
module github.com/maximhq/bifrost/plugins/compat
-go 1.26.1
+go 1.26.2
require (
github.com/maximhq/bifrost/core v1.5.2
diff --git a/plugins/governance/go.mod b/plugins/governance/go.mod
index 0abf0caa70..61409c6b68 100644
--- a/plugins/governance/go.mod
+++ b/plugins/governance/go.mod
@@ -1,6 +1,6 @@
module github.com/maximhq/bifrost/plugins/governance
-go 1.26.1
+go 1.26.2
require gorm.io/gorm v1.31.1
diff --git a/plugins/jsonparser/go.mod b/plugins/jsonparser/go.mod
index 42171926ad..883e837587 100644
--- a/plugins/jsonparser/go.mod
+++ b/plugins/jsonparser/go.mod
@@ -1,6 +1,6 @@
module github.com/maximhq/bifrost/plugins/jsonparser
-go 1.26.1
+go 1.26.2
require github.com/maximhq/bifrost/core v1.5.2
diff --git a/plugins/logging/go.mod b/plugins/logging/go.mod
index a0b0c5c2b4..eaf8b6f4c5 100644
--- a/plugins/logging/go.mod
+++ b/plugins/logging/go.mod
@@ -1,6 +1,6 @@
module github.com/maximhq/bifrost/plugins/logging
-go 1.26.1
+go 1.26.2
require (
github.com/bytedance/sonic v1.15.0
diff --git a/plugins/maxim/go.mod b/plugins/maxim/go.mod
index 1c27aa1b93..44a6544b01 100644
--- a/plugins/maxim/go.mod
+++ b/plugins/maxim/go.mod
@@ -1,6 +1,6 @@
module github.com/maximhq/bifrost/plugins/maxim
-go 1.26.1
+go 1.26.2
require (
github.com/maximhq/bifrost/core v1.5.2
diff --git a/plugins/mocker/go.mod b/plugins/mocker/go.mod
index bcf0aebc61..dd40feefbf 100644
--- a/plugins/mocker/go.mod
+++ b/plugins/mocker/go.mod
@@ -1,6 +1,6 @@
module github.com/maximhq/bifrost/plugins/mocker
-go 1.26.1
+go 1.26.2
require (
github.com/jaswdr/faker/v2 v2.8.0
diff --git a/plugins/otel/go.mod b/plugins/otel/go.mod
index 69c9fddb3a..7595e08f75 100644
--- a/plugins/otel/go.mod
+++ b/plugins/otel/go.mod
@@ -1,6 +1,6 @@
module github.com/maximhq/bifrost/plugins/otel
-go 1.26.1
+go 1.26.2
require (
github.com/maximhq/bifrost/core v1.5.2
diff --git a/plugins/semanticcache/go.mod b/plugins/semanticcache/go.mod
index c65f88d4e1..aa0f81afc5 100644
--- a/plugins/semanticcache/go.mod
+++ b/plugins/semanticcache/go.mod
@@ -1,6 +1,6 @@
module github.com/maximhq/bifrost/plugins/semanticcache
-go 1.26.1
+go 1.26.2
require (
github.com/cespare/xxhash/v2 v2.3.0
diff --git a/plugins/telemetry/go.mod b/plugins/telemetry/go.mod
index 6313ac1894..f110b0517c 100644
--- a/plugins/telemetry/go.mod
+++ b/plugins/telemetry/go.mod
@@ -1,6 +1,6 @@
module github.com/maximhq/bifrost/plugins/telemetry
-go 1.26.1
+go 1.26.2
require (
github.com/maximhq/bifrost/core v1.5.2
diff --git a/transports/go.mod b/transports/go.mod
index 4366567848..211edb90a4 100644
--- a/transports/go.mod
+++ b/transports/go.mod
@@ -1,6 +1,6 @@
module github.com/maximhq/bifrost/transports
-go 1.26.1
+go 1.26.2
require (
github.com/andybalholm/brotli v1.2.0
diff --git a/ui/app/workspace/dashboard/components/charts/chartCard.tsx b/ui/app/workspace/dashboard/components/charts/chartCard.tsx
index 16883d487a..d2929c01b4 100644
--- a/ui/app/workspace/dashboard/components/charts/chartCard.tsx
+++ b/ui/app/workspace/dashboard/components/charts/chartCard.tsx
@@ -4,45 +4,68 @@ import { cn } from "@/lib/utils";
import type { ReactNode } from "react";
interface ChartCardProps {
- title: string;
- children: ReactNode;
- headerActions?: ReactNode;
- loading?: boolean;
- testId?: string;
- height?: string;
- className?: string;
+ title: string;
+ children: ReactNode;
+ headerActions?: ReactNode;
+ loading?: boolean;
+ testId?: string;
+ height?: string;
+ className?: string;
}
-export function ChartCard({ title, children, headerActions, loading, testId, height = "200px", className }: ChartCardProps) {
- if (loading) {
- return (
-
-
-
{title}
- {headerActions && (
-
- {headerActions}
-
- )}
-
-
-
-
-
- );
- }
+export function ChartCard({
+ title,
+ children,
+ headerActions,
+ loading,
+ testId,
+ height = "200px",
+ className,
+}: ChartCardProps) {
+ if (loading) {
+ return (
+
+
+
{title}
+ {headerActions && (
+
+ {headerActions}
+
+ )}
+
+
+
+
+
+ );
+ }
- return (
-
-
-
{title}
- {headerActions && (
-
- {headerActions}
-
- )}
-
- {children}
-
- );
-}
\ No newline at end of file
+ return (
+
+
+
{title}
+ {headerActions && (
+
+ {headerActions}
+
+ )}
+
+ {children}
+
+ );
+}
diff --git a/ui/app/workspace/dashboard/components/charts/logVolumeChart.tsx b/ui/app/workspace/dashboard/components/charts/logVolumeChart.tsx
index 13cd3d0ab2..d35b79e847 100644
--- a/ui/app/workspace/dashboard/components/charts/logVolumeChart.tsx
+++ b/ui/app/workspace/dashboard/components/charts/logVolumeChart.tsx
@@ -1,156 +1,219 @@
import type { LogsHistogramResponse } from "@/lib/types/logs";
import { useMemo } from "react";
-import { Area, AreaChart, Bar, BarChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
-import { CHART_COLORS, formatFullTimestamp, formatTimestamp } from "../../utils/chartUtils";
+import {
+ Area,
+ AreaChart,
+ Bar,
+ BarChart,
+ CartesianGrid,
+ ResponsiveContainer,
+ Tooltip,
+ XAxis,
+ YAxis,
+} from "recharts";
+import {
+ CHART_COLORS,
+ formatFullTimestamp,
+ formatTimestamp,
+} from "../../utils/chartUtils";
import { ChartErrorBoundary } from "./chartErrorBoundary";
import type { ChartType } from "./chartTypeToggle";
interface LogVolumeChartProps {
- data: LogsHistogramResponse | null;
- chartType: ChartType;
- startTime: number;
- endTime: number;
+ data: LogsHistogramResponse | null;
+ chartType: ChartType;
+ startTime: number;
+ endTime: number;
}
-function CustomTooltip({ active, payload }: any) {
- if (!active || !payload || !payload.length) return null;
+type LogVolumeDataPoint = {
+ timestamp: string;
+ count: number;
+ success: number;
+ error: number;
+ index: number;
+ formattedTime: string;
+};
- const data = payload[0]?.payload;
- if (!data) return null;
+interface CustomTooltipProps {
+ active?: boolean;
+ payload?: Array<{ payload?: LogVolumeDataPoint }>;
+}
+
+function CustomTooltip({ active, payload }: CustomTooltipProps) {
+ if (!active || !payload || !payload.length) return null;
- return (
-
-
{formatFullTimestamp(data.timestamp)}
-
-
-
-
- Success
-
- {data.success.toLocaleString()}
-
-
-
-
- Error
-
- {data.error.toLocaleString()}
-
-
-
- );
+ const data = payload[0]?.payload;
+ if (!data) return null;
+
+ return (
+
+
+ {formatFullTimestamp(data.timestamp)}
+
+
+
+
+
+ Success
+
+
+ {data.success.toLocaleString()}
+
+
+
+
+
+ Error
+
+
+ {data.error.toLocaleString()}
+
+
+
+
+ );
}
-export function LogVolumeChart({ data, chartType, startTime, endTime }: LogVolumeChartProps) {
- const chartData = useMemo(() => {
- if (!data?.buckets || !data.bucket_size_seconds) {
- return [];
- }
+export function LogVolumeChart({
+ data,
+ chartType,
+ startTime,
+ endTime,
+}: LogVolumeChartProps) {
+ const chartData = useMemo(() => {
+ if (!data?.buckets || !data.bucket_size_seconds) {
+ return [];
+ }
- return data.buckets.map((bucket, index) => ({
- ...bucket,
- index,
- formattedTime: formatTimestamp(bucket.timestamp, data.bucket_size_seconds),
- }));
- }, [data]);
+ return data.buckets.map((bucket, index) => ({
+ ...bucket,
+ index,
+ formattedTime: formatTimestamp(
+ bucket.timestamp,
+ data.bucket_size_seconds,
+ ),
+ }));
+ }, [data]);
- if (!data?.buckets || chartData.length === 0) {
- return No data available
;
- }
+ if (!data?.buckets || chartData.length === 0) {
+ return (
+
+ No data available
+
+ );
+ }
- const commonProps = {
- data: chartData,
- margin: { top: 6, right: 4, left: 12, bottom: 0 },
- };
+ const commonProps = {
+ data: chartData,
+ margin: { top: 6, right: 4, left: 12, bottom: 0 },
+ };
- return (
-
-
- {chartType === "bar" ? (
-
-
- chartData[Math.round(idx)]?.formattedTime || ""}
- interval="preserveStartEnd"
- />
- v.toLocaleString()}
- domain={[0, (dataMax: number) => Math.max(dataMax, 1)]}
- allowDataOverflow={false}
- />
- } cursor={{ fill: "#8c8c8f", fillOpacity: 0.15 }} />
-
-
-
- ) : (
-
-
- chartData[Math.round(idx)]?.formattedTime || ""}
- interval="preserveStartEnd"
- />
- v.toLocaleString()}
- domain={[0, (dataMax: number) => Math.max(dataMax, 1)]}
- allowDataOverflow={false}
- />
- } />
-
-
-
- )}
-
-
- );
-}
\ No newline at end of file
+ return (
+
+
+ {chartType === "bar" ? (
+
+
+
+ chartData[Math.round(idx)]?.formattedTime || ""
+ }
+ interval="preserveStartEnd"
+ />
+ v.toLocaleString()}
+ domain={[0, (dataMax: number) => Math.max(dataMax, 1)]}
+ allowDataOverflow={false}
+ />
+ }
+ cursor={{ fill: "#8c8c8f", fillOpacity: 0.15 }}
+ />
+
+
+
+ ) : (
+
+
+
+ chartData[Math.round(idx)]?.formattedTime || ""
+ }
+ interval="preserveStartEnd"
+ />
+ v.toLocaleString()}
+ domain={[0, (dataMax: number) => Math.max(dataMax, 1)]}
+ allowDataOverflow={false}
+ />
+ } />
+
+
+
+ )}
+
+
+ );
+}
diff --git a/ui/app/workspace/dashboard/components/overviewTab.tsx b/ui/app/workspace/dashboard/components/overviewTab.tsx
index 5fdeff2b91..d3e6793677 100644
--- a/ui/app/workspace/dashboard/components/overviewTab.tsx
+++ b/ui/app/workspace/dashboard/components/overviewTab.tsx
@@ -1,18 +1,22 @@
-import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
import type {
- CostHistogramResponse,
- LatencyHistogramResponse,
- LogsHistogramResponse,
- ModelHistogramResponse,
- TokenHistogramResponse,
+ CostHistogramResponse,
+ LatencyHistogramResponse,
+ LogsHistogramResponse,
+ ModelHistogramResponse,
+ TokenHistogramResponse,
} from "@/lib/types/logs";
import {
- CHART_COLORS,
- CHART_HEADER_ACTIONS_CLASS,
- CHART_HEADER_CONTROLS_CLASS,
- CHART_HEADER_LEGEND_CLASS,
- LATENCY_COLORS,
- getModelColor,
+ CHART_COLORS,
+ CHART_HEADER_ACTIONS_CLASS,
+ CHART_HEADER_CONTROLS_CLASS,
+ CHART_HEADER_LEGEND_CLASS,
+ LATENCY_COLORS,
+ getModelColor,
} from "../utils/chartUtils";
import CacheTokenMeterChart from "./charts/cacheTokenMeterChart";
import { ChartCard } from "./charts/chartCard";
@@ -25,323 +29,454 @@ import { ModelUsageChart } from "./charts/modelUsageChart";
import { TokenUsageChart } from "./charts/tokenUsageChart";
export interface OverviewTabProps {
- // Data
- histogramData: LogsHistogramResponse | null;
- tokenData: TokenHistogramResponse | null;
- costData: CostHistogramResponse | null;
- modelData: ModelHistogramResponse | null;
- latencyData: LatencyHistogramResponse | null;
+ // Data
+ histogramData: LogsHistogramResponse | null;
+ tokenData: TokenHistogramResponse | null;
+ costData: CostHistogramResponse | null;
+ modelData: ModelHistogramResponse | null;
+ latencyData: LatencyHistogramResponse | null;
- // Loading states
- loadingHistogram: boolean;
- loadingTokens: boolean;
- loadingCost: boolean;
- loadingModels: boolean;
- loadingLatency: boolean;
+ // Loading states
+ loadingHistogram: boolean;
+ loadingTokens: boolean;
+ loadingCost: boolean;
+ loadingModels: boolean;
+ loadingLatency: boolean;
- // Time range
- startTime: number;
- endTime: number;
+ // Time range
+ startTime: number;
+ endTime: number;
- // Chart types
- volumeChartType: ChartType;
- tokenChartType: ChartType;
- costChartType: ChartType;
- modelChartType: ChartType;
- latencyChartType: ChartType;
+ // Chart types
+ volumeChartType: ChartType;
+ tokenChartType: ChartType;
+ costChartType: ChartType;
+ modelChartType: ChartType;
+ latencyChartType: ChartType;
- // Model selections
- costModel: string;
- usageModel: string;
+ // Model selections
+ costModel: string;
+ usageModel: string;
- // Derived model lists
- costModels: string[];
- usageModels: string[];
- availableModels: string[];
+ // Derived model lists
+ costModels: string[];
+ usageModels: string[];
+ availableModels: string[];
- // Chart type toggle callbacks
- onVolumeChartToggle: (type: ChartType) => void;
- onTokenChartToggle: (type: ChartType) => void;
- onCostChartToggle: (type: ChartType) => void;
- onModelChartToggle: (type: ChartType) => void;
- onLatencyChartToggle: (type: ChartType) => void;
+ // Chart type toggle callbacks
+ onVolumeChartToggle: (type: ChartType) => void;
+ onTokenChartToggle: (type: ChartType) => void;
+ onCostChartToggle: (type: ChartType) => void;
+ onModelChartToggle: (type: ChartType) => void;
+ onLatencyChartToggle: (type: ChartType) => void;
- // Filter callbacks
- onCostModelChange: (model: string) => void;
- onUsageModelChange: (model: string) => void;
+ // Filter callbacks
+ onCostModelChange: (model: string) => void;
+ onUsageModelChange: (model: string) => void;
}
export function OverviewTab({
- histogramData,
- tokenData,
- costData,
- modelData,
- latencyData,
- loadingHistogram,
- loadingTokens,
- loadingCost,
- loadingModels,
- loadingLatency,
- startTime,
- endTime,
- volumeChartType,
- tokenChartType,
- costChartType,
- modelChartType,
- latencyChartType,
- costModel,
- usageModel,
- costModels,
- usageModels,
- availableModels,
- onVolumeChartToggle,
- onTokenChartToggle,
- onCostChartToggle,
- onModelChartToggle,
- onLatencyChartToggle,
- onCostModelChange,
- onUsageModelChange,
+ histogramData,
+ tokenData,
+ costData,
+ modelData,
+ latencyData,
+ loadingHistogram,
+ loadingTokens,
+ loadingCost,
+ loadingModels,
+ loadingLatency,
+ startTime,
+ endTime,
+ volumeChartType,
+ tokenChartType,
+ costChartType,
+ modelChartType,
+ latencyChartType,
+ costModel,
+ usageModel,
+ costModels,
+ usageModels,
+ availableModels,
+ onVolumeChartToggle,
+ onTokenChartToggle,
+ onCostChartToggle,
+ onModelChartToggle,
+ onLatencyChartToggle,
+ onCostModelChange,
+ onUsageModelChange,
}: OverviewTabProps) {
- return (
- <>
- {/* Charts Grid */}
-
- {/* Log Volume Chart */}
-
-
-
-
- Success
-
-
-
- Error
-
-
-
-
-
-
- }
- >
-
-
+ return (
+ <>
+ {/* Charts Grid */}
+
+ {/* Log Volume Chart */}
+
+
+
+
+ Success
+
+
+
+ Error
+
+
+
+
+
+
+ }
+ >
+
+
- {/* Token Usage Chart */}
-
-
-
-
- Input
-
-
-
- Output
-
-
-
- Cached
-
-
-
-
-
-
- }
- >
-
-
+ {/* Token Usage Chart */}
+
+
+
+
+ Input
+
+
+
+ Output
+
+
+
+ Cached
+
+
+
+
+
+
+ }
+ >
+
+
- {/* Cache Hit Rate Meter */}
-
-
-
+ {/* Cache Hit Rate Meter */}
+
+
+
- {/* Cost Chart */}
-
-
- {costModel === "all" ? (
- costModels.length > 0 && (
- <>
-
-
-
-
- {costModels[0]}
-
-
- {costModels[0]}
-
- {costModels.length > 1 && (
-
-
-
- +{costModels.length - 1} more
-
-
-
-
- {costModels.slice(1).map((model, idx) => (
-
-
- {model}
-
- ))}
-
-
-
- )}
- >
- )
- ) : (
-
-
-
-
- {costModel}
-
-
- {costModel}
-
- )}
-
-
-
-
-
-
- }
- >
-
-
+ {/* Cost Chart */}
+
+
+ {costModel === "all" ? (
+ costModels.length > 0 && (
+ <>
+
+
+
+
+
+ {costModels[0]}
+
+
+
+ {costModels[0]}
+
+ {costModels.length > 1 && (
+
+
+
+ +{costModels.length - 1} more
+
+
+
+
+ {costModels.slice(1).map((model, idx) => (
+
+
+ {model}
+
+ ))}
+
+
+
+ )}
+ >
+ )
+ ) : (
+
+
+
+
+
+ {costModel}
+
+
+
+ {costModel}
+
+ )}
+
+
+
+
+
+
+ }
+ >
+
+
- {/* Model Usage Chart */}
-
-
- {usageModel === "all" ? (
- usageModels.length > 0 && (
- <>
-
-
-
-
- {usageModels[0]}
-
-
- {usageModels[0]}
-
- {usageModels.length > 1 && (
-
-
-
- +{usageModels.length - 1} more
-
-
-
-
- {usageModels.slice(1).map((model, idx) => (
-
-
- {model}
-
- ))}
-
-
-
- )}
- >
- )
- ) : (
- <>
-
-
- Success
-
-
-
- Error
-
- >
- )}
-
-
-
-
-
-
- }
- >
-
-
+ {/* Model Usage Chart */}
+
+
+ {usageModel === "all" ? (
+ usageModels.length > 0 && (
+ <>
+
+
+
+
+
+ {usageModels[0]}
+
+
+
+ {usageModels[0]}
+
+ {usageModels.length > 1 && (
+
+
+
+ +{usageModels.length - 1} more
+
+
+
+
+ {usageModels.slice(1).map((model, idx) => (
+
+
+ {model}
+
+ ))}
+
+
+
+ )}
+ >
+ )
+ ) : (
+ <>
+
+
+ Success
+
+
+
+ Error
+
+ >
+ )}
+
+
+
+
+
+
+ }
+ >
+
+
- {/* Latency Chart */}
-
-
-
-
- Avg
-
-
-
- P90
-
-
-
- P95
-
-
-
- P99
-
-
-
-
-
-
- }
- >
-
-
-
- >
- );
-}
\ No newline at end of file
+ {/* Latency Chart */}
+
+
+
+
+ Avg
+
+
+
+ P90
+
+
+
+ P95
+
+
+
+ P99
+
+
+
+
+
+
+ }
+ >
+
+
+
+ >
+ );
+}
diff --git a/ui/app/workspace/logs/page.tsx b/ui/app/workspace/logs/page.tsx
index af26b87b39..230404bdec 100644
--- a/ui/app/workspace/logs/page.tsx
+++ b/ui/app/workspace/logs/page.tsx
@@ -11,1090 +11,1309 @@ import { useColumnConfig } from "@/components/table";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Card, CardContent } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
-import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
import { useWebSocket } from "@/hooks/useWebSocket";
import {
- getErrorMessage,
- useDeleteLogsMutation,
- useGetAvailableFilterDataQuery,
- useLazyGetLogsHistogramQuery,
- useLazyGetLogsQuery,
- useLazyGetLogsStatsQuery,
+ getErrorMessage,
+ useDeleteLogsMutation,
+ useGetAvailableFilterDataQuery,
+ useLazyGetLogsHistogramQuery,
+ useLazyGetLogsQuery,
+ useLazyGetLogsStatsQuery,
} from "@/lib/store";
import { useLazyGetLogByIdQuery } from "@/lib/store/apis/logsApi";
import type {
- ChatMessage,
- ChatMessageContent,
- ContentBlock,
- LogEntry,
- LogFilters,
- LogsHistogramResponse,
- LogStats,
- Pagination,
+ ChatMessage,
+ ChatMessageContent,
+ ContentBlock,
+ LogEntry,
+ LogFilters,
+ LogsHistogramResponse,
+ LogStats,
+ Pagination,
} from "@/lib/types/logs";
import { dateUtils } from "@/lib/types/logs";
import { COMPACT_NUMBER_FORMAT } from "@/lib/utils/numbers";
import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib";
import NumberFlow from "@number-flow/react";
-import { AlertCircle, BarChart, CheckCircle, Clock, DollarSign, Hash, Info } from "lucide-react";
-import { parseAsArrayOf, parseAsBoolean, parseAsInteger, parseAsString, useQueryStates } from "nuqs";
+import {
+ AlertCircle,
+ BarChart,
+ CheckCircle,
+ Clock,
+ DollarSign,
+ Hash,
+ Info,
+} from "lucide-react";
+import {
+ parseAsArrayOf,
+ parseAsBoolean,
+ parseAsInteger,
+ parseAsString,
+ useQueryStates,
+} from "nuqs";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
export default function LogsPage() {
- const [logs, setLogs] = useState([]);
- const [totalItems, setTotalItems] = useState(0); // changes with filters
- const [stats, setStats] = useState(null);
- const [histogram, setHistogram] = useState(null);
- const [initialLoading, setInitialLoading] = useState(true); // on initial load
- const [fetchingLogs, setFetchingLogs] = useState(false); // on pagination/filters change
- const [fetchingStats, setFetchingStats] = useState(false); // on stats fetch
- const [fetchingHistogram, setFetchingHistogram] = useState(false); // on histogram fetch
- const [error, setError] = useState(null);
- const [showEmptyState, setShowEmptyState] = useState(false);
-
- const hasDeleteAccess = useRbac(RbacResource.Logs, RbacOperation.Delete);
-
- // RTK Query lazy hooks for manual triggering
- const [triggerGetLogs] = useLazyGetLogsQuery();
- const [triggerGetStats] = useLazyGetLogsStatsQuery();
- const [triggerGetHistogram] = useLazyGetLogsHistogramQuery();
- const [deleteLogs] = useDeleteLogsMutation();
-
- const [selectedSessionId, setSelectedSessionId] = useState(null);
- const [sessionHighlightedLogId, setSessionHighlightedLogId] = useState(null);
- // Stable handler so SessionDetailsSheet's loadSessionPage useCallback doesn't
- // recreate on every parent re-render. Without this, every live WebSocket log
- // tick would re-render LogsPage, hand the sheet a fresh inline arrow, recreate
- // loadSessionPage, and trip the reset effect — wiping sessionLogs and
- // refetching from offset 0 while the sheet is open.
- const handleSessionSheetOpenChange = useCallback((open: boolean) => {
- if (!open) {
- setSelectedSessionId(null);
- setSessionHighlightedLogId(null);
- }
- }, []);
- const [isChartOpen, setIsChartOpen] = useState(true);
- const [triggerGetLogById] = useLazyGetLogByIdQuery();
- const [fetchedLog, setFetchedLog] = useState(null);
-
- // Debouncing for streaming updates (client-side)
- const streamingUpdateTimeouts = useRef