import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Sheet, SheetContent } from "@/components/ui/sheet"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { ProviderIconType, RenderProviderIcon } from "@/lib/constants/icons"; import type { ProviderName } from "@/lib/constants/logs"; import { RequestTypeColors, RequestTypeLabels, Status, StatusBarColors } from "@/lib/constants/logs"; import { getErrorMessage } from "@/lib/store"; import { useGetLogSessionSummaryByIdQuery, useLazyGetLogSessionByIdQuery } from "@/lib/store/apis/logsApi"; import { LogEntry } from "@/lib/types/logs"; import { cn } from "@/lib/utils"; import { ArrowDown, ArrowUp, Loader2 } from "lucide-react"; import { format } from "date-fns"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; import { LogMessageCell } from "../views/columns"; const SESSION_LOG_PAGE_SIZE = 500; const HIGHLIGHTED_ROW = "border-l-2 border-l-sky-500 bg-sky-500/[0.08] shadow-[inset_0_0_0_1px_rgba(56,189,248,0.18)] hover:bg-sky-500/[0.24] hover:shadow-[inset_0_0_0_1px_rgba(56,189,248,0.38)] dark:hover:bg-sky-400/[0.18]"; function formatDurationFromMs(durationMs?: number) { if (!durationMs || durationMs <= 0) return "0s"; const totalSeconds = Math.floor(durationMs / 1000); const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; if (hours > 0) { return `${hours}h ${String(minutes).padStart(2, "0")}m ${String(seconds).padStart(2, "0")}s`; } if (minutes > 0) { return `${minutes}m ${String(seconds).padStart(2, "0")}s`; } return `${seconds}s`; } interface SummaryCard { label: string; value: string; helper?: string; size?: "sm"; } interface SessionDetailsSheetProps { sessionId: string | null; highlightedLogId?: string | null; open: boolean; onOpenChange: (open: boolean) => void; onLogClick?: (log: LogEntry) => void; onFilterByParentRequestId?: (parentRequestId: string) => void; } export function SessionDetailsSheet({ sessionId, highlightedLogId, open, onOpenChange, onLogClick, onFilterByParentRequestId, }: SessionDetailsSheetProps) { const [triggerGetSession] = useLazyGetLogSessionByIdQuery(); const [sessionLogs, setSessionLogs] = useState([]); const [loadingSession, setLoadingSession] = useState(false); const [totalCount, setTotalCount] = useState(0); const [fetchedCount, setFetchedCount] = useState(0); const fetchedCountRef = useRef(fetchedCount); const totalCountRef = useRef(totalCount); const [hasMore, setHasMore] = useState(false); const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc"); const { data: sessionSummary } = useGetLogSessionSummaryByIdQuery(sessionId || "", { skip: !open || !sessionId, pollingInterval: 5000, refetchOnMountOrArgChange: true, }); const summaryCards: SummaryCard[] = useMemo( () => [ { label: "Logs", value: (sessionSummary?.count || 0).toLocaleString(), helper: sessionSummary && sessionLogs.length < sessionSummary.count ? `(${sessionLogs.length.toLocaleString()} loaded)` : undefined, }, { label: "Total Cost", value: `$${(sessionSummary?.total_cost || 0).toFixed(4)}`, }, { label: "Total Tokens", value: (sessionSummary?.total_tokens || 0).toLocaleString(), }, { label: "Started", value: sessionSummary?.started_at ? format(new Date(sessionSummary.started_at), "MMM d, yyyy hh:mm:ss aa") : "N/A", size: "sm", }, { label: "Latest Update", value: sessionSummary?.latest_at ? format(new Date(sessionSummary.latest_at), "MMM d, yyyy hh:mm:ss aa") : "N/A", size: "sm", }, { label: "Duration", value: formatDurationFromMs(sessionSummary?.duration_ms), }, ], [sessionSummary, sessionLogs.length], ); const sortSessionLogs = useCallback( (logs: LogEntry[]) => [...logs].sort((a, b) => sortOrder === "asc" ? new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime() : new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(), ), [sortOrder], ); const loadSessionPage = useCallback( async (offset: number, reset = false) => { if (!sessionId) return; setLoadingSession(true); try { const result = await triggerGetSession({ sessionId, pagination: { limit: SESSION_LOG_PAGE_SIZE, offset, order: sortOrder }, }); if (result.error) { toast.error("Failed to load session logs", { description: getErrorMessage(result.error), }); return; } if (result.data) { if (reset && result.data.count === 0) { onOpenChange(false); return; } setTotalCount(result.data.count); setHasMore(result.data.has_more); setFetchedCount(offset + result.data.returned_count); setSessionLogs((prev) => { const next = reset ? result.data!.logs : [...prev, ...result.data!.logs]; const seen = new Map(); for (const log of next) { seen.set(log.id, log); } return sortSessionLogs(Array.from(seen.values())); }); } } finally { setLoadingSession(false); } }, [onOpenChange, sessionId, sortOrder, sortSessionLogs, triggerGetSession], ); useEffect(() => { fetchedCountRef.current = fetchedCount; }, [fetchedCount]); useEffect(() => { totalCountRef.current = totalCount; }, [totalCount]); useEffect(() => { if (!open || !sessionId) { return; } setSessionLogs([]); setFetchedCount(0); setTotalCount(0); fetchedCountRef.current = 0; totalCountRef.current = 0; setHasMore(false); loadSessionPage(0, true); }, [open, sessionId, sortOrder, loadSessionPage]); return (
Session
{sessionId && onFilterByParentRequestId ? ( onFilterByParentRequestId(sessionId)} > {sessionId} Filter this session ) : ( {sessionId} )}
{summaryCards.map((card) => (
{card.label}
{card.helper ? (
{card.value} {card.helper}
) : ( card.value )}
))}
Time Type Message Provider Model {loadingSession && sessionLogs.length === 0 ? (
Loading session...
) : sessionLogs.length ? ( sessionLogs.map((log) => ( onLogClick?.(log)} >
{log.id === highlightedLogId ? (
Current
) : null} {format(new Date(log.timestamp), "yyyy-MM-dd hh:mm:ss aa (XXX)")}
{RequestTypeLabels[log.object as keyof typeof RequestTypeLabels]} {log.provider as ProviderName} {log.model || "N/A"} )) ) : ( No logs found for this session. )}
{hasMore ? (
) : null}
); }