diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/DrawerHeader.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/DrawerHeader.tsx index 364959b0b5..6b3f7fdaa5 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/DrawerHeader.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/DrawerHeader.tsx @@ -2,6 +2,7 @@ import { Button, Tag, Tooltip, Typography } from "antd"; import { CloseOutlined, CopyOutlined, 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, @@ -29,7 +30,7 @@ interface DrawerHeaderProps { /** * Header component for the log details drawer. - * Displays request ID, navigation controls, status, environment, and timestamp. + * Displays model/provider, request ID, navigation controls, status, environment, and timestamp. */ export function DrawerHeader({ log, @@ -41,6 +42,9 @@ export function DrawerHeader({ 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 */}
@@ -64,6 +71,45 @@ export function DrawerHeader({ ); } +/** + * 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 button */ @@ -93,6 +139,7 @@ function RequestIdSection({ requestId, onCopy }: { requestId: string; onCopy: () /** * Navigation controls (previous, next, close) + * Shows keyboard shortcuts with bounding boxes for visibility */ function NavigationSection({ onPrevious, @@ -103,18 +150,32 @@ function NavigationSection({ onNext: () => void; onClose: () => void; }) { + const keyboardShortcutStyle = { + border: "1px solid #d9d9d9", + borderRadius: 4, + padding: "0 4px", + fontSize: 12, + fontFamily: "monospace", + marginLeft: 4, + background: "#fafafa", + }; + return (
- - +
-
); } diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.tsx index 1f1d935973..6463abf89d 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.tsx @@ -1,53 +1,22 @@ import { Typography } from "antd"; import { JsonView, defaultStyles } from "react-json-view-lite"; import "react-json-view-lite/dist/index.css"; -import { - JSON_MAX_HEIGHT, - FONT_SIZE_SMALL, - COLOR_BG_LIGHT, - SPACING_LARGE, - FONT_FAMILY_MONO, - VIEW_MODE_JSON, -} from "./constants"; +import { JSON_MAX_HEIGHT, COLOR_BG_LIGHT, SPACING_LARGE } from "./constants"; const { Text } = Typography; -export type ViewMode = "formatted" | "json"; - interface JsonViewerProps { data: any; - mode: ViewMode; + mode: "formatted"; } /** - * Displays JSON data in either formatted tree view or raw JSON format. - * Formatted view uses an interactive tree, JSON view shows raw stringified output. + * Displays JSON data in formatted tree view. + * Uses an interactive tree component for easy navigation. */ -export function JsonViewer({ data, mode }: JsonViewerProps) { +export function JsonViewer({ data }: JsonViewerProps) { if (!data) return No data; - if (mode === VIEW_MODE_JSON) { - return ( -
-        {JSON.stringify(data, null, 2)}
-      
- ); - } - - // Formatted tree view return (
(TAB_REQUEST); - const [jsonViewMode, setJsonViewMode] = useState(VIEW_MODE_FORMATTED); // Keyboard navigation const { selectNextLog, selectPreviousLog } = useKeyboardNavigation({ @@ -162,8 +160,9 @@ export function LogDetailsDrawer({ )} {/* Request Details Section */} - - +
+ + {logEntry.model} {logEntry.custom_llm_provider || "-"} {logEntry.call_type} @@ -183,6 +182,7 @@ export function LogDetailsDrawer({ )} +
{/* Metrics Section */} @@ -193,31 +193,19 @@ export function LogDetailsDrawer({ {/* Configuration Info Message - Show when data is missing */} - {/* Request/Response JSON - Using Tabs with View Toggle */} + {/* Request/Response JSON - Collapsible */} copyToClipboard(JSON.stringify(data, null, 2), label)} getRawRequest={getRawRequest} getFormattedResponse={getFormattedResponse} /> {/* Guardrail Data - Show only if present */} - {hasGuardrailData && ( -
- -
- )} + {hasGuardrailData && } {/* Vector Store Request Data - Show only if present */} - {hasVectorStoreData && ( -
- -
- )} + {hasVectorStoreData && } {/* Metadata Card - Only show if there's metadata */} {logEntry.metadata && Object.keys(logEntry.metadata).length > 0 && ( @@ -251,8 +239,8 @@ function ErrorDescription({ errorInfo }: { errorInfo: any }) { function TagsSection({ tags }: { tags: Record }) { return ( -
- +
+ Tags
@@ -286,8 +274,9 @@ function MetricsSection({ logEntry, metadata }: { logEntry: LogEntry; metadata: metadata.additional_usage_values.cache_read_input_tokens > 0); return ( - - +
+ + )} - {metadata?.litellm_overhead_time_ms !== undefined && ( + {metadata?.litellm_overhead_time_ms !== undefined && metadata.litellm_overhead_time_ms !== null && ( {metadata.litellm_overhead_time_ms.toFixed(2)} ms @@ -331,30 +320,26 @@ function MetricsSection({ logEntry, metadata }: { logEntry: LogEntry; metadata: +
); } interface RequestResponseSectionProps { - activeTab: typeof TAB_REQUEST | typeof TAB_RESPONSE; - jsonViewMode: ViewMode; hasResponse: boolean; - onTabChange: (key: typeof TAB_REQUEST | typeof TAB_RESPONSE) => void; - onViewModeChange: (mode: ViewMode) => void; onCopy: (data: any, label: string) => void; getRawRequest: () => any; getFormattedResponse: () => any; } function RequestResponseSection({ - activeTab, - jsonViewMode, hasResponse, - onTabChange, - onViewModeChange, onCopy, getRawRequest, getFormattedResponse, }: RequestResponseSectionProps) { + const [activeTab, setActiveTab] = useState(TAB_REQUEST); + const [isOpen, setIsOpen] = useState(true); + const handleCopy = () => { const data = activeTab === TAB_REQUEST ? getRawRequest() : getFormattedResponse(); const label = activeTab === TAB_REQUEST ? "Request" : "Response"; @@ -362,98 +347,109 @@ function RequestResponseSection({ }; return ( - - onTabChange(key as typeof TAB_REQUEST | typeof TAB_RESPONSE)} - tabBarExtraContent={ -
- {/* View Mode Toggle */} - onViewModeChange(e.target.value)}> - Formatted - JSON - - - {/* Copy Button */} - -
- } +
+ setIsOpen(keys.includes("request-response"))} + expandIcon={({ isActive }) => } + bordered={false} items={[ { - key: TAB_REQUEST, - label: "Request", + key: "request-response", + label: Request & Response, children: ( -
- -
- ), - }, - { - key: TAB_RESPONSE, - label: "Response", - children: ( -
- {hasResponse ? ( - - ) : ( -
- Response data not available -
- )} -
+ setActiveTab(key as typeof TAB_REQUEST | typeof TAB_RESPONSE)} + tabBarExtraContent={ + + } + items={[ + { + key: TAB_REQUEST, + label: "Request", + children: ( +
+ +
+ ), + }, + { + key: TAB_RESPONSE, + label: "Response", + children: ( +
+ {hasResponse ? ( + + ) : ( +
+ Response data not available +
+ )} +
+ ), + }, + ]} + /> ), }, ]} - style={{ padding: `0 ${SPACING_XLARGE}px` }} + styles={{ + header: { + padding: "16px", + borderBottom: "1px solid #f0f0f0", + }, + body: { + padding: 0, + }, + }} /> - +
); } function MetadataSection({ metadata, onCopy }: { metadata: Record; onCopy: (data: string) => void }) { return ( - } - onClick={() => onCopy(JSON.stringify(metadata, null, 2))} - > - Copy - - } - > -
+      }
+            onClick={() => onCopy(JSON.stringify(metadata, null, 2))}
+          >
+            Copy
+          
+        }
       >
-        {JSON.stringify(metadata, null, 2)}
-      
-
+
+          {JSON.stringify(metadata, null, 2)}
+        
+
+
); } diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/TokenFlow.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/TokenFlow.tsx index a9a30e55f1..5eec3c0a5c 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/TokenFlow.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/TokenFlow.tsx @@ -1,5 +1,4 @@ import { Typography } from "antd"; -import { COLOR_SECONDARY, FONT_FAMILY_MONO, FONT_SIZE_MEDIUM, SPACING_SMALL, SPACING_MEDIUM } from "./constants"; const { Text } = Typography; @@ -10,18 +9,14 @@ interface TokenFlowProps { } /** - * Displays token usage in a flow format: "prompt → completion (Σ total)" - * Makes it easy to see the relationship between input, output, and total tokens. + * Displays token usage in LiteLLM format: "12 (9 prompt tokens + 3 completion tokens)" + * Shows total with breakdown of prompt and completion tokens. */ export function TokenFlow({ prompt = 0, completion = 0, total = 0 }: TokenFlowProps) { return ( - - {prompt.toLocaleString()} - - {completion.toLocaleString()} - - (Σ {total.toLocaleString()}) - + + {total.toLocaleString()} ({prompt.toLocaleString()} prompt tokens + {completion.toLocaleString()} completion + tokens) ); } diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/constants.ts b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/constants.ts index e1222ce00a..91f5ff8f11 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/constants.ts +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/constants.ts @@ -9,14 +9,10 @@ export const API_BASE_MAX_WIDTH = 200; export const JSON_MAX_HEIGHT = 400; export const METADATA_MAX_HEIGHT = 300; -// Tab keys +// Tab keys (kept for backwards compatibility if needed) export const TAB_REQUEST = "request" as const; export const TAB_RESPONSE = "response" as const; -// View modes -export const VIEW_MODE_FORMATTED = "formatted" as const; -export const VIEW_MODE_JSON = "json" as const; - // Keyboard shortcuts export const KEY_ESCAPE = "Escape"; export const KEY_J_LOWER = "j";