From e2f7b10c8d0694172fcdc5f072e36c7e5b5d733c Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Fri, 30 Jan 2026 18:20:50 -0800 Subject: [PATCH] address feedback --- .../view_logs/CostBreakdownViewer.tsx | 14 +- .../LogDetailsDrawer/DrawerHeader.tsx | 205 ++++++++ .../view_logs/LogDetailsDrawer/JsonViewer.tsx | 35 ++ .../LogDetailsDrawer/LogDetailsDrawer.tsx | 441 ++++++++++++++++++ .../view_logs/LogDetailsDrawer/TokenFlow.tsx | 22 + .../LogDetailsDrawer/TruncatedValue.tsx | 35 ++ .../LogDetailsDrawer/clipboardUtils.ts | 43 -- .../view_logs/LogDetailsDrawer/constants.ts | 4 +- .../view_logs/LogDetailsDrawer/index.ts | 2 + .../LogDetailsDrawer/useKeyboardNavigation.ts | 87 ++++ .../src/components/view_logs/columns.tsx | 40 -- .../src/components/view_logs/index.tsx | 46 +- .../src/components/view_logs/table.tsx | 24 +- 13 files changed, 885 insertions(+), 113 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/DrawerHeader.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailsDrawer.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/TokenFlow.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/TruncatedValue.tsx delete mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/clipboardUtils.ts create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/index.ts create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/useKeyboardNavigation.ts diff --git a/ui/litellm-dashboard/src/components/view_logs/CostBreakdownViewer.tsx b/ui/litellm-dashboard/src/components/view_logs/CostBreakdownViewer.tsx index affe28e0b2..c56e2d3af5 100644 --- a/ui/litellm-dashboard/src/components/view_logs/CostBreakdownViewer.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/CostBreakdownViewer.tsx @@ -7,6 +7,7 @@ export interface CostBreakdown { output_cost?: number; total_cost?: number; tool_usage_cost?: number; + additional_costs?: Record; original_cost?: number; discount_percent?: number; discount_amount?: number; @@ -59,7 +60,7 @@ export const CostBreakdownViewer: React.FC = ({ } return ( -
+
@@ -88,6 +89,17 @@ export const CostBreakdownViewer: React.FC = ({ {formatCost(costBreakdown.tool_usage_cost)}
)} + {/* Additional Costs (free-form) */} + {costBreakdown.additional_costs && Object.keys(costBreakdown.additional_costs).length > 0 && ( + <> + {Object.entries(costBreakdown.additional_costs).map(([key, value]) => ( +
+ {key}: + {formatCost(value)} +
+ ))} + + )}
{/* Subtotal / Original Cost */} diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/DrawerHeader.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/DrawerHeader.tsx new file mode 100644 index 0000000000..25fcdf953e --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/DrawerHeader.tsx @@ -0,0 +1,205 @@ +import { Button, Space, Tag, Tooltip, Typography } from "antd"; +import { CloseOutlined, UpOutlined, DownOutlined } from "@ant-design/icons"; +import moment from "moment"; +import { LogEntry } from "../columns"; +import { getProviderLogoAndName } from "../../provider_info_helpers"; +import { + DRAWER_HEADER_PADDING, + COLOR_BORDER, + COLOR_BACKGROUND, + SPACING_MEDIUM, + SPACING_LARGE, + FONT_SIZE_HEADER, + FONT_SIZE_MEDIUM, + FONT_FAMILY_MONO, + SPACING_SMALL, +} from "./constants"; + +const { Text } = Typography; + +interface DrawerHeaderProps { + log: LogEntry; + onClose: () => void; + onPrevious: () => void; + onNext: () => void; + statusLabel: string; + statusColor: "error" | "success"; + environment: string; +} + +/** + * Header component for the log details drawer. + * Displays model/provider, request ID, navigation controls, status, environment, and timestamp. + */ +export function DrawerHeader({ + log, + onClose, + onPrevious, + onNext, + statusLabel, + statusColor, + environment, +}: DrawerHeaderProps) { + const provider = log.custom_llm_provider || ""; + const providerInfo = provider ? getProviderLogoAndName(provider) : null; + + return ( +
+ {/* Row 0: Model + Provider with Logo */} + + + {/* Row 1: Request ID + Actions */} +
+ + +
+ + {/* Row 2: Status + Env + Timestamp */} + +
+ ); +} + +/** + * Model and Provider display with logo + */ +function ModelProviderSection({ + model, + providerLogo, + providerName, +}: { + model: string; + providerLogo?: string; + providerName?: string; +}) { + return ( + + {providerLogo && ( + {providerName { + const target = e.target as HTMLImageElement; + target.style.display = "none"; + }} + /> + )} + + + {model} + + {providerName && ( + + {providerName} + + )} + + + ); +} + +/** + * Request ID display with copy functionality + */ +function RequestIdSection({ requestId }: { requestId: string }) { + return ( +
+ + + {requestId} + + +
+ ); +} + +/** + * Navigation controls (previous, next, close) + * Shows keyboard shortcuts with bounding boxes for visibility + */ +function NavigationSection({ + onPrevious, + onNext, + onClose, +}: { + onPrevious: () => void; + onNext: () => void; + onClose: () => void; +}) { + const keyboardShortcutStyle = { + border: "1px solid #d9d9d9", + borderRadius: 4, + padding: "0 4px", + fontSize: 12, + fontFamily: "monospace", + marginLeft: 4, + background: "#fafafa", + }; + + return ( + }> + + + + - ) : ( - - ); - }; - - // Return the component - return ; - }, - }, { header: "Time", accessorKey: "startTime", diff --git a/ui/litellm-dashboard/src/components/view_logs/index.tsx b/ui/litellm-dashboard/src/components/view_logs/index.tsx index 826fc7ccc0..3859a5e51f 100644 --- a/ui/litellm-dashboard/src/components/view_logs/index.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/index.tsx @@ -31,6 +31,7 @@ import SpendLogsSettingsModal from "./SpendLogsSettingsModal/SpendLogsSettingsMo import { DataTable } from "./table"; import { VectorStoreViewer } from "./VectorStoreViewer"; import NewBadge from "../common_components/NewBadge"; +import { LogDetailsDrawer } from "./LogDetailsDrawer"; interface SpendLogsTableProps { accessToken: string | null; @@ -89,7 +90,8 @@ export default function SpendLogsTable({ const [filterByCurrentUser, setFilterByCurrentUser] = useState(userRole && internalUserRoles.includes(userRole)); const [activeTab, setActiveTab] = useState("request logs"); - const [expandedRequestId, setExpandedRequestId] = useState(null); + const [selectedLog, setSelectedLog] = useState(null); + const [isDrawerOpen, setIsDrawerOpen] = useState(false); const [selectedSessionId, setSelectedSessionId] = useState(null); const [isSpendLogsSettingsModalVisible, setIsSpendLogsSettingsModalVisible] = useState(false); @@ -317,17 +319,6 @@ export default function SpendLogsTable({ enabled: !!accessToken && !!selectedSessionId, }); - // Add this effect to preserve expanded state when data refreshes - useEffect(() => { - if (logs.data?.data && expandedRequestId) { - // Check if the expanded request ID still exists in the new data - const stillExists = logs.data.data.some((log) => log.request_id === expandedRequestId); - if (!stillExists) { - // If the request ID no longer exists in the data, clear the expanded state - setExpandedRequestId(null); - } - } - }, [logs.data?.data, expandedRequestId]); if (!accessToken || !token || !userRole || !userID) { return null; @@ -367,8 +358,18 @@ export default function SpendLogsTable({ logs.refetch(); }; - const handleRowExpand = (requestId: string | null) => { - setExpandedRequestId(requestId); + const handleRowClick = (log: LogEntry) => { + setSelectedLog(log); + setIsDrawerOpen(true); + }; + + const handleCloseDrawer = () => { + setIsDrawerOpen(false); + // Optionally keep selectedLog for animation purposes + }; + + const handleSelectLog = (log: LogEntry) => { + setSelectedLog(log); }; // Function to extract unique error codes from logs @@ -554,9 +555,7 @@ export default function SpendLogsTable({ setIsSpendLogsSettingsModalVisible(true)} />} - getRowCanExpand={() => true} - // Optionally: add session-specific row expansion state + onRowClick={handleRowClick} />
) : ( @@ -753,8 +752,7 @@ export default function SpendLogsTable({ setIsSpendLogsSettingsModalVisible(true)} />} - getRowCanExpand={() => true} + onRowClick={handleRowClick} /> @@ -775,6 +773,16 @@ export default function SpendLogsTable({ + + {/* Log Details Drawer */} + setIsSpendLogsSettingsModalVisible(true)} + allLogs={filteredData} + onSelectLog={handleSelectLog} + /> ); } diff --git a/ui/litellm-dashboard/src/components/view_logs/table.tsx b/ui/litellm-dashboard/src/components/view_logs/table.tsx index 605341cb2e..fb7706cba1 100644 --- a/ui/litellm-dashboard/src/components/view_logs/table.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/table.tsx @@ -6,8 +6,10 @@ import { Table, TableHead, TableHeaderCell, TableBody, TableRow, TableCell } fro interface DataTableProps { data: TData[]; columns: ColumnDef[]; - renderSubComponent: (props: { row: Row }) => React.ReactElement; - getRowCanExpand: (row: Row) => boolean; + onRowClick?: (row: TData) => void; + // Legacy props for backward compatibility (audit logs) + renderSubComponent?: (props: { row: Row }) => React.ReactElement; + getRowCanExpand?: (row: Row) => boolean; isLoading?: boolean; loadingMessage?: string; noDataMessage?: string; @@ -16,22 +18,26 @@ interface DataTableProps { export function DataTable({ data = [], columns, - getRowCanExpand, + onRowClick, renderSubComponent, + getRowCanExpand, isLoading = false, loadingMessage = "🚅 Loading logs...", noDataMessage = "No logs found", }: DataTableProps) { + // Determine if we're in legacy expansion mode or new drawer mode + const isLegacyMode = !!renderSubComponent && !!getRowCanExpand; + const table = useReactTable({ data, columns, - getRowCanExpand, + ...(isLegacyMode && { getRowCanExpand }), getRowId: (row: TData, index: number) => { const _row: any = row as any; return _row?.request_id ?? String(index); }, getCoreRowModel: getCoreRowModel(), - getExpandedRowModel: getExpandedRowModel(), + ...(isLegacyMode && { getExpandedRowModel: getExpandedRowModel() }), }); return ( @@ -62,7 +68,10 @@ export function DataTable({ ) : table.getRowModel().rows.length > 0 ? ( table.getRowModel().rows.map((row) => ( - + !isLegacyMode && onRowClick?.(row.original)} + > {row.getVisibleCells().map((cell) => ( {flexRender(cell.column.columnDef.cell, cell.getContext())} @@ -70,7 +79,8 @@ export function DataTable({ ))} - {row.getIsExpanded() && ( + {/* Legacy expansion mode for audit logs */} + {isLegacyMode && row.getIsExpanded() && renderSubComponent && (
{renderSubComponent({ row })}