diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json index 5e322598a10..098ba3147ec 100644 --- a/ui/litellm-dashboard/eslint-suppressions.json +++ b/ui/litellm-dashboard/eslint-suppressions.json @@ -3506,29 +3506,15 @@ "count": 1 } }, - "src/components/view_logs/CostBreakdownViewer.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/components/view_logs/EvalViewer/EvalViewer.tsx": { - "local/no-complex-jsx-arrow": { - "count": 1 - }, "no-nested-ternary": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 } }, "src/components/view_logs/GuardrailViewer/CompliancePanel.tsx": { "no-nested-ternary": { "count": 2 }, - "no-restricted-imports": { - "count": 1 - }, "react-hooks/set-state-in-effect": { "count": 1 } @@ -3541,31 +3527,17 @@ "src/components/view_logs/GuardrailViewer/GuardrailViewer.tsx": { "no-nested-ternary": { "count": 4 - }, - "no-restricted-imports": { - "count": 1 - } - }, - "src/components/view_logs/LogDetailsDrawer/DrawerHeader.tsx": { - "no-restricted-imports": { - "count": 1 } }, "src/components/view_logs/LogDetailsDrawer/LogDetailContent.tsx": { "no-nested-ternary": { "count": 3 - }, - "no-restricted-imports": { - "count": 1 } }, "src/components/view_logs/LogDetailsDrawer/LogDetailsDrawer.tsx": { "no-nested-ternary": { "count": 2 }, - "no-restricted-imports": { - "count": 1 - }, "react-hooks/set-state-in-effect": { "count": 2 } @@ -3575,36 +3547,11 @@ "count": 2 } }, - "src/components/view_logs/LogDetailsDrawer/RealtimePrettyView.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/components/view_logs/LogDetailsDrawer/useKeyboardNavigation.ts": { "react-hooks/immutability": { "count": 2 } }, - "src/components/view_logs/ToolsSection/FormattedToolView.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/components/view_logs/ToolsSection/ToolExpandedContent.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/components/view_logs/ToolsSection/ToolItem.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/components/view_logs/VectorStoreViewer.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/components/view_logs/columns.tsx": { "local/filename-pascal-case": { "count": 1 diff --git a/ui/litellm-dashboard/src/components/view_logs/CostBreakdownViewer.tsx b/ui/litellm-dashboard/src/components/view_logs/CostBreakdownViewer.tsx index 96ac965cf8b..cc2696a85b3 100644 --- a/ui/litellm-dashboard/src/components/view_logs/CostBreakdownViewer.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/CostBreakdownViewer.tsx @@ -1,5 +1,6 @@ -import React from "react"; -import { Collapse } from "antd"; +import React, { useState } from "react"; +import { ChevronDown, ChevronRight } from "lucide-react"; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { formatNumberWithCommas } from "@/utils/dataUtils"; export interface CostBreakdown { @@ -49,6 +50,7 @@ export const CostBreakdownViewer: React.FC = ({ cacheReadTokens, cacheCreationTokens, }) => { + const [open, setOpen] = useState(false); const isCached = cacheHit?.toLowerCase() === "true"; const hasTokenCounts = promptTokens !== undefined || completionTokens !== undefined; @@ -90,197 +92,195 @@ export const CostBreakdownViewer: React.FC = ({ return (
- -

Cost Breakdown

-
- Total: - - {formatCost(totalSpend)} - {isCached && " (Cached)"} - -
-
- ), - children: ( -
- {/* Step 1: Base Token Costs */} -
- {(() => { - const hasCacheBreakdown = - costBreakdown?.cache_read_cost !== undefined || costBreakdown?.cache_creation_cost !== undefined; - if (hasCacheBreakdown) { - // Separate line items: Input / Cache Read / Cache Write - const rawCost = isCached - ? 0 - : (inputCost ?? 0) - - (costBreakdown?.cache_read_cost ?? 0) - - (costBreakdown?.cache_creation_cost ?? 0); - return ( - <> -
- Input Cost: - - {formatCost(rawCost)} - {rawInputTokens !== undefined && rawInputTokens !== null && ( - - ({rawInputTokens.toLocaleString()} tokens) - - )} - -
- {(costBreakdown?.cache_read_cost ?? 0) > 0 && ( -
- Prompt Cache Read Cost: - - {formatCost(isCached ? 0 : costBreakdown?.cache_read_cost)} - {(cacheReadTokens ?? 0) > 0 && ( - - ({(cacheReadTokens ?? 0).toLocaleString()} tokens) - - )} - -
- )} - {(costBreakdown?.cache_creation_cost ?? 0) > 0 && ( -
- Prompt Cache Write Cost: - - {formatCost(isCached ? 0 : costBreakdown?.cache_creation_cost)} - {(cacheCreationTokens ?? 0) > 0 && ( - - ({(cacheCreationTokens ?? 0).toLocaleString()} tokens) - - )} - -
- )} - - ); - } - return ( + + + {open ? ( + + ) : ( + + )} +
+

Cost Breakdown

+
+ Total: + + {formatCost(totalSpend)} + {isCached && " (Cached)"} + +
+
+
+ +
+ {/* Step 1: Base Token Costs */} +
+ {(() => { + const hasCacheBreakdown = + costBreakdown?.cache_read_cost !== undefined || costBreakdown?.cache_creation_cost !== undefined; + if (hasCacheBreakdown) { + // Separate line items: Input / Cache Read / Cache Write + const rawCost = isCached + ? 0 + : (inputCost ?? 0) - + (costBreakdown?.cache_read_cost ?? 0) - + (costBreakdown?.cache_creation_cost ?? 0); + return ( + <>
Input Cost: - {formatCost(inputCost)} - {promptTokens !== undefined && ( + {formatCost(rawCost)} + {rawInputTokens !== undefined && rawInputTokens !== null && ( - ({promptTokens.toLocaleString()} prompt tokens) + ({rawInputTokens.toLocaleString()} tokens) )}
- ); - })()} + {(costBreakdown?.cache_read_cost ?? 0) > 0 && ( +
+ Prompt Cache Read Cost: + + {formatCost(isCached ? 0 : costBreakdown?.cache_read_cost)} + {(cacheReadTokens ?? 0) > 0 && ( + + ({(cacheReadTokens ?? 0).toLocaleString()} tokens) + + )} + +
+ )} + {(costBreakdown?.cache_creation_cost ?? 0) > 0 && ( +
+ Prompt Cache Write Cost: + + {formatCost(isCached ? 0 : costBreakdown?.cache_creation_cost)} + {(cacheCreationTokens ?? 0) > 0 && ( + + ({(cacheCreationTokens ?? 0).toLocaleString()} tokens) + + )} + +
+ )} + + ); + } + return (
- Output Cost: + Input Cost: - {formatCost(outputCost)} - {completionTokens !== undefined && ( + {formatCost(inputCost)} + {promptTokens !== undefined && ( - ({completionTokens.toLocaleString()} completion tokens) + ({promptTokens.toLocaleString()} prompt tokens) )}
- {costBreakdown?.tool_usage_cost !== undefined && costBreakdown.tool_usage_cost > 0 && ( -
- Tool Usage Cost: - {formatCost(costBreakdown.tool_usage_cost)} -
- )} - {costBreakdown?.additional_costs && - Object.entries(costBreakdown.additional_costs) - .filter(([, value]) => value != null && value !== 0) - .map(([key, value]) => ( -
- {key}: - {formatCost(value)} -
- ))} -
- - {/* Subtotal / Original Cost - hide when cached since it would be $0 */} - {!isCached && ( -
-
- Original LLM Cost: - {formatCost(originalCost)} -
-
- )} - - {/* Step 2: Adjustments (Discount & Margin) */} - {(hasDiscount || hasMargin) && ( -
- {/* Discounts */} - {hasDiscount && ( -
- {costBreakdown.discount_percent !== undefined && costBreakdown.discount_percent !== 0 && ( -
- - Discount ({formatPercent(costBreakdown.discount_percent)}): - - -{formatCost(costBreakdown.discount_amount)} -
- )} - {costBreakdown.discount_amount !== undefined && - costBreakdown.discount_percent === undefined && ( -
- Discount Amount: - -{formatCost(costBreakdown.discount_amount)} -
- )} -
- )} - - {/* Margins */} - {hasMargin && ( -
- {costBreakdown.margin_percent !== undefined && costBreakdown.margin_percent !== 0 && ( -
- - Margin ({formatPercent(costBreakdown.margin_percent)}): - - - + - {formatCost( - (costBreakdown.margin_total_amount || 0) - (costBreakdown.margin_fixed_amount || 0), - )} - -
- )} - {costBreakdown.margin_fixed_amount !== undefined && costBreakdown.margin_fixed_amount !== 0 && ( -
- Margin: - +{formatCost(costBreakdown.margin_fixed_amount)} -
- )} -
- )} -
- )} - - {/* Final Summary */} -
-
- Final Calculated Cost: - - {formatCost(totalCost)} - {isCached && " (Cached)"} + ); + })()} +
+ Output Cost: + + {formatCost(outputCost)} + {completionTokens !== undefined && ( + + ({completionTokens.toLocaleString()} completion tokens) -
+ )} +
+
+ {costBreakdown?.tool_usage_cost !== undefined && costBreakdown.tool_usage_cost > 0 && ( +
+ Tool Usage Cost: + {formatCost(costBreakdown.tool_usage_cost)} +
+ )} + {costBreakdown?.additional_costs && + Object.entries(costBreakdown.additional_costs) + .filter(([, value]) => value != null && value !== 0) + .map(([key, value]) => ( +
+ {key}: + {formatCost(value)} +
+ ))} +
+ + {/* Subtotal / Original Cost - hide when cached since it would be $0 */} + {!isCached && ( +
+
+ Original LLM Cost: + {formatCost(originalCost)}
- ), - }, - ]} - /> + )} + + {/* Step 2: Adjustments (Discount & Margin) */} + {(hasDiscount || hasMargin) && ( +
+ {/* Discounts */} + {hasDiscount && ( +
+ {costBreakdown.discount_percent !== undefined && costBreakdown.discount_percent !== 0 && ( +
+ + Discount ({formatPercent(costBreakdown.discount_percent)}): + + -{formatCost(costBreakdown.discount_amount)} +
+ )} + {costBreakdown.discount_amount !== undefined && costBreakdown.discount_percent === undefined && ( +
+ Discount Amount: + -{formatCost(costBreakdown.discount_amount)} +
+ )} +
+ )} + + {/* Margins */} + {hasMargin && ( +
+ {costBreakdown.margin_percent !== undefined && costBreakdown.margin_percent !== 0 && ( +
+ + Margin ({formatPercent(costBreakdown.margin_percent)}): + + + + + {formatCost( + (costBreakdown.margin_total_amount || 0) - (costBreakdown.margin_fixed_amount || 0), + )} + +
+ )} + {costBreakdown.margin_fixed_amount !== undefined && costBreakdown.margin_fixed_amount !== 0 && ( +
+ Margin: + +{formatCost(costBreakdown.margin_fixed_amount)} +
+ )} +
+ )} +
+ )} + + {/* Final Summary */} +
+
+ Final Calculated Cost: + + {formatCost(totalCost)} + {isCached && " (Cached)"} + +
+
+
+
+
); }; diff --git a/ui/litellm-dashboard/src/components/view_logs/EvalViewer/EvalViewer.tsx b/ui/litellm-dashboard/src/components/view_logs/EvalViewer/EvalViewer.tsx index 60b27eb0e3a..217efc7ac27 100644 --- a/ui/litellm-dashboard/src/components/view_logs/EvalViewer/EvalViewer.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/EvalViewer/EvalViewer.tsx @@ -1,8 +1,9 @@ import React from "react"; -import { Card, Tag, Table, Typography, Space, Tooltip } from "antd"; -import { CheckCircleOutlined, CloseCircleOutlined, ExperimentOutlined } from "@ant-design/icons"; - -const { Text } = Typography; +import { CircleCheck, CircleX, FlaskConical } from "lucide-react"; +import { Badge } from "@/components/ui/badge"; +import { Card, CardAction, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; interface EvalVerdict { criterion_name: string; @@ -36,10 +37,10 @@ export default function EvalViewer({ data }: EvalViewerProps) { return (
- - + + LLM Judge Results - +
{entries.map((entry, idx) => ( @@ -56,151 +57,159 @@ function EvalEntryCard({ entry }: { entry: EvalInformation }) { // Filter out synthetic "Overall" row the judge sometimes appends — it's already in the header const verdicts = (entry.verdicts || []).filter((v) => (v.criterion_name || "").toLowerCase() !== "overall"); - const columns = [ - { - title: "Criterion", - dataIndex: "criterion_name", - key: "criterion_name", - width: 160, - render: (v: string) => ( - - {v} - - ), - }, - { - title: "Weight", - dataIndex: "weight", - key: "weight", - width: 65, - render: (v: number) => - v != null ? ( - - {v}% - - ) : null, - }, - { - title: "Score", - dataIndex: "score", - key: "score", - width: 65, - render: (v: number) => ( - = 70 ? "#52c41a" : v >= 50 ? "#faad14" : "#ff4d4f", fontWeight: 600 }}>{v} - ), - }, - { - title: ( - - Weighted - - ), - key: "weighted", - width: 75, - render: (_: unknown, row: EvalVerdict) => { - if (row.weight == null) return null; - const contrib = (row.score * row.weight) / 100; - return ( - - {contrib % 1 === 0 ? contrib : contrib.toFixed(1)} - - ); - }, - }, - { - title: "Comment", - dataIndex: "reasoning", - key: "reasoning", - ellipsis: { showTitle: false }, - render: (v: string) => ( - - {v} - - ), - }, - ]; + const hasWeights = verdicts.some((v) => v.weight != null); + const weightedTotal = verdicts.reduce((sum, v) => sum + (v.weight != null ? (v.score * v.weight) / 100 : 0), 0); return ( - - {passed ? ( - - ) : ( - - )} - {entry.eval_name} - {passed ? "PASSED" : "FAILED"} - - - {entry.overall_score?.toFixed(0)} / 100 - {entry.threshold != null && ` (threshold: ${entry.threshold})`} - - - - } - extra={ - - {entry.judge_model && ( - - Judge: {entry.judge_model} - - )} - {entry.iteration != null && ( - - Iter: {entry.iteration + 1} - - )} - - } - > - {entry.eval_error && ( - - Judge error: {entry.eval_error} - - )} + + + +
+ {passed ? ( + + ) : ( + + )} + {entry.eval_name} + {passed ? "PASSED" : "FAILED"} + + + + } + > + {entry.overall_score?.toFixed(0)} / 100 + {entry.threshold != null && ` (threshold: ${entry.threshold})`} + + + Weighted average of all criterion scores. Each criterion has a weight (%) set when the eval was + created — higher-weight criteria count more toward the final score. + + + +
+
+ +
+ {entry.judge_model && ( + + Judge: {entry.judge_model} + + )} + {entry.iteration != null && ( + + Iter: {entry.iteration + 1} + + )} +
+
+
- {verdicts.length > 0 ? ( - { - const hasWeights = verdicts.some((v) => v.weight != null); - if (!hasWeights) return null; - const total = verdicts.reduce((sum, v) => sum + (v.weight != null ? (v.score * v.weight) / 100 : 0), 0); - return ( - - - - Total - - - - - - - {total % 1 === 0 ? total : total.toFixed(1)} - - - - - ); - }} - /> - ) : ( - - Score: {entry.overall_score?.toFixed(1)} — no per-criterion breakdown available. - - )} + + {entry.eval_error && ( + + Judge error: {entry.eval_error} + + )} + + {verdicts.length > 0 ? ( +
+ + + Criterion + Weight + Score + + + + }> + Weighted + + + Score × Weight — how much each criterion contributes to the final score + + + + + Comment + + + + {verdicts.map((row) => { + const contrib = row.weight != null ? (row.score * row.weight) / 100 : null; + return ( + + + + {row.criterion_name} + + + + {row.weight != null ? ( + + {row.weight}% + + ) : null} + + + = 70 ? "#52c41a" : row.score >= 50 ? "#faad14" : "#ff4d4f", + fontWeight: 600, + }} + > + {row.score} + + + + {contrib != null ? ( + + {contrib % 1 === 0 ? contrib : contrib.toFixed(1)} + + ) : null} + + + + + }>{row.reasoning} + {row.reasoning} + + + + + ); + })} + + {hasWeights && ( + + + + + Total + + + + + + + {weightedTotal % 1 === 0 ? weightedTotal : weightedTotal.toFixed(1)} + + + + + + )} +
+ ) : ( + + Score: {entry.overall_score?.toFixed(1)} — no per-criterion breakdown available. + + )} +
); } diff --git a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/CompliancePanel.tsx b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/CompliancePanel.tsx index ebe89f12a7b..1d74927a264 100644 --- a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/CompliancePanel.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/CompliancePanel.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from "react"; -import { Tooltip } from "antd"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { checkEuAiActCompliance, checkGdprCompliance, @@ -66,9 +66,12 @@ const ComplianceCard = ({ {loading ? ( ) : error ? ( - - -- - + + + }>-- + {error} + + ) : data?.compliant ? ( ) : ( diff --git a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.tsx b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.tsx index 4625f500c6d..32db72a0a25 100644 --- a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.tsx @@ -1,5 +1,5 @@ import React, { useState, useMemo } from "react"; -import { Tooltip } from "antd"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import PresidioDetectedEntities from "./PresidioDetectedEntities"; import BedrockGuardrailDetails, { BedrockGuardrailResponse, @@ -517,13 +517,20 @@ const EvaluationCard = ({ entry }: { entry: GuardrailInformation }) => { )} {riskScore != null && success && ( - - - Risk {riskScore}/10 - - + + + + } + > + Risk {riskScore}/10 + + {`Risk score: ${riskScore}/10`} + + )}
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 ee619291f66..e80b447f402 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/DrawerHeader.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/DrawerHeader.tsx @@ -1,6 +1,9 @@ -import { Button, Space, Tag, Tooltip, Typography } from "antd"; -import { CloseOutlined, UpOutlined, DownOutlined } from "@ant-design/icons"; +import { useState } from "react"; +import { Check, ChevronDown, ChevronUp, Copy, X } from "lucide-react"; import moment from "moment"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { LogEntry } from "../columns"; import { AutoRouterTag } from "@/components/shared/table_cells"; import { ClassifyTag } from "./ClassifyTag"; @@ -10,15 +13,11 @@ import { 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; @@ -96,7 +95,7 @@ function ModelProviderSection({ providerName?: string; }) { return ( - +
{providerLogo && ( )} - - +
+ {model} - + {providerName && ( - + {providerName} - + )} - - +
+
); } @@ -128,24 +127,50 @@ function ModelProviderSection({ * Request ID display with copy functionality */ function RequestIdSection({ requestId }: { requestId: string }) { + const [copied, setCopied] = useState(false); + + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(requestId); + setCopied(true); + setTimeout(() => setCopied(false), 1200); + } catch { + /* clipboard unavailable in non-secure contexts */ + } + }; + return (
- - - {requestId} - - + + + + } + > + {requestId} + + + {requestId} + +
); } @@ -172,21 +197,29 @@ function NavigationSection({ marginLeft: 4, background: "#fafafa", }; + const splitStyle = { width: 1, height: 20, background: COLOR_BORDER }; return ( - }> - - - - + ); +} + function ErrorDescription({ errorInfo }: { errorInfo: any }) { return (
{errorInfo.error_code && (
- Error Code: {errorInfo.error_code} + Error Code: {errorInfo.error_code}
)} {errorInfo.error_message && (
- Message: {errorInfo.error_message} + Message: {errorInfo.error_message}
)}
@@ -241,16 +299,16 @@ function ErrorDescription({ errorInfo }: { errorInfo: any }) { function TagsSection({ tags }: { tags: Record }) { return (
- + Tags - - + +
{Object.entries(tags).map(([key, value]) => ( - + {key}: {String(value)} - + ))} - +
); } @@ -262,12 +320,12 @@ function GuardrailLabel({ label, maskedCount }: { label: string; maskedCount: nu }; return ( - + {label} - {maskedCount > 0 && {maskedCount} masked} - + {maskedCount > 0 && {maskedCount} masked} + ); } @@ -291,26 +349,24 @@ const PROMPT_CACHE_DOCS_URL = "https://docs.litellm.ai/docs/completion/prompt_ca function MetricLabel({ label, tooltip, docsUrl }: { label: string; tooltip: string; docsUrl: string }) { return ( - + {label} - + + + } + > + + + {tooltip}{" "} - + Docs - - } - > - - - + +
+ + ); } @@ -333,102 +389,111 @@ function MetricsSection({ logEntry, metadata }: { logEntry: LogEntry; metadata: return (
- - - {showAnthropicMessagesInputOutput ? ( - <> - {formatNumberWithCommas(uncachedInputTokens)} - - {formatNumberWithCommas(logEntry.completion_tokens)} - - - ) : ( - - - - )} - ${formatNumberWithCommas(logEntry.spend || 0, 8)} - - {logEntry.request_duration_ms != null ? (logEntry.request_duration_ms / 1000).toFixed(3) : "-"} s - - {ttftMs != null && ttftMs > 0 && ( - {(ttftMs / 1000).toFixed(3)} s - )} - - {showResponseCache && ( - - } - > - {isResponseCacheHit ? "Hit" : "Miss"} - - )} - {promptCacheReadTokens > 0 && ( - - } - > - {formatNumberWithCommas(promptCacheReadTokens)} - - )} - {promptCacheCreationTokens > 0 && ( - - } - > - {formatNumberWithCommas(promptCacheCreationTokens)} - - )} - - {metadata?.litellm_overhead_time_ms !== undefined && metadata.litellm_overhead_time_ms !== null && ( - - {metadata.litellm_overhead_time_ms.toFixed(2)} ms - - )} - - - {metadata?.attempted_retries !== undefined && metadata?.attempted_retries !== null ? ( - metadata.attempted_retries > 0 ? ( - <> - {metadata.attempted_retries} - {metadata.max_retries !== undefined && metadata.max_retries !== null - ? ` / ${metadata.max_retries}` - : ""} - - ) : ( - None - ) + + + Metrics + + + + {showAnthropicMessagesInputOutput ? ( + <> + {formatNumberWithCommas(uncachedInputTokens)} + + {formatNumberWithCommas(logEntry.completion_tokens)} + + ) : ( - "-" + + + + )} + ${formatNumberWithCommas(logEntry.spend || 0, 8)} + + {logEntry.request_duration_ms != null ? (logEntry.request_duration_ms / 1000).toFixed(3) : "-"} s + + {ttftMs != null && ttftMs > 0 && ( + {(ttftMs / 1000).toFixed(3)} s )} - - - {moment(logEntry.startTime).format("YYYY-MM-DDTHH:mm:ss.SSS[Z]")} - - - {moment(logEntry.endTime).format("YYYY-MM-DDTHH:mm:ss.SSS[Z]")} - - + {showResponseCache && ( + + } + > + + {isResponseCacheHit ? "Hit" : "Miss"} + + + )} + {promptCacheReadTokens > 0 && ( + + } + > + {formatNumberWithCommas(promptCacheReadTokens)} + + )} + {promptCacheCreationTokens > 0 && ( + + } + > + {formatNumberWithCommas(promptCacheCreationTokens)} + + )} + + {metadata?.litellm_overhead_time_ms !== undefined && metadata.litellm_overhead_time_ms !== null && ( + + {metadata.litellm_overhead_time_ms.toFixed(2)} ms + + )} + + + {metadata?.attempted_retries !== undefined && metadata?.attempted_retries !== null ? ( + metadata.attempted_retries > 0 ? ( + <> + {metadata.attempted_retries} + {metadata.max_retries !== undefined && metadata.max_retries !== null + ? ` / ${metadata.max_retries}` + : ""} + + ) : ( + + None + + ) + ) : ( + "-" + )} + + + + {moment(logEntry.startTime).format("YYYY-MM-DDTHH:mm:ss.SSS[Z]")} + + + {moment(logEntry.endTime).format("YYYY-MM-DDTHH:mm:ss.SSS[Z]")} + + +
); @@ -449,6 +514,7 @@ function RequestResponseSection({ getFormattedResponse, logEntry, }: RequestResponseSectionProps) { + const [open, setOpen] = useState(true); const [activeTab, setActiveTab] = useState(TAB_REQUEST); const [viewMode, setViewMode] = useState<"pretty" | "json">("pretty"); @@ -476,90 +542,76 @@ function RequestResponseSection({ return (
- { - const target = e.target as HTMLElement; - if (target.closest(".ant-radio-group")) { - e.stopPropagation(); - } - }} - > -

- Request & Response -

- setViewMode(e.target.value)}> - Pretty - JSON - -
- ), - children: ( -
- {viewMode === "pretty" ? ( - - ) : ( - setActiveTab(key as typeof TAB_REQUEST | typeof TAB_RESPONSE)} - tabBarExtraContent={ - - } - items={[ - { - key: TAB_REQUEST, - label: "Request", - children: ( -
- -
- ), - }, - { - key: TAB_RESPONSE, - label: "Response", - children: ( -
- {hasResponse || hasError ? ( - - ) : ( -
- Response data not available -
- )} -
- ), - }, - ]} - /> - )} -
- ), - }, - ]} - /> + + setViewMode(value as "pretty" | "json")}> +
+ + {open ? ( + + ) : ( + + )} +

