From e6704034de9aa7e18205661f65476b21e1de7ecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B9=E9=9B=AA?= <2b@yorha.xyz> Date: Wed, 10 Jun 2026 14:38:43 +0900 Subject: [PATCH] feat(ui): i18n for view_logs batch 1 --- .../AuditLogDrawer/AuditLogDrawer.tsx | 57 +++++--- .../view_logs/ConfigInfoMessage.tsx | 19 ++- .../view_logs/CostBreakdownViewer.tsx | 64 ++++++--- .../src/components/view_logs/ErrorViewer.tsx | 20 +-- .../view_logs/EvalViewer/EvalViewer.tsx | 39 +++--- .../BedrockGuardrailDetails.tsx | 128 ++++++++++-------- ui/litellm-dashboard/src/locales/en.json | 106 +++++++++++++++ ui/litellm-dashboard/src/locales/zh-CN.json | 106 +++++++++++++++ 8 files changed, 410 insertions(+), 129 deletions(-) diff --git a/ui/litellm-dashboard/src/components/view_logs/AuditLogDrawer/AuditLogDrawer.tsx b/ui/litellm-dashboard/src/components/view_logs/AuditLogDrawer/AuditLogDrawer.tsx index 0c303b03a3f..0eec24615dd 100644 --- a/ui/litellm-dashboard/src/components/view_logs/AuditLogDrawer/AuditLogDrawer.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/AuditLogDrawer/AuditLogDrawer.tsx @@ -2,6 +2,7 @@ import { Drawer, Tag, Typography } from "antd"; import { CloseOutlined, CopyOutlined, CheckOutlined } from "@ant-design/icons"; import { useState, useCallback } from "react"; import moment from "moment"; +import { useTranslation } from "react-i18next"; import { AuditLogEntry } from "../columns"; import DefaultProxyAdminTag from "../../common_components/DefaultProxyAdminTag"; @@ -13,14 +14,6 @@ interface AuditLogDrawerProps { log: AuditLogEntry | null; } -const TABLE_NAME_DISPLAY: Record = { - LiteLLM_VerificationToken: "Keys", - LiteLLM_TeamTable: "Teams", - LiteLLM_UserTable: "Users", - LiteLLM_OrganizationTable: "Organizations", - LiteLLM_ProxyModelTable: "Models", -}; - const ACTION_COLOR: Record = { created: "green", updated: "blue", @@ -29,6 +22,7 @@ const ACTION_COLOR: Record = { }; function CopyableJsonBlock({ label, value }: { label: string; value: Record }) { + const { t } = useTranslation(); const [copied, setCopied] = useState(false); const handleCopy = useCallback(async () => { @@ -61,7 +55,7 @@ function CopyableJsonBlock({ label, value }: { label: string; value: Record {copied ? : } @@ -83,6 +77,7 @@ function MetadataRow({ label, value }: { label: string; value: React.ReactNode } } function DiffSection({ log }: { log: AuditLogEntry }) { + const { t } = useTranslation(); const { action, table_name, before_value, updated_values } = log; const isKeyTable = table_name === "LiteLLM_VerificationToken"; const isUpdateAction = action === "updated" || action === "rotated"; @@ -120,8 +115,10 @@ function DiffSection({ log }: { log: AuditLogEntry }) { } }); - displayBefore = Object.keys(changedBefore).length > 0 ? changedBefore : { note: "No differing fields detected" }; - displayAfter = Object.keys(changedAfter).length > 0 ? changedAfter : { note: "No differing fields detected" }; + displayBefore = + Object.keys(changedBefore).length > 0 ? changedBefore : { note: t("viewLogs.auditLogDrawer.noDifferingFields") }; + displayAfter = + Object.keys(changedAfter).length > 0 ? changedAfter : { note: t("viewLogs.auditLogDrawer.noDifferingFields") }; } const renderValue = (label: string, value: Record | null | undefined) => { @@ -149,17 +146,20 @@ function DiffSection({ log }: { log: AuditLogEntry }) {
{value.token !== undefined && (

- Token: {value.token ?? "N/A"} + {t("viewLogs.auditLogDrawer.tokenLabel")}{" "} + {value.token ?? "N/A"}

)} {value.spend !== undefined && (

- Spend: ${Number(value.spend).toFixed(6)} + {t("viewLogs.auditLogDrawer.spendLabel")} $ + {Number(value.spend).toFixed(6)}

)} {value.max_budget !== undefined && (

- Max Budget: ${Number(value.max_budget).toFixed(6)} + {t("viewLogs.auditLogDrawer.maxBudgetLabel")} $ + {Number(value.max_budget).toFixed(6)}

)}
@@ -173,13 +173,23 @@ function DiffSection({ log }: { log: AuditLogEntry }) { return (
- {renderValue("Before", displayBefore)} - {renderValue("After", displayAfter)} + {renderValue(t("viewLogs.auditLogDrawer.before"), displayBefore)} + {renderValue(t("viewLogs.auditLogDrawer.after"), displayAfter)}
); } export function AuditLogDrawer({ open, onClose, log }: AuditLogDrawerProps) { + const { t } = useTranslation(); + + const TABLE_NAME_DISPLAY: Record = { + LiteLLM_VerificationToken: t("viewLogs.auditLogDrawer.tableKeys"), + LiteLLM_TeamTable: t("viewLogs.auditLogDrawer.tableTeams"), + LiteLLM_UserTable: t("viewLogs.auditLogDrawer.tableUsers"), + LiteLLM_OrganizationTable: t("viewLogs.auditLogDrawer.tableOrganizations"), + LiteLLM_ProxyModelTable: t("viewLogs.auditLogDrawer.tableModels"), + }; + if (!log) return null; const tableDisplay = TABLE_NAME_DISPLAY[log.table_name] ?? log.table_name; @@ -209,7 +219,7 @@ export function AuditLogDrawer({ open, onClose, log }: AuditLogDrawerProps) { @@ -219,19 +229,22 @@ export function AuditLogDrawer({ open, onClose, log }: AuditLogDrawerProps) {
{/* Metadata */}
-

Details

- +

{t("common.details")}

+ {log.object_id} } /> - } /> } + /> + diff --git a/ui/litellm-dashboard/src/components/view_logs/ConfigInfoMessage.tsx b/ui/litellm-dashboard/src/components/view_logs/ConfigInfoMessage.tsx index a231a099716..b040fb6b49a 100644 --- a/ui/litellm-dashboard/src/components/view_logs/ConfigInfoMessage.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/ConfigInfoMessage.tsx @@ -1,10 +1,13 @@ import React from "react"; +import { Trans, useTranslation } from "react-i18next"; interface ConfigInfoMessageProps { show: boolean; } export const ConfigInfoMessage: React.FC = ({ show }) => { + const { t } = useTranslation(); + if (!show) return null; return ( @@ -27,20 +30,22 @@ export const ConfigInfoMessage: React.FC = ({ show }) =>
-

Request/Response Data Not Available

+

{t("viewLogs.configInfoMessage.title")}

- To view request and response details, enable prompt storage in your LiteLLM configuration by adding the - following to your proxy_config.yaml file, or toggle - the setting in Admin Settings → Logging Settings. + , + settingsPath: , + }} + />

           {`general_settings:
   store_model_in_db: true
   store_prompts_in_spend_logs: true`}
         
-

- Note: This will only affect new requests after the configuration change. -

+

{t("viewLogs.configInfoMessage.note")}

); diff --git a/ui/litellm-dashboard/src/components/view_logs/CostBreakdownViewer.tsx b/ui/litellm-dashboard/src/components/view_logs/CostBreakdownViewer.tsx index 1832951c734..2c7a8931513 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 { useTranslation } from "react-i18next"; import { formatNumberWithCommas } from "@/utils/dataUtils"; export interface CostBreakdown { @@ -49,6 +50,7 @@ export const CostBreakdownViewer: React.FC = ({ cacheReadTokens, cacheCreationTokens, }) => { + const { t } = useTranslation(); const isCached = cacheHit?.toLowerCase() === "true"; const hasTokenCounts = promptTokens !== undefined || completionTokens !== undefined; @@ -97,12 +99,12 @@ export const CostBreakdownViewer: React.FC = ({ key: "1", label: (
-

Cost Breakdown

+

{t("viewLogs.costBreakdownViewer.title")}

- Total: + {t("common.total")}: {formatCost(totalSpend)} - {isCached && " (Cached)"} + {isCached && ` (${t("viewLogs.costBreakdownViewer.cached")})`}
@@ -124,24 +126,29 @@ export const CostBreakdownViewer: React.FC = ({ return ( <>
- Input Cost: + + {t("viewLogs.costBreakdownViewer.inputCost")}: + {formatCost(rawCost)} {rawInputTokens !== undefined && rawInputTokens !== null && ( - ({rawInputTokens.toLocaleString()} tokens) + ({rawInputTokens.toLocaleString()} {t("viewLogs.costBreakdownViewer.tokens")}) )}
{(costBreakdown?.cache_read_cost ?? 0) > 0 && (
- Cache Read Cost: + + {t("viewLogs.costBreakdownViewer.cacheReadCost")}: + {formatCost(isCached ? 0 : costBreakdown?.cache_read_cost)} {(cacheReadTokens ?? 0) > 0 && ( - ({(cacheReadTokens ?? 0).toLocaleString()} tokens) + ({(cacheReadTokens ?? 0).toLocaleString()}{" "} + {t("viewLogs.costBreakdownViewer.tokens")}) )} @@ -149,12 +156,15 @@ export const CostBreakdownViewer: React.FC = ({ )} {(costBreakdown?.cache_creation_cost ?? 0) > 0 && (
- Cache Write Cost: + + {t("viewLogs.costBreakdownViewer.cacheWriteCost")}: + {formatCost(isCached ? 0 : costBreakdown?.cache_creation_cost)} {(cacheCreationTokens ?? 0) > 0 && ( - ({(cacheCreationTokens ?? 0).toLocaleString()} tokens) + ({(cacheCreationTokens ?? 0).toLocaleString()}{" "} + {t("viewLogs.costBreakdownViewer.tokens")}) )} @@ -165,12 +175,14 @@ export const CostBreakdownViewer: React.FC = ({ } return (
- Input Cost: + + {t("viewLogs.costBreakdownViewer.inputCost")}: + {formatCost(inputCost)} {promptTokens !== undefined && ( - ({promptTokens.toLocaleString()} prompt tokens) + ({promptTokens.toLocaleString()} {t("viewLogs.costBreakdownViewer.promptTokens")}) )} @@ -178,19 +190,23 @@ export const CostBreakdownViewer: React.FC = ({ ); })()}
- Output Cost: + + {t("viewLogs.costBreakdownViewer.outputCost")}: + {formatCost(outputCost)} {completionTokens !== undefined && ( - ({completionTokens.toLocaleString()} completion tokens) + ({completionTokens.toLocaleString()} {t("viewLogs.costBreakdownViewer.completionTokens")}) )}
{costBreakdown?.tool_usage_cost !== undefined && costBreakdown.tool_usage_cost > 0 && (
- Tool Usage Cost: + + {t("viewLogs.costBreakdownViewer.toolUsageCost")}: + {formatCost(costBreakdown.tool_usage_cost)}
)} @@ -209,7 +225,7 @@ export const CostBreakdownViewer: React.FC = ({ {!isCached && (
- Original LLM Cost: + {t("viewLogs.costBreakdownViewer.originalLlmCost")}: {formatCost(originalCost)}
@@ -224,7 +240,8 @@ export const CostBreakdownViewer: React.FC = ({ {costBreakdown.discount_percent !== undefined && costBreakdown.discount_percent !== 0 && (
- Discount ({formatPercent(costBreakdown.discount_percent)}): + {t("viewLogs.costBreakdownViewer.discount")} ( + {formatPercent(costBreakdown.discount_percent)}): -{formatCost(costBreakdown.discount_amount)}
@@ -232,7 +249,9 @@ export const CostBreakdownViewer: React.FC = ({ {costBreakdown.discount_amount !== undefined && costBreakdown.discount_percent === undefined && (
- Discount Amount: + + {t("viewLogs.costBreakdownViewer.discountAmount")}: + -{formatCost(costBreakdown.discount_amount)}
)} @@ -245,7 +264,8 @@ export const CostBreakdownViewer: React.FC = ({ {costBreakdown.margin_percent !== undefined && costBreakdown.margin_percent !== 0 && (
- Margin ({formatPercent(costBreakdown.margin_percent)}): + {t("viewLogs.costBreakdownViewer.margin")} ({formatPercent(costBreakdown.margin_percent)} + ): + @@ -257,7 +277,7 @@ export const CostBreakdownViewer: React.FC = ({ )} {costBreakdown.margin_fixed_amount !== undefined && costBreakdown.margin_fixed_amount !== 0 && (
- Margin: + {t("viewLogs.costBreakdownViewer.margin")}: +{formatCost(costBreakdown.margin_fixed_amount)}
)} @@ -269,10 +289,12 @@ export const CostBreakdownViewer: React.FC = ({ {/* Final Summary */}
- Final Calculated Cost: + + {t("viewLogs.costBreakdownViewer.finalCalculatedCost")}: + {formatCost(totalCost)} - {isCached && " (Cached)"} + {isCached && ` (${t("viewLogs.costBreakdownViewer.cached")})`}
diff --git a/ui/litellm-dashboard/src/components/view_logs/ErrorViewer.tsx b/ui/litellm-dashboard/src/components/view_logs/ErrorViewer.tsx index e0a34da6690..905c945a63c 100644 --- a/ui/litellm-dashboard/src/components/view_logs/ErrorViewer.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/ErrorViewer.tsx @@ -1,4 +1,5 @@ import React from "react"; +import { useTranslation } from "react-i18next"; interface ErrorViewerProps { errorInfo: { @@ -11,6 +12,7 @@ interface ErrorViewerProps { } export const ErrorViewer: React.FC = ({ errorInfo }) => { + const { t } = useTranslation(); const [expandedFrames, setExpandedFrames] = React.useState<{ [key: number]: boolean }>({}); const [allExpanded, setAllExpanded] = React.useState(false); @@ -89,20 +91,20 @@ export const ErrorViewer: React.FC = ({ errorInfo }) => { d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /> - Error Details + {t("viewLogs.errorViewer.title")}
- Type: - {errorInfo.error_class || "Unknown Error"} + {t("viewLogs.errorViewer.type")}: + {errorInfo.error_class || t("viewLogs.errorViewer.unknownError")}
- Message: + {t("viewLogs.errorViewer.message")}: - {errorInfo.error_message || "Unknown error occurred"} + {errorInfo.error_message || t("viewLogs.errorViewer.unknownErrorOccurred")}
@@ -110,18 +112,18 @@ export const ErrorViewer: React.FC = ({ errorInfo }) => { {errorInfo.traceback && (
-

Traceback

+

{t("viewLogs.errorViewer.traceback")}

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..760d0172f28 100644 --- a/ui/litellm-dashboard/src/components/view_logs/EvalViewer/EvalViewer.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/EvalViewer/EvalViewer.tsx @@ -1,6 +1,7 @@ import React from "react"; import { Card, Tag, Table, Typography, Space, Tooltip } from "antd"; import { CheckCircleOutlined, CloseCircleOutlined, ExperimentOutlined } from "@ant-design/icons"; +import { useTranslation } from "react-i18next"; const { Text } = Typography; @@ -29,6 +30,7 @@ interface EvalViewerProps { } export default function EvalViewer({ data }: EvalViewerProps) { + const { t } = useTranslation(); const entries: EvalInformation[] = Array.isArray(data) ? data : [data]; if (!entries.length) return null; @@ -38,7 +40,7 @@ export default function EvalViewer({ data }: EvalViewerProps) {
- LLM Judge Results + {t("viewLogs.evalViewer.title")}
@@ -50,6 +52,7 @@ export default function EvalViewer({ data }: EvalViewerProps) { } function EvalEntryCard({ entry }: { entry: EvalInformation }) { + const { t } = useTranslation(); const passed = entry.passed; const scoreColor = passed ? "#52c41a" : "#ff4d4f"; @@ -58,7 +61,7 @@ function EvalEntryCard({ entry }: { entry: EvalInformation }) { const columns = [ { - title: "Criterion", + title: t("viewLogs.evalViewer.colCriterion"), dataIndex: "criterion_name", key: "criterion_name", width: 160, @@ -69,7 +72,7 @@ function EvalEntryCard({ entry }: { entry: EvalInformation }) { ), }, { - title: "Weight", + title: t("viewLogs.evalViewer.colWeight"), dataIndex: "weight", key: "weight", width: 65, @@ -81,7 +84,7 @@ function EvalEntryCard({ entry }: { entry: EvalInformation }) { ) : null, }, { - title: "Score", + title: t("viewLogs.evalViewer.colScore"), dataIndex: "score", key: "score", width: 65, @@ -91,8 +94,10 @@ function EvalEntryCard({ entry }: { entry: EvalInformation }) { }, { title: ( - - Weighted + + + {t("viewLogs.evalViewer.colWeighted")} + ), key: "weighted", @@ -108,7 +113,7 @@ function EvalEntryCard({ entry }: { entry: EvalInformation }) { }, }, { - title: "Comment", + title: t("viewLogs.evalViewer.colComment"), dataIndex: "reasoning", key: "reasoning", ellipsis: { showTitle: false }, @@ -133,13 +138,13 @@ function EvalEntryCard({ entry }: { entry: EvalInformation }) { )} {entry.eval_name} - {passed ? "PASSED" : "FAILED"} - + + {passed ? t("viewLogs.evalViewer.passed") : t("viewLogs.evalViewer.failed")} + + {entry.overall_score?.toFixed(0)} / 100 - {entry.threshold != null && ` (threshold: ${entry.threshold})`} + {entry.threshold != null && ` (${t("viewLogs.evalViewer.threshold")}: ${entry.threshold})`} @@ -148,12 +153,12 @@ function EvalEntryCard({ entry }: { entry: EvalInformation }) { {entry.judge_model && ( - Judge: {entry.judge_model} + {t("viewLogs.evalViewer.judge")}: {entry.judge_model} )} {entry.iteration != null && ( - Iter: {entry.iteration + 1} + {t("viewLogs.evalViewer.iter")}: {entry.iteration + 1} )} @@ -161,7 +166,7 @@ function EvalEntryCard({ entry }: { entry: EvalInformation }) { > {entry.eval_error && ( - Judge error: {entry.eval_error} + {t("viewLogs.evalViewer.judgeError")}: {entry.eval_error} )} @@ -181,7 +186,7 @@ function EvalEntryCard({ entry }: { entry: EvalInformation }) { - Total + {t("common.total")} @@ -198,7 +203,7 @@ function EvalEntryCard({ entry }: { entry: EvalInformation }) { /> ) : ( - Score: {entry.overall_score?.toFixed(1)} — no per-criterion breakdown available. + {t("viewLogs.evalViewer.scoreNoCriterion", { score: entry.overall_score?.toFixed(1) })} )} diff --git a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/BedrockGuardrailDetails.tsx b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/BedrockGuardrailDetails.tsx index 89fa635e7a3..40d0353c70d 100644 --- a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/BedrockGuardrailDetails.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/BedrockGuardrailDetails.tsx @@ -1,4 +1,5 @@ import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; export type BedrockGuardrailAction = "NONE" | "GUARDRAIL_INTERVENED"; @@ -114,7 +115,10 @@ const chip = (text: React.ReactNode, tone: ChipTone = "slate") => { return {text}; }; -const boolPill = (b?: boolean) => (b ? chip("detected", "red") : chip("not detected", "slate")); +const boolPill = (b: boolean | undefined, t: (key: string) => string) => + b + ? chip(t("viewLogs.bedrockGuardrailDetails.detected"), "red") + : chip(t("viewLogs.bedrockGuardrailDetails.notDetected"), "slate"); interface SectionProps { title: string; @@ -169,6 +173,8 @@ const Divider: React.FC = () =>
; /** ====== Main component ====== */ export const BedrockGuardrailDetails: React.FC<{ response: BedrockGuardrailResponse }> = ({ response }) => { + const { t } = useTranslation(); + if (!response) return null; const outputs: BedrockOutputContent[] = (response.outputs ?? response.output ?? []) as BedrockOutputContent[]; @@ -179,12 +185,18 @@ export const BedrockGuardrailDetails: React.FC<{ response: BedrockGuardrailRespo
{response.guardrailCoverage?.textCharacters && chip( - `text guarded ${response.guardrailCoverage.textCharacters.guarded ?? 0}/${response.guardrailCoverage.textCharacters.total ?? 0}`, + t("viewLogs.bedrockGuardrailDetails.textGuarded", { + guarded: response.guardrailCoverage.textCharacters.guarded ?? 0, + total: response.guardrailCoverage.textCharacters.total ?? 0, + }), "blue", )} {response.guardrailCoverage?.images && chip( - `images guarded ${response.guardrailCoverage.images.guarded ?? 0}/${response.guardrailCoverage.images.total ?? 0}`, + t("viewLogs.bedrockGuardrailDetails.imagesGuarded", { + guarded: response.guardrailCoverage.images.guarded ?? 0, + total: response.guardrailCoverage.images.total ?? 0, + }), "blue", )}
@@ -208,17 +220,21 @@ export const BedrockGuardrailDetails: React.FC<{ response: BedrockGuardrailRespo
- {chip(response.action ?? "N/A", actionTone)} - {response.actionReason && {response.actionReason}} + + {chip(response.action ?? "N/A", actionTone)} + + {response.actionReason && ( + {response.actionReason} + )} {response.blockedResponse && ( - + {response.blockedResponse} )}
- {coverageChips} - {usagePills} + {coverageChips} + {usagePills}
@@ -226,11 +242,13 @@ export const BedrockGuardrailDetails: React.FC<{ response: BedrockGuardrailRespo {outputs.length > 0 && ( <> -

Outputs

+

{t("viewLogs.bedrockGuardrailDetails.outputs")}

{outputs.map((o, i) => (
-
{o.text ?? (non-text output)}
+
+ {o.text ?? {t("viewLogs.bedrockGuardrailDetails.nonTextOutput")}} +
))}
@@ -256,7 +274,7 @@ export const BedrockGuardrailDetails: React.FC<{ response: BedrockGuardrailRespo return (
@@ -269,9 +287,9 @@ export const BedrockGuardrailDetails: React.FC<{ response: BedrockGuardrailRespo {/* Word policy */} {assess.wordPolicy && (
-
Word Policy
+
{t("viewLogs.bedrockGuardrailDetails.wordPolicy")}
{(assess.wordPolicy.customWords?.length ?? 0) > 0 && ( -
+
{assess.wordPolicy.customWords!.map((w, i) => (
@@ -279,14 +297,14 @@ export const BedrockGuardrailDetails: React.FC<{ response: BedrockGuardrailRespo {chip(w.action ?? "N/A", w.detected ? "red" : "slate")} {w.match}
- {boolPill(w.detected)} + {boolPill(w.detected, t)}
))}
)} {(assess.wordPolicy.managedWordLists?.length ?? 0) > 0 && ( -
+
{assess.wordPolicy.managedWordLists!.map((w, i) => (
@@ -295,7 +313,7 @@ export const BedrockGuardrailDetails: React.FC<{ response: BedrockGuardrailRespo {w.match} {w.type && chip(w.type, "slate")}
- {boolPill(w.detected)} + {boolPill(w.detected, t)}
))}
@@ -307,16 +325,16 @@ export const BedrockGuardrailDetails: React.FC<{ response: BedrockGuardrailRespo {/* Content policy */} {assess.contentPolicy?.filters?.length ? (
-
Content Policy
+
{t("viewLogs.bedrockGuardrailDetails.contentPolicy")}
- - - - - + + + + + @@ -324,7 +342,7 @@ export const BedrockGuardrailDetails: React.FC<{ response: BedrockGuardrailRespo - + @@ -338,16 +356,16 @@ export const BedrockGuardrailDetails: React.FC<{ response: BedrockGuardrailRespo {/* Contextual grounding */} {assess.contextualGroundingPolicy?.filters?.length ? (
-
Contextual Grounding
+
{t("viewLogs.bedrockGuardrailDetails.contextualGrounding")}
TypeActionDetectedStrengthConfidence{t("viewLogs.bedrockGuardrailDetails.colType")}{t("viewLogs.bedrockGuardrailDetails.colAction")}{t("viewLogs.bedrockGuardrailDetails.colDetected")}{t("viewLogs.bedrockGuardrailDetails.colStrength")}{t("viewLogs.bedrockGuardrailDetails.colConfidence")}
{f.type ?? "—"} {chip(f.action ?? "—", f.detected ? "red" : "slate")}{boolPill(f.detected)}{boolPill(f.detected, t)} {f.filterStrength ?? "—"} {f.confidence ?? "—"}
- - - - - + + + + + @@ -355,7 +373,7 @@ export const BedrockGuardrailDetails: React.FC<{ response: BedrockGuardrailRespo - + @@ -369,9 +387,9 @@ export const BedrockGuardrailDetails: React.FC<{ response: BedrockGuardrailRespo {/* Sensitive Information */} {assess.sensitiveInformationPolicy && (
-
Sensitive Information
+
{t("viewLogs.bedrockGuardrailDetails.sensitiveInformation")}
{(assess.sensitiveInformationPolicy.piiEntities?.length ?? 0) > 0 && ( -
+
{assess.sensitiveInformationPolicy.piiEntities!.map((p, i) => (
@@ -380,14 +398,14 @@ export const BedrockGuardrailDetails: React.FC<{ response: BedrockGuardrailRespo {p.type && chip(p.type, "slate")} {p.match}
- {boolPill(p.detected)} + {boolPill(p.detected, t)}
))}
)} {(assess.sensitiveInformationPolicy.regexes?.length ?? 0) > 0 && ( -
+
{assess.sensitiveInformationPolicy.regexes!.map((r, i) => (
{r.regex}
- {boolPill(r.detected)} + {boolPill(r.detected, t)} {r.match && {r.match}}
@@ -414,15 +432,15 @@ export const BedrockGuardrailDetails: React.FC<{ response: BedrockGuardrailRespo {/* Topic policy */} {assess.topicPolicy?.topics?.length ? (
-
Topic Policy
+
{t("viewLogs.bedrockGuardrailDetails.topicPolicy")}
- {assess.topicPolicy.topics!.map((t, i) => ( + {assess.topicPolicy.topics!.map((topic, i) => (
- {chip(t.action ?? "N/A", t.detected ? "red" : "slate")} - {t.name ?? "topic"} - {t.type && chip(t.type, "slate")} - {boolPill(t.detected)} + {chip(topic.action ?? "N/A", topic.detected ? "red" : "slate")} + {topic.name ?? "topic"} + {topic.type && chip(topic.type, "slate")} + {boolPill(topic.detected, t)}
))} @@ -432,31 +450,35 @@ export const BedrockGuardrailDetails: React.FC<{ response: BedrockGuardrailRespo {/* Invocation metrics */} {assess.invocationMetrics && ( -
+
- {assess.invocationMetrics.guardrailProcessingLatency ?? "—"} - + + {assess.invocationMetrics.guardrailProcessingLatency ?? "—"} + +
{assess.invocationMetrics.guardrailCoverage?.textCharacters && chip( - `text ${assess.invocationMetrics.guardrailCoverage.textCharacters.guarded ?? 0}/${ - assess.invocationMetrics.guardrailCoverage.textCharacters.total ?? 0 - }`, + t("viewLogs.bedrockGuardrailDetails.textCoverage", { + guarded: assess.invocationMetrics.guardrailCoverage.textCharacters.guarded ?? 0, + total: assess.invocationMetrics.guardrailCoverage.textCharacters.total ?? 0, + }), "blue", )} {assess.invocationMetrics.guardrailCoverage?.images && chip( - `images ${assess.invocationMetrics.guardrailCoverage.images.guarded ?? 0}/${ - assess.invocationMetrics.guardrailCoverage.images.total ?? 0 - }`, + t("viewLogs.bedrockGuardrailDetails.imagesCoverage", { + guarded: assess.invocationMetrics.guardrailCoverage.images.guarded ?? 0, + total: assess.invocationMetrics.guardrailCoverage.images.total ?? 0, + }), "blue", )}
- +
{assess.invocationMetrics.usage && Object.entries(assess.invocationMetrics.usage).map(([k, v]) => @@ -478,7 +500,7 @@ export const BedrockGuardrailDetails: React.FC<{ response: BedrockGuardrailRespo {/* Automated reasoning (fallback render) */} {assess.automatedReasoningPolicy?.findings?.length ? ( -
+
{assess.automatedReasoningPolicy.findings!.map((f, i) => (
@@ -495,7 +517,7 @@ export const BedrockGuardrailDetails: React.FC<{ response: BedrockGuardrailRespo
       ) : null}
 
       {/* Raw JSON (for debugging / completeness) */}
-      
+
{JSON.stringify(response, null, 2)}
diff --git a/ui/litellm-dashboard/src/locales/en.json b/ui/litellm-dashboard/src/locales/en.json index d5f9da4ada7..4d590dd6121 100644 --- a/ui/litellm-dashboard/src/locales/en.json +++ b/ui/litellm-dashboard/src/locales/en.json @@ -1950,6 +1950,112 @@ "labelTotalSpend": "Total Spend (USD)" } }, + "viewLogs": { + "auditLogDrawer": { + "tableKeys": "Keys", + "tableTeams": "Teams", + "tableUsers": "Users", + "tableOrganizations": "Organizations", + "tableModels": "Models", + "copyJson": "Copy JSON", + "noDifferingFields": "No differing fields detected", + "tokenLabel": "Token:", + "spendLabel": "Spend:", + "maxBudgetLabel": "Max Budget:", + "before": "Before", + "after": "After", + "labelTable": "Table", + "labelObjectId": "Object ID", + "labelChangedBy": "Changed By", + "labelApiKeyHash": "API Key (Hash)" + }, + "configInfoMessage": { + "title": "Request/Response Data Not Available", + "description": "To view request and response details, enable prompt storage in your LiteLLM configuration by adding the following to your proxy_config.yaml file, or toggle the setting in Admin Settings → Logging Settings.", + "note": "Note: This will only affect new requests after the configuration change." + }, + "costBreakdownViewer": { + "title": "Cost Breakdown", + "cached": "Cached", + "inputCost": "Input Cost", + "outputCost": "Output Cost", + "cacheReadCost": "Cache Read Cost", + "cacheWriteCost": "Cache Write Cost", + "toolUsageCost": "Tool Usage Cost", + "tokens": "tokens", + "promptTokens": "prompt tokens", + "completionTokens": "completion tokens", + "originalLlmCost": "Original LLM Cost", + "discount": "Discount", + "discountAmount": "Discount Amount", + "margin": "Margin", + "finalCalculatedCost": "Final Calculated Cost" + }, + "errorViewer": { + "title": "Error Details", + "type": "Type", + "message": "Message", + "unknownError": "Unknown Error", + "unknownErrorOccurred": "Unknown error occurred", + "traceback": "Traceback", + "collapseAll": "Collapse All", + "expandAll": "Expand All", + "copyTraceback": "Copy traceback" + }, + "evalViewer": { + "title": "LLM Judge Results", + "colCriterion": "Criterion", + "colWeight": "Weight", + "colScore": "Score", + "colWeighted": "Weighted", + "weightedTooltip": "Score × Weight — how much each criterion contributes to the final score", + "colComment": "Comment", + "passed": "PASSED", + "failed": "FAILED", + "overallScoreTooltip": "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.", + "threshold": "threshold", + "judge": "Judge", + "iter": "Iter", + "judgeError": "Judge error", + "scoreNoCriterion": "Score: {{score}} — no per-criterion breakdown available." + }, + "bedrockGuardrailDetails": { + "detected": "detected", + "notDetected": "not detected", + "textGuarded": "text guarded {{guarded}}/{{total}}", + "imagesGuarded": "images guarded {{guarded}}/{{total}}", + "action": "Action", + "actionReason": "Action Reason", + "blockedResponse": "Blocked Response", + "coverage": "Coverage", + "usage": "Usage", + "outputs": "Outputs", + "nonTextOutput": "(non-text output)", + "assessmentTitle": "Assessment #{{number}}", + "wordPolicy": "Word Policy", + "customWords": "Custom Words", + "managedWordLists": "Managed Word Lists", + "contentPolicy": "Content Policy", + "colType": "Type", + "colAction": "Action", + "colDetected": "Detected", + "colStrength": "Strength", + "colConfidence": "Confidence", + "contextualGrounding": "Contextual Grounding", + "colScore": "Score", + "colThreshold": "Threshold", + "sensitiveInformation": "Sensitive Information", + "piiEntities": "PII Entities", + "customRegexes": "Custom Regexes", + "topicPolicy": "Topic Policy", + "invocationMetrics": "Invocation Metrics", + "latencyMs": "Latency (ms)", + "textCoverage": "text {{guarded}}/{{total}}", + "imagesCoverage": "images {{guarded}}/{{total}}", + "automatedReasoningFindings": "Automated Reasoning Findings", + "rawResponse": "Raw Bedrock Guardrail Response" + } + }, "userAgentActivity": { "summaryTitle": "Summary by User Agent", "summarySubtitle": "Performance metrics for different user agents", diff --git a/ui/litellm-dashboard/src/locales/zh-CN.json b/ui/litellm-dashboard/src/locales/zh-CN.json index 1a6b758dfb5..b968d24f852 100644 --- a/ui/litellm-dashboard/src/locales/zh-CN.json +++ b/ui/litellm-dashboard/src/locales/zh-CN.json @@ -1950,6 +1950,112 @@ "labelTotalSpend": "总花费(USD)" } }, + "viewLogs": { + "auditLogDrawer": { + "tableKeys": "密钥", + "tableTeams": "团队", + "tableUsers": "用户", + "tableOrganizations": "组织", + "tableModels": "模型", + "copyJson": "复制 JSON", + "noDifferingFields": "未检测到差异字段", + "tokenLabel": "Token:", + "spendLabel": "花费:", + "maxBudgetLabel": "最大预算:", + "before": "变更前", + "after": "变更后", + "labelTable": "表", + "labelObjectId": "对象 ID", + "labelChangedBy": "操作人", + "labelApiKeyHash": "API 密钥(哈希)" + }, + "configInfoMessage": { + "title": "请求/响应数据不可用", + "description": "要查看请求和响应详情,请在您的 LiteLLM 配置中启用提示词存储,方法是在 proxy_config.yaml 文件中添加以下内容,或在管理员设置 → 日志设置中切换该选项。", + "note": "注意:此设置仅对配置更改后的新请求生效。" + }, + "costBreakdownViewer": { + "title": "费用明细", + "cached": "已缓存", + "inputCost": "输入费用", + "outputCost": "输出费用", + "cacheReadCost": "缓存读取费用", + "cacheWriteCost": "缓存写入费用", + "toolUsageCost": "工具使用费用", + "tokens": "Token", + "promptTokens": "提示词 Token", + "completionTokens": "补全 Token", + "originalLlmCost": "原始 LLM 费用", + "discount": "折扣", + "discountAmount": "折扣金额", + "margin": "利润", + "finalCalculatedCost": "最终计算费用" + }, + "errorViewer": { + "title": "错误详情", + "type": "类型", + "message": "消息", + "unknownError": "未知错误", + "unknownErrorOccurred": "发生了未知错误", + "traceback": "堆栈跟踪", + "collapseAll": "全部折叠", + "expandAll": "全部展开", + "copyTraceback": "复制堆栈跟踪" + }, + "evalViewer": { + "title": "LLM 评判结果", + "colCriterion": "标准", + "colWeight": "权重", + "colScore": "得分", + "colWeighted": "加权得分", + "weightedTooltip": "得分 × 权重 — 每个标准对最终得分的贡献度", + "colComment": "备注", + "passed": "通过", + "failed": "未通过", + "overallScoreTooltip": "所有标准得分的加权平均值。每个标准在创建评估时设置权重(%),权重越高的标准对最终得分影响越大。", + "threshold": "阈值", + "judge": "评判模型", + "iter": "迭代", + "judgeError": "评判错误", + "scoreNoCriterion": "得分:{{score}} — 无逐标准明细。" + }, + "bedrockGuardrailDetails": { + "detected": "已检测", + "notDetected": "未检测", + "textGuarded": "文本已守护 {{guarded}}/{{total}}", + "imagesGuarded": "图片已守护 {{guarded}}/{{total}}", + "action": "动作", + "actionReason": "动作原因", + "blockedResponse": "已拦截响应", + "coverage": "覆盖范围", + "usage": "用量", + "outputs": "输出", + "nonTextOutput": "(非文本输出)", + "assessmentTitle": "评估 #{{number}}", + "wordPolicy": "词语策略", + "customWords": "自定义词语", + "managedWordLists": "托管词语列表", + "contentPolicy": "内容策略", + "colType": "类型", + "colAction": "动作", + "colDetected": "是否检测到", + "colStrength": "强度", + "colConfidence": "置信度", + "contextualGrounding": "上下文接地", + "colScore": "得分", + "colThreshold": "阈值", + "sensitiveInformation": "敏感信息", + "piiEntities": "PII 实体", + "customRegexes": "自定义正则", + "topicPolicy": "主题策略", + "invocationMetrics": "调用指标", + "latencyMs": "延迟(毫秒)", + "textCoverage": "文本 {{guarded}}/{{total}}", + "imagesCoverage": "图片 {{guarded}}/{{total}}", + "automatedReasoningFindings": "自动推理发现", + "rawResponse": "原始 Bedrock 护栏响应" + } + }, "userAgentActivity": { "summaryTitle": "按 User Agent 汇总", "summarySubtitle": "不同 User Agent 的性能指标",
TypeActionDetectedScoreThreshold{t("viewLogs.bedrockGuardrailDetails.colType")}{t("viewLogs.bedrockGuardrailDetails.colAction")}{t("viewLogs.bedrockGuardrailDetails.colDetected")}{t("viewLogs.bedrockGuardrailDetails.colScore")}{t("viewLogs.bedrockGuardrailDetails.colThreshold")}
{f.type ?? "—"} {chip(f.action ?? "—", f.detected ? "red" : "slate")}{boolPill(f.detected)}{boolPill(f.detected, t)} {f.score ?? "—"} {f.threshold ?? "—"}