diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailContent.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailContent.tsx index 1dca2715e36..2045ecf3bc0 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailContent.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailContent.tsx @@ -1,4 +1,5 @@ import { useState } from "react"; +import { useTranslation } from "react-i18next"; import { Typography, Descriptions, Card, Tag, Tabs, Alert, Collapse, Radio, Space, Spin } from "antd"; import moment from "moment"; import { LogEntry } from "../columns"; @@ -52,6 +53,7 @@ export interface LogDetailContentProps { * be reused for both single-log and session-mode views. */ export function LogDetailContent({ logEntry, isLoadingDetails = false, accessToken }: LogDetailContentProps) { + const { t } = useTranslation(); const metadata = logEntry.metadata || {}; const hasError = metadata.status === "failure"; const errorInfo = hasError ? metadata.error_information : null; @@ -83,9 +85,9 @@ export function LogDetailContent({ logEntry, isLoadingDetails = false, accessTok if (hasError && errorInfo) { return { error: { - message: errorInfo.error_message || "An error occurred", - type: errorInfo.error_class || "error", - code: errorInfo.error_code || "unknown", + message: errorInfo.error_message || t("common.somethingWentWrong"), + type: errorInfo.error_class || t("common.error"), + code: errorInfo.error_code || t("common.unknown"), param: null, }, }; @@ -100,7 +102,7 @@ export function LogDetailContent({ logEntry, isLoadingDetails = false, accessTok } className="mb-6" /> @@ -113,22 +115,33 @@ export function LogDetailContent({ logEntry, isLoadingDetails = false, accessTok {/* Request Details */}
- + - {logEntry.model} - {logEntry.custom_llm_provider || "-"} - {logEntry.call_type} - + {logEntry.model} + + {logEntry.custom_llm_provider || "-"} + + + {logEntry.call_type} + + - + {logEntry.requester_ip_address && ( - {logEntry.requester_ip_address} + + {logEntry.requester_ip_address} + )} {hasGuardrailData && ( - + )} @@ -165,7 +178,7 @@ export function LogDetailContent({ logEntry, isLoadingDetails = false, accessTok {isLoadingDetails ? (
-
Loading request & response data...
+
{t("viewLogs.logDetailContent.loadingRequestResponse")}
) : ( {errorInfo.error_code && (
- Error Code: {errorInfo.error_code} + {t("viewLogs.logDetailContent.errorCode")} {errorInfo.error_code}
)} {errorInfo.error_message && (
- Message: {errorInfo.error_message} + {t("viewLogs.logDetailContent.errorMessage")} {errorInfo.error_message}
)}
@@ -233,10 +247,11 @@ function ErrorDescription({ errorInfo }: { errorInfo: any }) { } function TagsSection({ tags }: { tags: Record }) { + const { t } = useTranslation(); return (
- Tags + {t("viewLogs.logDetailContent.tags")} {Object.entries(tags).map(([key, value]) => ( @@ -250,6 +265,7 @@ function TagsSection({ tags }: { tags: Record }) { } function GuardrailLabel({ label, maskedCount }: { label: string; maskedCount: number }) { + const { t } = useTranslation(); const handleClick = () => { const el = document.getElementById("guardrail-section"); if (el) el.scrollIntoView({ behavior: "smooth" }); @@ -260,7 +276,7 @@ function GuardrailLabel({ label, maskedCount }: { label: string; maskedCount: nu {label} - {maskedCount > 0 && {maskedCount} masked} + {maskedCount > 0 && {t("viewLogs.logDetailContent.masked", { count: maskedCount })}} ); } @@ -279,6 +295,7 @@ function getUncachedInputTextTokens(metadata: Record): number | und } function MetricsSection({ logEntry, metadata }: { logEntry: LogEntry; metadata: Record }) { + const { t } = useTranslation(); const completionStartTime = logEntry.completionStartTime; const ttftMs = completionStartTime && completionStartTime !== logEntry.endTime @@ -300,17 +317,19 @@ function MetricsSection({ logEntry, metadata }: { logEntry: LogEntry; metadata: return (
- + {showAnthropicMessagesInputOutput ? ( <> - {formatNumberWithCommas(uncachedInputTokens)} - + + {formatNumberWithCommas(uncachedInputTokens)} + + {formatNumberWithCommas(logEntry.completion_tokens)} ) : ( - + )} - ${formatNumberWithCommas(logEntry.spend || 0, 8)} - + + ${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 + + {(ttftMs / 1000).toFixed(3)} s + )} {hasCacheActivity && ( <> - + {cacheHitValue} {metadata?.additional_usage_values?.cache_read_input_tokens > 0 && ( - + {formatNumberWithCommas(metadata.additional_usage_values.cache_read_input_tokens)} )} {metadata?.additional_usage_values?.cache_creation_input_tokens > 0 && ( - + {formatNumberWithCommas(metadata.additional_usage_values.cache_creation_input_tokens)} )} @@ -345,12 +368,12 @@ function MetricsSection({ logEntry, metadata }: { logEntry: LogEntry; metadata: )} {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 ? ( <> @@ -360,17 +383,17 @@ function MetricsSection({ logEntry, metadata }: { logEntry: LogEntry; metadata: : ""} ) : ( - None + {t("viewLogs.logDetailContent.retriesNone")} ) ) : ( "-" )} - + {moment(logEntry.startTime).format("YYYY-MM-DDTHH:mm:ss.SSS[Z]")} - + {moment(logEntry.endTime).format("YYYY-MM-DDTHH:mm:ss.SSS[Z]")} @@ -394,6 +417,7 @@ function RequestResponseSection({ getFormattedResponse, logEntry, }: RequestResponseSectionProps) { + const { t } = useTranslation(); const [activeTab, setActiveTab] = useState(TAB_REQUEST); const [viewMode, setViewMode] = useState<"pretty" | "json">("pretty"); @@ -438,11 +462,11 @@ function RequestResponseSection({ }} >

- Request & Response + {t("viewLogs.logDetailContent.requestAndResponse")}

setViewMode(e.target.value)}> - Pretty - JSON + {t("viewLogs.logDetailContent.viewPretty")} + {t("viewLogs.logDetailContent.viewJson")}
), @@ -467,7 +491,10 @@ function RequestResponseSection({ @@ -475,7 +502,7 @@ function RequestResponseSection({ items={[ { key: TAB_REQUEST, - label: "Request", + label: t("viewLogs.logDetailContent.tabRequest"), children: (
@@ -484,14 +511,14 @@ function RequestResponseSection({ }, { key: TAB_RESPONSE, - label: "Response", + label: t("viewLogs.logDetailContent.tabResponse"), children: (
{hasResponse || hasError ? ( ) : (
- Response data not available + {t("viewLogs.logDetailContent.responseNotAvailable")}
)}
@@ -510,6 +537,7 @@ function RequestResponseSection({ } export function GuardrailJumpLink({ guardrailEntries }: { guardrailEntries: any[] }) { + const { t } = useTranslation(); const allPassed = guardrailEntries.every((e) => { const status = e?.guardrail_status || e?.status; return status === "pass" || status === "passed" || status === "success"; @@ -538,8 +566,8 @@ export function GuardrailJumpLink({ guardrailEntries }: { guardrailEntries: any[ border: `1px solid ${allPassed ? "#bbf7d0" : "#fecaca"}`, }} > - {allPassed ? "\u2713" : "\u2717"} {guardrailEntries.length} guardrail{guardrailEntries.length !== 1 ? "s" : ""}{" "} - evaluated + {allPassed ? "\u2713" : "\u2717"}{" "} + {t("viewLogs.logDetailContent.guardrailsEvaluated", { count: guardrailEntries.length })} {"\u2193"}
@@ -547,6 +575,7 @@ export function GuardrailJumpLink({ guardrailEntries }: { guardrailEntries: any[ } function MetadataSection({ metadata }: { metadata: Record }) { + const { t } = useTranslation(); return (
}) { items={[ { key: "1", - label:

Metadata

, + label:

{t("viewLogs.logDetailContent.metadata")}

, children: (
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 029a9814b81..bc748cdf93e 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailsDrawer.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailsDrawer.tsx @@ -1,4 +1,5 @@ import { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; import { Button, Drawer } from "antd"; import { CheckOutlined, CopyOutlined, LeftOutlined, RightOutlined } from "@ant-design/icons"; import { Bot, Sparkles, Wrench } from "lucide-react"; @@ -234,6 +235,8 @@ export function LogDetailsDrawer({ } }; + const { t } = useTranslation(); + if (!currentLog || !enrichedLog) return null; return ( @@ -259,7 +262,7 @@ export function LogDetailsDrawer({ icon={} onClick={() => setIsSidebarCollapsed(true)} className="absolute top-2 left-2 z-20 !bg-white !border !border-slate-200 !rounded-md" - aria-label="Collapse trace sidebar" + aria-label={t("viewLogs.logDetailsDrawer.collapseTraceSidebar")} /> ) : (
@@ -130,6 +132,7 @@ export function RealtimePrettyView({ response, metrics }: RealtimePrettyViewProp } function SessionCard({ session, turnCount }: { session: RealtimeSession; turnCount: number }) { + const { t } = useTranslation(); const [isCollapsed, setIsCollapsed] = useState(true); return ( @@ -170,14 +173,14 @@ function SessionCard({ session, turnCount }: { session: RealtimeSession; turnCou
- Session + {t("viewLogs.realtimePrettyView.labelSession")}
{session.model} {turnCount > 0 && ( - {turnCount} {turnCount === 1 ? "turn" : "turns"} + {t("viewLogs.realtimePrettyView.turnCount", { count: turnCount })} )} {session.voice && ( @@ -214,15 +217,32 @@ function SessionCard({ session, turnCount }: { session: RealtimeSession; turnCou fontSize: 13, }} > - - - - - - - {session.turn_detection && } + + + + + + + {session.turn_detection && ( + + )} {session.tools && session.tools.length > 0 && ( - + )} @@ -238,7 +258,7 @@ function SessionCard({ session, turnCount }: { session: RealtimeSession; turnCou marginBottom: 4, }} > - Instructions + {t("viewLogs.realtimePrettyView.labelInstructions")}
{usage && ( - {usage.input_tokens ?? 0} in / {usage.output_tokens ?? 0} out tokens + {t("viewLogs.realtimePrettyView.tokenUsage", { + input: usage.input_tokens ?? 0, + output: usage.output_tokens ?? 0, + })} )} {response.conversation_id && ( @@ -367,8 +391,18 @@ function ResponseTurn({ response, index }: { response: RealtimeResponse; index: ))} {/* Token breakdown if available */} - {usage?.input_token_details && } - {usage?.output_token_details && } + {usage?.input_token_details && ( + + )} + {usage?.output_token_details && ( + + )}
); } diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/SectionHeader.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/SectionHeader.tsx index 817928f170c..acd9e92d124 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/SectionHeader.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/SectionHeader.tsx @@ -2,6 +2,7 @@ * SectionHeader - Datadog-style header with icon, label, metrics, and copy */ +import { useTranslation } from "react-i18next"; import { Typography, Button, Tooltip } from "antd"; import { MessageOutlined, CopyOutlined, DownOutlined, UpOutlined } from "@ant-design/icons"; @@ -26,6 +27,7 @@ export function SectionHeader({ onToggleCollapse, turnCount, }: SectionHeaderProps) { + const { t } = useTranslation(); return (
✨ )} - {type === "input" ? "Input" : "Output"} + + {type === "input" ? t("viewLogs.sectionHeader.labelInput") : t("viewLogs.sectionHeader.labelOutput")} +
{/* Tokens */} {tokens !== undefined && ( - Tokens: {tokens.toLocaleString()} + {t("viewLogs.sectionHeader.tokens", { count: tokens.toLocaleString() })} )} {/* Cost */} {cost !== undefined && ( - Cost: ${cost.toFixed(6)} + {t("viewLogs.sectionHeader.cost", { amount: cost.toFixed(6) })} )} {/* Turn count */} {turnCount !== undefined && turnCount > 0 && ( - Turns: {turnCount} + {t("viewLogs.sectionHeader.turns", { count: turnCount })} )} {/* Copy Button */} - +