+ Request & Response +

+
+ + Pretty + JSON + +
+ +
+ + + + + setActiveTab(key as typeof TAB_REQUEST | typeof TAB_RESPONSE)} + > +
+ + Request + Response + + +
+ +
+ +
+
+ +
+ {hasResponse || hasError ? ( + + ) : ( +
+ Response data not available +
+ )} +
+
+
+
+
+
+
+
); } @@ -602,43 +654,40 @@ export function GuardrailJumpLink({ guardrailEntries }: { guardrailEntries: any[ } function MetadataSection({ metadata }: { metadata: Record }) { + const [open, setOpen] = useState(true); + return (
- Metadata, - children: ( -
-
- -
-
-                  {JSON.stringify(metadata, null, 2)}
-                
-
- ), - }, - ]} - /> + + + {open ? ( + + ) : ( + + )} +

Metadata

+
+ +
+
+ JSON.stringify(metadata, null, 2)} label="Copy Metadata" /> +
+
+              {JSON.stringify(metadata, null, 2)}
+            
+
+
+
); } diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailsDrawer.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailsDrawer.tsx index 5b27cfc1d0f..40e6e6f2051 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailsDrawer.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailsDrawer.tsx @@ -1,7 +1,8 @@ import { useEffect, useMemo, useState } from "react"; -import { Button, Drawer, Segmented } from "antd"; -import { CheckOutlined, CopyOutlined, LeftOutlined, RightOutlined } from "@ant-design/icons"; -import { Bot, Sparkles, Wrench } from "lucide-react"; +import { Bot, Check, ChevronLeft, ChevronRight, Copy, Sparkles, Wrench } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Sheet, SheetContent } from "@/components/ui/sheet"; +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { LogEntry } from "../columns"; import { AutoRouterIcon, useIsAutoRoutedModelGroup } from "@/components/shared/table_cells"; import { AGENT_CALL_TYPES, MCP_CALL_TYPES } from "../constants"; @@ -297,181 +298,183 @@ export function LogDetailsDrawer({ if (!currentLog || !enrichedLog) return null; return ( - { + if (!nextOpen) onClose(); }} > -
- {!isSidebarCollapsed ? ( - + +
+ {!isSidebarCollapsed ? ( + + ) : ( + + )} + {!isSidebarCollapsed && ( +
+
+
+
+
+ {isSessionMode ? "Session" : "Trace"} +
+
+ {leftPanelDisplayId} + +
-
-
- {logsForList.length} req - {[ - isSessionMode - ? llmCount - : logsForList.filter( - (row) => !MCP_CALL_TYPES.includes(row.call_type) && !AGENT_CALL_TYPES.includes(row.call_type), - ).length, - isSessionMode - ? agentCount - : logsForList.filter((row) => AGENT_CALL_TYPES.includes(row.call_type)).length, - isSessionMode ? mcpCount : logsForList.filter((row) => MCP_CALL_TYPES.includes(row.call_type)).length, - ].map((count, i) => { - const label = [" LLM", " Agent", " MCP"][i]; - return count > 0 ? ( - +
+ {logsForList.length} req + {[ + isSessionMode + ? llmCount + : logsForList.filter( + (row) => !MCP_CALL_TYPES.includes(row.call_type) && !AGENT_CALL_TYPES.includes(row.call_type), + ).length, + isSessionMode + ? agentCount + : logsForList.filter((row) => AGENT_CALL_TYPES.includes(row.call_type)).length, + isSessionMode + ? mcpCount + : logsForList.filter((row) => MCP_CALL_TYPES.includes(row.call_type)).length, + ].map((count, i) => { + const label = [" LLM", " Agent", " MCP"][i]; + return count > 0 ? ( + + · + {count} + {label} + + ) : null; + })} + · + {isSessionMode ? getSpendString(totalSessionCost) : getSpendString(currentLog.spend || 0)} + {isSessionMode && ( + <> · - {count} - {label} - - ) : null; - })} - · - {isSessionMode ? getSpendString(totalSessionCost) : getSpendString(currentLog.spend || 0)} + {sessionDurationSeconds}s + + )} +
+ {isSessionMode && sessionTruncated && ( +
+ Showing most recent {logsForList.length} of {sessionTotalCount} +
+ )} {isSessionMode && ( - <> - · - {sessionDurationSeconds}s - + setSessionSortMode(value as SessionLogSortMode)} + > + + + Duration + + + Start time + + + )}
- {isSessionMode && sessionTruncated && ( -
- Showing most recent {logsForList.length} of {sessionTotalCount} -
- )} - {isSessionMode && ( - setSessionSortMode(value as SessionLogSortMode)} - /> - )} -
-
- {normalizeGuardrailEntries(metadata?.guardrail_information).length > 0 && ( -
- -
- )} - {isSessionMode ? ( -
- {/* Child events — vertical tree line with horizontal connectors */} -
-
- {logsForList.map((row, idx) => { - const isLast = idx === logsForList.length - 1; - return ( -
-
- {isLast &&
} - { - setSelectedSessionRequestId(row.request_id); - onSelectLog?.(row); - }} - /> -
- ); - })} +
+ {normalizeGuardrailEntries(metadata?.guardrail_information).length > 0 && ( +
+
-
- ) : ( -
- {logsForList.map((row) => ( - onSelectLog?.(row)} - /> - ))} -
- )} + )} + {isSessionMode ? ( +
+ {/* Child events — vertical tree line with horizontal connectors */} +
+
+ {logsForList.map((row, idx) => { + const isLast = idx === logsForList.length - 1; + return ( +
+
+ {isLast &&
} + { + setSelectedSessionRequestId(row.request_id); + onSelectLog?.(row); + }} + /> +
+ ); + })} +
+
+ ) : ( +
+ {logsForList.map((row) => ( + onSelectLog?.(row)} + /> + ))} +
+ )} +
-
- )} + )} -
- -
- + +
+ +
-
- + + ); } diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/RealtimePrettyView.test.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/RealtimePrettyView.test.tsx index 4f8f662aa16..201c9e74ce8 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/RealtimePrettyView.test.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/RealtimePrettyView.test.tsx @@ -4,16 +4,6 @@ import userEvent from "@testing-library/user-event"; import { describe, it, expect, vi, beforeEach } from "vitest"; import { RealtimePrettyView, isRealtimeResponse } from "./RealtimePrettyView"; -vi.mock("antd", async () => { - const actual = await vi.importActual("antd"); - return { - ...actual, - message: { - success: vi.fn(), - }, - }; -}); - const sampleRealtimeResponse = { usage: { total_tokens: 587, diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/RealtimePrettyView.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/RealtimePrettyView.tsx index 5552c6decbe..5441bcbc4cf 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/RealtimePrettyView.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/RealtimePrettyView.tsx @@ -5,19 +5,11 @@ */ import { useState } from "react"; -import { Typography, Tag, Tooltip } from "antd"; -import { - SoundOutlined, - MessageOutlined, - SettingOutlined, - AudioOutlined, - DownOutlined, - UpOutlined, -} from "@ant-design/icons"; +import { ChevronDown, ChevronUp, MessageSquare, Mic, Settings, Volume2 } from "lucide-react"; +import { Badge } from "@/components/ui/badge"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { SectionHeader } from "./SectionHeader"; -const { Text } = Typography; - interface RealtimeEvent { type: string; event_id?: string; @@ -163,34 +155,34 @@ function SessionCard({ session, turnCount }: { session: RealtimeSession; turnCou
{isCollapsed ? ( - + ) : ( - + )}
- - Session + + Session
- + {session.model} - + {turnCount > 0 && ( - + {turnCount} {turnCount === 1 ? "turn" : "turns"} - + )} {session.voice && ( - - {session.voice} - + + {session.voice} + )} {session.modalities && (
{session.modalities.map((m) => ( - - {m === "audio" ? : } {m} - + + {m === "audio" ? : } {m} + ))}
)} @@ -228,8 +220,8 @@ function SessionCard({ session, turnCount }: { session: RealtimeSession; turnCou {session.instructions && (
- Instructions - +
- + {response.status || "unknown"} - + {usage && ( - + {usage.input_tokens ?? 0} in / {usage.output_tokens ?? 0} out tokens - + )} {response.conversation_id && ( - - - conv: {response.conversation_id.slice(0, 12)}... - - + + + } + > + conv: {response.conversation_id.slice(0, 12)}... + + {response.conversation_id} + + )}
@@ -381,8 +378,8 @@ function OutputMessage({ output }: { output: RealtimeOutputItem }) { return (
- {output.role?.toUpperCase() || "ASSISTANT"} - + {contents.map((c, cIdx) => { const text = c.transcript || c.text; if (!text) return null; @@ -407,20 +404,18 @@ function OutputMessage({ output }: { output: RealtimeOutputItem }) { }} > {c.type === "audio" && ( - )} {c.type === "text" && ( - - + {label} Token Breakdown - +
{ if (typeof value === "number") { return ( - + {formatTokenLabel(key)}: {value.toLocaleString()} - + ); } return null; @@ -483,9 +481,9 @@ function ConfigRow({ label, value }: { label: string; value: any }) { if (value === undefined || value === null) return null; return (
- + {label} - +
{String(value)}
); diff --git a/ui/litellm-dashboard/src/components/view_logs/ToolsSection/FormattedToolView.tsx b/ui/litellm-dashboard/src/components/view_logs/ToolsSection/FormattedToolView.tsx index c58bde72995..b548aa2f368 100644 --- a/ui/litellm-dashboard/src/components/view_logs/ToolsSection/FormattedToolView.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/ToolsSection/FormattedToolView.tsx @@ -2,11 +2,9 @@ * Formatted view of tool definition with parameters table and call data */ -import { Typography, Table } from "antd"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { ParsedTool, ParameterRow } from "./types"; -const { Text } = Typography; - interface FormattedToolViewProps { tool: ParsedTool; } @@ -23,57 +21,27 @@ export function FormattedToolView({ tool }: FormattedToolViewProps) { }), ); - const columns = [ - { - title: "Parameter", - dataIndex: "name", - key: "name", - render: (name: string, record: ParameterRow) => ( - - {name} - {record.required && *} - - ), - }, - { - title: "Type", - dataIndex: "type", - key: "type", - render: (type: string) => ( - - {type} - - ), - }, - { - title: "Description", - dataIndex: "description", - key: "description", - render: (desc: string) => {desc}, - }, - ]; - return (
{/* Description */} {tool.description && (
- {tool.description} - +
)} {/* Parameters Table */} {parameterRows.length > 0 && (
- Parameters - - + +
+ + + Parameter + Type + Description + + + + {parameterRows.map((row) => ( + + + + {row.name} + {row.required && *} + + + + {row.type} + + + {row.description} + + + ))} + +
)} {/* If tool was called, show the arguments used */} {tool.called && tool.callData && (
- Called With - +
- - Description - - setViewMode(e.target.value)}> - Formatted - JSON - + Description + setViewMode(value as ViewMode)}> + + Formatted + JSON + +
{viewMode === "formatted" ? : } diff --git a/ui/litellm-dashboard/src/components/view_logs/ToolsSection/ToolItem.tsx b/ui/litellm-dashboard/src/components/view_logs/ToolsSection/ToolItem.tsx index 78525e1a661..112364f5ff3 100644 --- a/ui/litellm-dashboard/src/components/view_logs/ToolsSection/ToolItem.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/ToolsSection/ToolItem.tsx @@ -3,13 +3,11 @@ */ import { useState } from "react"; -import { Typography, Tag } from "antd"; -import { ToolOutlined, RightOutlined, DownOutlined } from "@ant-design/icons"; +import { ChevronDown, ChevronRight, Wrench } from "lucide-react"; +import { Badge } from "@/components/ui/badge"; import { ParsedTool } from "./types"; import { ToolExpandedContent } from "./ToolExpandedContent"; -const { Text } = Typography; - interface ToolItemProps { tool: ParsedTool; } @@ -39,18 +37,18 @@ export function ToolItem({ tool }: ToolItemProps) { }} >
- - + + {tool.index}. {tool.name} - +
- {tool.called ? "called" : "not called"} + {tool.called ? "called" : "not called"} {expanded ? ( - + ) : ( - + )}
diff --git a/ui/litellm-dashboard/src/components/view_logs/VectorStoreViewer.tsx b/ui/litellm-dashboard/src/components/view_logs/VectorStoreViewer.tsx index bdafc817020..fd46ba34d67 100644 --- a/ui/litellm-dashboard/src/components/view_logs/VectorStoreViewer.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/VectorStoreViewer.tsx @@ -1,5 +1,6 @@ import React, { useState } from "react"; -import { Collapse } from "antd"; +import { ChevronDown, ChevronRight } from "lucide-react"; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { getProviderLogoAndName } from "../provider_info_helpers"; interface VectorStoreContent { @@ -31,6 +32,7 @@ interface VectorStoreViewerProps { } export function VectorStoreViewer({ data }: VectorStoreViewerProps) { + const [open, setOpen] = useState(true); const [expandedResults, setExpandedResults] = useState>({}); if (!data || data.length === 0) { @@ -57,110 +59,110 @@ export function VectorStoreViewer({ data }: VectorStoreViewerProps) { return (
- Vector Store Requests, - children: ( -
- {data.map((request, index) => ( -
-
-
-
-
- Query: - {request.query} -
-
- Vector Store ID: - {request.vector_store_id} -
-
- Provider: - - {(() => { - const { logo, displayName } = getProviderLogoAndName(request.custom_llm_provider); - return ( - <> - {logo && {`${displayName}} - {displayName} - - ); - })()} + + + {open ? ( + + ) : ( + + )} +

Vector Store Requests

+
+ +
+ {data.map((request, index) => ( +
+
+
+
+
+ Query: + {request.query} +
+
+ Vector Store ID: + {request.vector_store_id} +
+
+ Provider: + + {(() => { + const { logo, displayName } = getProviderLogoAndName(request.custom_llm_provider); + return ( + <> + {logo && {`${displayName}} + {displayName} + + ); + })()} + +
+
+
+
+ Start Time: + {formatTime(request.start_time)} +
+
+ End Time: + {formatTime(request.end_time)} +
+
+ Duration: + {calculateDuration(request.start_time, request.end_time)} +
+
+
+
+ +

Search Results

+
+ {request.vector_store_search_response.data.map((result, resultIndex) => { + const isExpanded = expandedResults[`${index}-${resultIndex}`] || false; + + return ( +
+
toggleResult(index, resultIndex)} + > + + + +
+ Result {resultIndex + 1} + + Score: {result.score.toFixed(4)}
-
-
- Start Time: - {formatTime(request.start_time)} + + {isExpanded && ( +
+ {result.content.map((content, contentIndex) => ( +
+
{content.type}
+
+                                  {content.text}
+                                
+
+ ))}
-
- End Time: - {formatTime(request.end_time)} -
-
- Duration: - {calculateDuration(request.start_time, request.end_time)} -
-
+ )}
-
- -

Search Results

-
- {request.vector_store_search_response.data.map((result, resultIndex) => { - const isExpanded = expandedResults[`${index}-${resultIndex}`] || false; - - return ( -
-
toggleResult(index, resultIndex)} - > - - - -
- Result {resultIndex + 1} - - Score: {result.score.toFixed(4)} - -
-
- - {isExpanded && ( -
- {result.content.map((content, contentIndex) => ( -
-
{content.type}
-
-                                      {content.text}
-                                    
-
- ))} -
- )} -
- ); - })} -
-
- ))} + ); + })} +
- ), - }, - ]} - /> + ))} +
+ +
); }