diff --git a/ui/litellm-dashboard/src/components/UsagePage/components/UsageAIChatPanel.tsx b/ui/litellm-dashboard/src/components/UsagePage/components/UsageAIChatPanel.tsx index 9384d75f020..249dabe2cef 100644 --- a/ui/litellm-dashboard/src/components/UsagePage/components/UsageAIChatPanel.tsx +++ b/ui/litellm-dashboard/src/components/UsagePage/components/UsageAIChatPanel.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useRef, useState } from "react"; import { Button, Select, Input, Spin } from "antd"; import ReactMarkdown from "react-markdown"; +import { useTranslation } from "react-i18next"; import { modelHubCall, usageAiChatStream, UsageAiToolCallEvent } from "../../networking"; const { TextArea } = Input; @@ -32,6 +33,7 @@ const TOOL_ICONS: Record = { }; const ToolCallDisplay: React.FC<{ step: ToolCallStep }> = ({ step }) => { + const { t } = useTranslation(); const icon = TOOL_ICONS[step.tool_name] || "🔧"; const args = step.arguments; const dateRange = args.start_date && args.end_date ? `${args.start_date} → ${args.end_date}` : ""; @@ -53,7 +55,9 @@ const ToolCallDisplay: React.FC<{ step: ToolCallStep }> = ({ step }) => { {icon} {step.tool_label} {dateRange &&
{dateRange}
} - {filter &&
Filter: {filter}
} + {filter && ( +
{t("usagePage.usageAiChatPanel.filterLabel", { filter })}
+ )} {step.status === "error" && step.error &&
{step.error}
} @@ -97,6 +101,7 @@ const MarkdownContent: React.FC<{ content: string }> = ({ content }) => ( ); const UsageAIChatPanel: React.FC = ({ open, onClose, accessToken }) => { + const { t } = useTranslation(); const [messages, setMessages] = useState([]); const [inputText, setInputText] = useState(""); const [isLoading, setIsLoading] = useState(false); @@ -177,7 +182,10 @@ const UsageAIChatPanel: React.FC = ({ open, onClose, acce (errorMsg: string) => { setStatusMessage(null); setActiveToolCalls([]); - setMessages((prev) => [...prev, { role: "assistant", content: `Error: ${errorMsg}` }]); + setMessages((prev) => [ + ...prev, + { role: "assistant", content: t("usagePage.usageAiChatPanel.errorPrefix", { message: errorMsg }) }, + ]); setStreamingContent(""); }, (status: string) => { @@ -198,8 +206,11 @@ const UsageAIChatPanel: React.FC = ({ open, onClose, acce if (error?.name === "AbortError" || abortController.signal.aborted) { return; } - const errorMsg = error?.message || "Failed to get response. Please try again."; - setMessages((prev) => [...prev, { role: "assistant", content: `Error: ${errorMsg}` }]); + const errorMsg = error?.message || t("usagePage.usageAiChatPanel.fallbackError"); + setMessages((prev) => [ + ...prev, + { role: "assistant", content: t("usagePage.usageAiChatPanel.errorPrefix", { message: errorMsg }) }, + ]); setStreamingContent(""); } finally { setIsLoading(false); @@ -243,7 +254,7 @@ const UsageAIChatPanel: React.FC = ({ open, onClose, acce -

Ask AI

+

{t("usagePage.usageAiChatPanel.title")}

-

Ask about your spend, models, keys, and trends

+

{t("usagePage.usageAiChatPanel.subtitle")}

{/* Model selector */}
setSelectedUserId(value ?? null)} filterOption={false} @@ -508,7 +519,9 @@ const UsagePage: React.FC = ({ teams, organizations }) => { searchValue={userSearchInput} onPopupScroll={handleUserPopupScroll} loading={isLoadingUsers} - notFoundContent={isLoadingUsers ? : "No users found"} + notFoundContent={ + isLoadingUsers ? : t("usagePage.usagePageView.noUsersFound") + } options={userOptions} popupRender={(menu) => ( <> @@ -526,11 +539,11 @@ const UsagePage: React.FC = ({ teams, organizations }) => {
- Cost - Model Activity - Key Activity - MCP Server Activity - Endpoint Activity + {t("usagePage.usagePageView.tabCost")} + {t("usagePage.usagePageView.tabModelActivity")} + {t("usagePage.usagePageView.tabKeyActivity")} + {t("usagePage.usagePageView.tabMcpServerActivity")} + {t("usagePage.usagePageView.tabEndpointActivity")}
@@ -568,7 +581,7 @@ const UsagePage: React.FC = ({ teams, organizations }) => {
- Project Spend{" "} + {t("usagePage.usagePageView.projectSpend")}{" "} {dateValue.from && dateValue.to && ( <> {dateValue.from.toLocaleDateString("en-US", { @@ -597,24 +610,24 @@ const UsagePage: React.FC = ({ teams, organizations }) => { - Usage Metrics + {t("usagePage.usagePageView.usageMetrics")} - Total Requests + {t("usagePage.usagePageView.totalRequests")} {userSpendData.metadata?.total_api_requests?.toLocaleString() || 0} - Successful Requests + {t("usagePage.usagePageView.successfulRequests")} {userSpendData.metadata?.total_successful_requests?.toLocaleString() || 0}
- Failed Requests - + {t("usagePage.usagePageView.failedRequests")} +
@@ -623,7 +636,7 @@ const UsagePage: React.FC = ({ teams, organizations }) => {
- Average Cost per Request + {t("usagePage.usagePageView.averageCostPerRequest")} $ {formatNumberWithCommas( @@ -637,7 +650,7 @@ const UsagePage: React.FC = ({ teams, organizations }) => { onClick={() => setShowTokenBreakdown(!showTokenBreakdown)} >
- Total Tokens + {t("usagePage.usagePageView.totalTokens")} {showTokenBreakdown ? ( ) : ( @@ -652,25 +665,25 @@ const UsagePage: React.FC = ({ teams, organizations }) => { {showTokenBreakdown && ( - Input Tokens + {t("usagePage.usagePageView.inputTokens")} {(userSpendData.metadata?.total_prompt_tokens || 0).toLocaleString()} - Output Tokens + {t("usagePage.usagePageView.outputTokens")} {userSpendData.metadata?.total_completion_tokens?.toLocaleString() || 0} - Cache Read Tokens + {t("usagePage.usagePageView.cacheReadTokens")} {userSpendData.metadata?.total_cache_read_input_tokens?.toLocaleString() || 0} - Cache Write Tokens + {t("usagePage.usagePageView.cacheWriteTokens")} {userSpendData.metadata?.total_cache_creation_input_tokens?.toLocaleString() || 0} @@ -683,7 +696,7 @@ const UsagePage: React.FC = ({ teams, organizations }) => { {/* Daily Spend Chart */} - Daily Spend + {t("usagePage.usagePageView.dailySpend")} {loading ? ( ) : ( @@ -702,12 +715,28 @@ const UsagePage: React.FC = ({ teams, organizations }) => {

{data.date}

- Spend: ${formatNumberWithCommas(data.metrics.spend, 2)} + {t("usagePage.usagePageView.tooltipSpend", { + amount: formatNumberWithCommas(data.metrics.spend, 2), + })} +

+

+ {t("usagePage.usagePageView.tooltipRequests", { + count: data.metrics.api_requests, + })} +

+

+ {t("usagePage.usagePageView.tooltipSuccessful", { + count: data.metrics.successful_requests, + })} +

+

+ {t("usagePage.usagePageView.tooltipFailed", { + count: data.metrics.failed_requests, + })} +

+

+ {t("usagePage.usagePageView.tooltipTokens", { count: data.metrics.total_tokens })}

-

Requests: {data.metrics.api_requests}

-

Successful: {data.metrics.successful_requests}

-

Failed: {data.metrics.failed_requests}

-

Tokens: {data.metrics.total_tokens}

); }} @@ -718,7 +747,7 @@ const UsagePage: React.FC = ({ teams, organizations }) => { {/* Top API Keys */} - Top Virtual Keys + {t("usagePage.usagePageView.topVirtualKeys")} = ({ teams, organizations }) => { {/* Top Models */} - {modelViewType === "groups" ? "Top Public Model Names" : "Top Litellm Models"} + + {modelViewType === "groups" + ? t("usagePage.usagePageView.topPublicModelNames") + : t("usagePage.usagePageView.topLitellmModels")} +
= ({ teams, organizations }) => { }`} onClick={() => setModelViewType("groups")} > - Public Model Name + {t("usagePage.usagePageView.publicModelName")}
@@ -791,18 +824,30 @@ const UsagePage: React.FC = ({ teams, organizations }) => {

{data.key}

- Spend: ${formatNumberWithCommas(data.spend, 2)} + {t("usagePage.usagePageView.tooltipSpend", { + amount: formatNumberWithCommas(data.spend, 2), + })}

- Total Requests: {data.requests.toLocaleString()} + {t("usagePage.usagePageView.tooltipTotalRequests", { + count: data.requests.toLocaleString(), + })}

- Successful: {data.successful_requests.toLocaleString()} + {t("usagePage.usagePageView.tooltipSuccessful", { + count: data.successful_requests.toLocaleString(), + })}

- Failed: {data.failed_requests.toLocaleString()} + {t("usagePage.usagePageView.tooltipFailed", { + count: data.failed_requests.toLocaleString(), + })} +

+

+ {t("usagePage.usagePageView.tooltipTokens", { + count: data.tokens.toLocaleString(), + })}

-

Tokens: {data.tokens.toLocaleString()}

); }} @@ -905,12 +950,13 @@ const UsagePage: React.FC = ({ teams, organizations }) => { - When a reusable credential is used, it will appear as a tag prefixed with{" "} - Credential: - in this view. + }} + /> } closable @@ -979,7 +1025,7 @@ const UsagePage: React.FC = ({ teams, organizations }) => { }} dateRange={dateValue} selectedFilters={[]} - customTitle="Export Usage Data" + customTitle={t("usagePage.usagePageView.exportUsageDataTitle")} /> {/* AI Chat Panel */} diff --git a/ui/litellm-dashboard/src/locales/en.json b/ui/litellm-dashboard/src/locales/en.json index 9d2c6ca316b..efefacb1d74 100644 --- a/ui/litellm-dashboard/src/locales/en.json +++ b/ui/litellm-dashboard/src/locales/en.json @@ -4870,6 +4870,63 @@ } }, "usagePage": { + "usageAiChatPanel": { + "title": "Ask AI", + "subtitle": "Ask about your spend, models, keys, and trends", + "modelPlaceholder": "Select a model (optional, defaults to gpt-4o-mini)", + "emptyStateTitle": "Ask a question about your usage", + "emptyStateExample": "e.g. \"Which model costs me the most?\"", + "thinking": "Thinking...", + "inputPlaceholder": "Ask about your usage...", + "sendButton": "Send", + "clearChat": "Clear chat", + "enterToSend": "Enter to send", + "filterLabel": "Filter: {{filter}}", + "errorPrefix": "Error: {{message}}", + "fallbackError": "Failed to get response. Please try again." + }, + "usagePageView": { + "filterByUser": "Filter by user", + "selectUserPlaceholder": "Select user to filter...", + "noUsersFound": "No users found", + "tabCost": "Cost", + "tabModelActivity": "Model Activity", + "tabKeyActivity": "Key Activity", + "tabMcpServerActivity": "MCP Server Activity", + "tabEndpointActivity": "Endpoint Activity", + "askAiButton": "Ask AI", + "exportDataButton": "Export Data", + "projectSpend": "Project Spend", + "usageMetrics": "Usage Metrics", + "totalRequests": "Total Requests", + "successfulRequests": "Successful Requests", + "failedRequests": "Failed Requests", + "failedRequestsTooltip": "Includes requests that failed to route to a provider, tool usage failures, and other request errors where the provider cannot be determined.", + "averageCostPerRequest": "Average Cost per Request", + "totalTokens": "Total Tokens", + "inputTokens": "Input Tokens", + "outputTokens": "Output Tokens", + "cacheReadTokens": "Cache Read Tokens", + "cacheWriteTokens": "Cache Write Tokens", + "dailySpend": "Daily Spend", + "tooltipSpend": "Spend: ${{amount}}", + "tooltipRequests": "Requests: {{count}}", + "tooltipSuccessful": "Successful: {{count}}", + "tooltipFailed": "Failed: {{count}}", + "tooltipTokens": "Tokens: {{count}}", + "tooltipTotalRequests": "Total Requests: {{count}}", + "topVirtualKeys": "Top Virtual Keys", + "topPublicModelNames": "Top Public Model Names", + "topLitellmModels": "Top Litellm Models", + "publicModelName": "Public Model Name", + "litellmModelName": "Litellm Model Name", + "fetchingSpendData": "Currently fetching spend data: fetched {{currentPage}} / {{totalPages}} pages. Charts will update periodically as data loads. Moving off of this page will stop and reset this. To continue using the UI in the meantime, open a new tab.", + "partialData": "Showing partial data ({{currentPage}}/{{totalPages}} pages loaded)", + "stopButton": "Stop", + "credentialBannerMessage": "Reusable credentials are automatically tracked as tags", + "credentialBannerDescription": "When a reusable credential is used, it will appear as a tag prefixed with Credential: in this view.", + "exportUsageDataTitle": "Export Usage Data" + }, "endpointUsageBarChart": { "title": "Success vs Failed Requests by Endpoint" }, diff --git a/ui/litellm-dashboard/src/locales/zh-CN.json b/ui/litellm-dashboard/src/locales/zh-CN.json index 995f1febca3..72804fe37e1 100644 --- a/ui/litellm-dashboard/src/locales/zh-CN.json +++ b/ui/litellm-dashboard/src/locales/zh-CN.json @@ -4870,6 +4870,63 @@ } }, "usagePage": { + "usageAiChatPanel": { + "title": "询问 AI", + "subtitle": "询问您的花费、模型、密钥及趋势", + "modelPlaceholder": "选择模型(可选,默认使用 gpt-4o-mini)", + "emptyStateTitle": "就您的用量提问", + "emptyStateExample": "例如:\"哪个模型花费最多?\"", + "thinking": "思考中...", + "inputPlaceholder": "询问您的用量...", + "sendButton": "发送", + "clearChat": "清空对话", + "enterToSend": "按 Enter 发送", + "filterLabel": "筛选:{{filter}}", + "errorPrefix": "错误:{{message}}", + "fallbackError": "获取响应失败,请重试。" + }, + "usagePageView": { + "filterByUser": "按用户筛选", + "selectUserPlaceholder": "选择用户以筛选...", + "noUsersFound": "未找到用户", + "tabCost": "花费", + "tabModelActivity": "模型活动", + "tabKeyActivity": "密钥活动", + "tabMcpServerActivity": "MCP 服务器活动", + "tabEndpointActivity": "端点活动", + "askAiButton": "询问 AI", + "exportDataButton": "导出数据", + "projectSpend": "项目花费", + "usageMetrics": "用量指标", + "totalRequests": "总请求数", + "successfulRequests": "成功请求数", + "failedRequests": "失败请求数", + "failedRequestsTooltip": "包含路由到提供商失败的请求、工具调用失败以及无法确定提供商的其他请求错误。", + "averageCostPerRequest": "每次请求平均费用", + "totalTokens": "总 Token 数", + "inputTokens": "输入 Token", + "outputTokens": "输出 Token", + "cacheReadTokens": "缓存读取 Token", + "cacheWriteTokens": "缓存写入 Token", + "dailySpend": "每日花费", + "tooltipSpend": "花费:${{amount}}", + "tooltipRequests": "请求数:{{count}}", + "tooltipSuccessful": "成功:{{count}}", + "tooltipFailed": "失败:{{count}}", + "tooltipTokens": "Token 数:{{count}}", + "tooltipTotalRequests": "总请求数:{{count}}", + "topVirtualKeys": "虚拟密钥排行", + "topPublicModelNames": "公开模型名称排行", + "topLitellmModels": "LiteLLM 模型排行", + "publicModelName": "公开模型名称", + "litellmModelName": "LiteLLM 模型名称", + "fetchingSpendData": "正在获取花费数据:已获取 {{currentPage}} / {{totalPages}} 页。图表会随数据加载定期更新。离开本页将停止并重置加载。如需继续使用界面,请 新标签页打开。", + "partialData": "显示部分数据(已加载 {{currentPage}}/{{totalPages}} 页)", + "stopButton": "停止", + "credentialBannerMessage": "可复用凭据会自动作为标签进行追踪", + "credentialBannerDescription": "当可复用凭据被使用时,将以 Credential: 前缀的标签形式出现在此视图中。", + "exportUsageDataTitle": "导出用量数据" + }, "endpointUsageBarChart": { "title": "按端点统计成功与失败请求" },