From 9da9a0bc9cf1700ca7280754f53c8a1388f95be1 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Wed, 11 Feb 2026 19:19:52 -0800 Subject: [PATCH] UI refactor view logs/sessions --- .../src/components/view_logs/index.tsx | 173 +++++++----------- 1 file changed, 71 insertions(+), 102 deletions(-) diff --git a/ui/litellm-dashboard/src/components/view_logs/index.tsx b/ui/litellm-dashboard/src/components/view_logs/index.tsx index 32ad34fa9ca..d7d9e65ed3f 100644 --- a/ui/litellm-dashboard/src/components/view_logs/index.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/index.tsx @@ -17,13 +17,12 @@ import { fetchAllKeyAliases } from "../key_team_helpers/filter_helpers"; import { KeyResponse, Team } from "../key_team_helpers/key_list"; import { PaginatedModelSelect } from "../ModelSelect/PaginatedModelSelect/PaginatedModelSelect"; import FilterComponent, { FilterOption } from "../molecules/filter"; -import { allEndUsersCall, keyInfoV1Call, keyListCall, sessionSpendLogsCall, uiSpendLogsCall } from "../networking"; +import { allEndUsersCall, keyInfoV1Call, keyListCall, uiSpendLogsCall } from "../networking"; import KeyInfoView from "../templates/key_info_view"; import AuditLogs from "./audit_logs"; -import { columns, expanderColumn, LogEntry } from "./columns"; -import { SessionChildRows } from "./McpChildRows"; +import { columns, LogEntry } from "./columns"; import { ConfigInfoMessage } from "./ConfigInfoMessage"; -import { ERROR_CODE_OPTIONS, QUICK_SELECT_OPTIONS } from "./constants"; +import { ERROR_CODE_OPTIONS, MCP_CALL_TYPES, QUICK_SELECT_OPTIONS } from "./constants"; import { CostBreakdownViewer } from "./CostBreakdownViewer"; import { ErrorViewer } from "./ErrorViewer"; import { useLogFilterLogic } from "./log_filter_logic"; @@ -31,7 +30,6 @@ import { LogDetailsDrawer } from "./LogDetailsDrawer"; import { getTimeRangeDisplay } from "./logs_utils"; import { prefetchLogDetails } from "./prefetch"; import { RequestResponsePanel } from "./RequestResponsePanel"; -import { SessionView } from "./SessionView"; import SpendLogsSettingsModal from "./SpendLogsSettingsModal/SpendLogsSettingsModal"; import { DataTable } from "./table"; import { VectorStoreViewer } from "./VectorStoreViewer"; @@ -305,76 +303,87 @@ export default function SpendLogsTable({ } }, [filters, accessToken, fetchKeyHashForAlias]); - // Fetch logs for a session if selected - const sessionLogs = useQuery({ - queryKey: ["sessionLogs", selectedSessionId], - queryFn: async () => { - if (!accessToken || !selectedSessionId) return { data: [], total: 0, page: 1, page_size: 50, total_pages: 1 }; - const response = await sessionSpendLogsCall(accessToken, selectedSessionId); - // If the API returns an array, wrap it in the same shape as PaginatedResponse - return { - data: response.data || response || [], - total: (response.data || response || []).length, - page: 1, - page_size: 1000, - total_pages: 1, - }; - }, - enabled: !!accessToken && !!selectedSessionId, - }); - - if (!accessToken || !token || !userRole || !userID) { return null; } - const filteredData = - filteredLogs.data - .filter((log) => { - const matchesSearch = - !searchTerm || - log.request_id.includes(searchTerm) || - log.model.includes(searchTerm) || - (log.user && log.user.includes(searchTerm)); + const searchedLogs = filteredLogs.data.filter((log) => { + const matchesSearch = + !searchTerm || + log.request_id.includes(searchTerm) || + log.model.includes(searchTerm) || + (log.user && log.user.includes(searchTerm)); - // No need for additional filtering since we're now handling this in the API call - return matchesSearch; + // No need for additional filtering since we're now handling this in the API call + return matchesSearch; + }); + + const sessionCompositionById = searchedLogs.reduce>((acc, log) => { + if (!log.session_id) return acc; + if (!acc[log.session_id]) { + acc[log.session_id] = { llm: 0, mcp: 0 }; + } + if (MCP_CALL_TYPES.includes(log.call_type)) { + acc[log.session_id].mcp += 1; + } else { + acc[log.session_id].llm += 1; + } + return acc; + }, {}); + + const filteredData = + searchedLogs + .map((log) => { + const sessionComposition = log.session_id ? sessionCompositionById[log.session_id] : undefined; + return { + ...log, + duration: (Date.parse(log.endTime) - Date.parse(log.startTime)) / 1000, + session_llm_count: sessionComposition?.llm ?? undefined, + session_mcp_count: sessionComposition?.mcp ?? undefined, + onKeyHashClick: (keyHash: string) => setSelectedKeyIdInfoView(keyHash), + onSessionClick: (sessionId: string) => { + if (sessionId) { + setSelectedSessionId(sessionId); + setSelectedLog(log); + setIsDrawerOpen(true); + } + }, + }; }) - .map((log) => ({ - ...log, - duration: (Date.parse(log.endTime) - Date.parse(log.startTime)) / 1000, - onKeyHashClick: (keyHash: string) => setSelectedKeyIdInfoView(keyHash), - onSessionClick: (sessionId: string) => { - if (sessionId) setSelectedSessionId(sessionId); - }, - })) - // Deduplicate multi-call sessions: show only the first row per session_id + // Deduplicate multi-call sessions: + // Prefer showing an LLM row as the root representative when available. .filter((log, _index, arr) => { if (!log.session_id || (log.session_total_count || 1) <= 1) return true; - return arr.findIndex((l) => l.session_id === log.session_id) === _index; + const rowsForSession = arr.filter((l) => l.session_id === log.session_id); + const llmRepresentative = rowsForSession.find( + (l) => !MCP_CALL_TYPES.includes(l.call_type), + ); + const representative = llmRepresentative || rowsForSession[0]; + return representative.request_id === log.request_id; }) || []; - // For session logs, add onKeyHashClick/onSessionClick as well - const sessionData = - sessionLogs.data?.data?.map((log) => ({ - ...log, - onKeyHashClick: (keyHash: string) => setSelectedKeyIdInfoView(keyHash), - onSessionClick: (sessionId: string) => { }, - })) || []; - // Add this function to handle manual refresh const handleRefresh = () => { logs.refetch(); }; const handleRowClick = (log: LogEntry) => { + // Multi-call session row: open in the same right-side drawer (session mode) + if (log.session_id && (log.session_total_count || 1) > 1) { + setSelectedSessionId(log.session_id); + setSelectedLog(log); + setIsDrawerOpen(true); + return; + } + // Single-call row: open the detail drawer + setSelectedSessionId(null); setSelectedLog(log); setIsDrawerOpen(true); }; const handleCloseDrawer = () => { setIsDrawerOpen(false); - // Optionally keep selectedLog for animation purposes + setSelectedSessionId(null); }; const handleSelectLog = (log: LogEntry) => { @@ -468,19 +477,6 @@ export default function SpendLogsTable({ }, ]; - // When a session is selected, render the SessionView component - if (selectedSessionId && sessionLogs.data) { - return ( -
- setSelectedSessionId(null)} - /> -
- ); - } - const formatTimeUnit = (value: number, unit: string) => { if (value === 1) { if (unit === "minutes") return "minute"; @@ -508,29 +504,12 @@ export default function SpendLogsTable({
-

- {selectedSessionId ? ( - <> - Session: {selectedSessionId} - - - ) : ( - "Request Logs" - )} -

- {!selectedSessionId && ( -
{selectedKeyInfo && selectedKeyIdInfoView && selectedKeyInfo.api_key === selectedKeyIdInfoView ? ( setSelectedKeyIdInfoView(null)} backButtonText="Back to Logs" /> - ) : selectedSessionId ? ( -
- -
) : ( <> )} (row.original.session_total_count || 1) > 1} - renderChildRows={({ row }) => ( - - )} /> @@ -773,6 +740,8 @@ export default function SpendLogsTable({ open={isDrawerOpen} onClose={handleCloseDrawer} logEntry={selectedLog} + sessionId={selectedSessionId} + accessToken={accessToken} onOpenSettings={() => setIsSpendLogsSettingsModalVisible(true)} allLogs={filteredData} onSelectLog={handleSelectLog}