feat(ui): i18n for usage and user agent activity views

This commit is contained in:
吹雪 2026-06-10 14:22:47 +09:00
parent 30a4169b08
commit 33ac749174
4 changed files with 188 additions and 68 deletions

View file

@ -13,6 +13,7 @@ import {
} from "@tremor/react";
import React, { useState, useEffect } from "react";
import { useTranslation, Trans } from "react-i18next";
import ViewUserSpend from "./view_user_spend";
import { ProxySettings } from "./user_dashboard";
@ -133,6 +134,7 @@ const isAdminOrAdminViewer = (role: string | null): boolean => {
};
const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, userID, keys, premiumUser }) => {
const { t } = useTranslation();
const currentDate = new Date();
const [keySpendData, setKeySpendData] = useState<any[]>([]);
const [topKeys, setTopKeys] = useState<any[]>([]);
@ -540,15 +542,15 @@ const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, use
return (
<div style={{ width: "100%" }} className="p-8">
<Card>
<Title>Database Query Limit Reached</Title>
<Title>{t("usage.dbLimitTitle")}</Title>
<Text className="mt-4">
SpendLogs in DB has {proxySettings.NUM_SPEND_LOGS_ROWS} rows.
{t("usage.dbLimitRows", { count: proxySettings.NUM_SPEND_LOGS_ROWS })}
<br></br>
Please follow our guide to view usage when SpendLogs has more than 1M rows.
{t("usage.dbLimitGuide")}
</Text>
<Button className="mt-4">
<a href="https://docs.litellm.ai/docs/proxy/spending_monitoring" target="_blank">
View Usage Guide
{t("usage.viewUsageGuide")}
</a>
</Button>
</Card>
@ -560,13 +562,13 @@ const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, use
<div style={{ width: "100%" }} className="p-8">
<TabGroup>
<TabList className="mt-2">
<Tab>All Up</Tab>
<Tab>{t("usage.tabAllUp")}</Tab>
{isAdminOrAdminViewer(userRole) ? (
<>
<Tab>Team Based Usage</Tab>
<Tab>Customer Usage</Tab>
<Tab>Tag Based Usage</Tab>
<Tab>{t("usage.tabTeamBased")}</Tab>
<Tab>{t("usage.tabCustomer")}</Tab>
<Tab>{t("usage.tabTagBased")}</Tab>
</>
) : (
<>
@ -578,22 +580,24 @@ const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, use
<TabPanel>
<TabGroup>
<TabList variant="solid" className="mt-1">
<Tab>Cost</Tab>
<Tab>Activity</Tab>
<Tab>{t("usage.tabCost")}</Tab>
<Tab>{t("usage.tabActivity")}</Tab>
</TabList>
<TabPanels>
<TabPanel>
<Grid numItems={2} className="gap-2 h-[100vh] w-full">
<Col numColSpan={2}>
<Text className="text-tremor-default text-tremor-content dark:text-dark-tremor-content mb-2 mt-2 text-lg">
Project Spend {new Date().toLocaleString("default", { month: "long" })} 1 -{" "}
{new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).getDate()}
{t("usage.projectSpend", {
month: new Date().toLocaleString("default", { month: "long" }),
days: new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).getDate(),
})}
</Text>
<ViewUserSpend userSpend={totalMonthlySpend} selectedTeam={null} userMaxBudget={null} />
</Col>
<Col numColSpan={2}>
<Card>
<Title>Monthly Spend</Title>
<Title>{t("usage.monthlySpend")}</Title>
<BarChart
data={keySpendData}
index="date"
@ -608,13 +612,13 @@ const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, use
</Col>
<Col numColSpan={1}>
<Card className="h-full">
<Title>Top Virtual Keys</Title>
<Title>{t("usage.topVirtualKeys")}</Title>
<TopKeyView topKeys={topKeys} teams={null} topKeysLimit={5} setTopKeysLimit={() => {}} />
</Card>
</Col>
<Col numColSpan={1}>
<Card className="h-full">
<Title>Top Models</Title>
<Title>{t("usage.topModels")}</Title>
<BarChart
className="mt-4 h-40"
data={topModels}
@ -632,7 +636,7 @@ const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, use
<Col numColSpan={1}></Col>
<Col numColSpan={2}>
<Card className="mb-2">
<Title>Spend by Provider</Title>
<Title>{t("usage.spendByProvider")}</Title>
<>
<Grid numItems={2}>
<Col numColSpan={1}>
@ -650,8 +654,8 @@ const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, use
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>Provider</TableHeaderCell>
<TableHeaderCell>Spend</TableHeaderCell>
<TableHeaderCell>{t("usage.colProvider")}</TableHeaderCell>
<TableHeaderCell>{t("usage.colSpend")}</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
@ -660,7 +664,7 @@ const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, use
<TableCell>{provider.provider}</TableCell>
<TableCell>
{parseFloat(provider.spend.toFixed(2)) < 0.00001
? "less than 0.00"
? t("usage.lessThanZero")
: formatNumberWithCommas(provider.spend, 2)}
</TableCell>
</TableRow>
@ -677,11 +681,11 @@ const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, use
<TabPanel>
<Grid numItems={1} className="gap-2 h-[75vh] w-full">
<Card>
<Title>All Up</Title>
<Title>{t("usage.tabAllUp")}</Title>
<Grid numItems={2}>
<Col>
<Subtitle style={{ fontSize: "15px", fontWeight: "normal", color: "#535452" }}>
API Requests {valueFormatterNumbers(globalActivity.sum_api_requests)}
{t("usage.apiRequests", { count: valueFormatterNumbers(globalActivity.sum_api_requests) })}
</Subtitle>
<AreaChart
className="h-40"
@ -695,7 +699,7 @@ const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, use
</Col>
<Col>
<Subtitle style={{ fontSize: "15px", fontWeight: "normal", color: "#535452" }}>
Tokens {valueFormatterNumbers(globalActivity.sum_total_tokens)}
{t("usage.tokens", { count: valueFormatterNumbers(globalActivity.sum_total_tokens) })}
</Subtitle>
<BarChart
className="h-40"
@ -717,7 +721,9 @@ const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, use
<Grid numItems={2}>
<Col>
<Subtitle style={{ fontSize: "15px", fontWeight: "normal", color: "#535452" }}>
API Requests {valueFormatterNumbers(globalActivity.sum_api_requests)}
{t("usage.apiRequests", {
count: valueFormatterNumbers(globalActivity.sum_api_requests),
})}
</Subtitle>
<AreaChart
className="h-40"
@ -731,7 +737,7 @@ const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, use
</Col>
<Col>
<Subtitle style={{ fontSize: "15px", fontWeight: "normal", color: "#535452" }}>
Tokens {valueFormatterNumbers(globalActivity.sum_total_tokens)}
{t("usage.tokens", { count: valueFormatterNumbers(globalActivity.sum_total_tokens) })}
</Subtitle>
<BarChart
className="h-40"
@ -756,11 +762,11 @@ const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, use
<Grid numItems={2} className="gap-2 h-[75vh] w-full">
<Col numColSpan={2}>
<Card className="mb-2">
<Title>Total Spend Per Team</Title>
<Title>{t("usage.totalSpendPerTeam")}</Title>
<BarList data={totalSpendPerTeam} />
</Card>
<Card>
<Title>Daily Spend Per Team</Title>
<Title>{t("usage.dailySpendPerTeam")}</Title>
<BarChart
className="h-72"
data={teamSpendData}
@ -777,10 +783,14 @@ const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, use
</TabPanel>
<TabPanel>
<p className="mb-2 text-gray-500 italic text-[12px]">
Customers of your LLM API calls. Tracked when a `user` param is passed in your LLM calls{" "}
<a className="text-blue-500" href="https://docs.litellm.ai/docs/proxy/users" target="_blank">
docs here
</a>
<Trans
i18nKey="usage.customersDesc"
components={{
docsLink: (
<a className="text-blue-500" href="https://docs.litellm.ai/docs/proxy/users" target="_blank" />
),
}}
/>
</p>
<Grid numItems={2}>
<Col>
@ -793,7 +803,7 @@ const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, use
/>
</Col>
<Col>
<Text>Select Key</Text>
<Text>{t("usage.selectKey")}</Text>
<Select defaultValue="all-keys">
<SelectItem
key="all-keys"
@ -802,7 +812,7 @@ const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, use
updateEndUserData(dateValue.from, dateValue.to, null);
}}
>
All Keys
{t("usage.allKeys")}
</SelectItem>
{keys?.map((key: any, index: number) => {
if (key && key["key_alias"] !== null && key["key_alias"].length > 0) {
@ -828,9 +838,9 @@ const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, use
<Table className="max-h-[70vh] min-h-[500px]">
<TableHead>
<TableRow>
<TableHeaderCell>Customer</TableHeaderCell>
<TableHeaderCell>Spend</TableHeaderCell>
<TableHeaderCell>Total Events</TableHeaderCell>
<TableHeaderCell>{t("usage.colCustomer")}</TableHeaderCell>
<TableHeaderCell>{t("usage.colSpend")}</TableHeaderCell>
<TableHeaderCell>{t("usage.colTotalEvents")}</TableHeaderCell>
</TableRow>
</TableHead>
@ -868,7 +878,7 @@ const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, use
value={"all-tags"}
onClick={() => setSelectedTags(["all-tags"])}
>
All Tags
{t("usage.allTags")}
</MultiSelectItem>
{allTagNames &&
allTagNames
@ -890,7 +900,7 @@ const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, use
value={"all-tags"}
onClick={() => setSelectedTags(["all-tags"])}
>
All Tags
{t("usage.allTags")}
</MultiSelectItem>
{allTagNames &&
allTagNames
@ -903,7 +913,7 @@ const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, use
// @ts-ignore
disabled={true}
>
✨ {tag} (Enterprise only Feature)
✨ {tag} {t("usage.enterpriseOnlyFeature")}
</SelectItem>
);
})}
@ -915,16 +925,20 @@ const UsagePage: React.FC<UsagePageProps> = ({ accessToken, token, userRole, use
<Grid numItems={2} className="gap-2 h-[75vh] w-full mb-4">
<Col numColSpan={2}>
<Card>
<Title>Spend Per Tag</Title>
<Title>{t("usage.spendPerTag")}</Title>
<Text>
Get Started by Tracking cost per tag{" "}
<a
className="text-blue-500"
href="https://docs.litellm.ai/docs/proxy/cost_tracking"
target="_blank"
>
here
</a>
<Trans
i18nKey="usage.spendPerTagDesc"
components={{
docsLink: (
<a
className="text-blue-500"
href="https://docs.litellm.ai/docs/proxy/cost_tracking"
target="_blank"
/>
),
}}
/>
</Text>
<BarChart
className="h-72"

View file

@ -1,4 +1,5 @@
import React, { useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import {
Card,
Title,
@ -62,6 +63,7 @@ interface UserAgentActivityProps {
}
const UserAgentActivity: React.FC<UserAgentActivityProps> = ({ accessToken, userRole, dateValue, onDateChange }) => {
const { t } = useTranslation();
// Maximum number of categories to show in charts to prevent color palette overflow
const MAX_CATEGORIES = 10;
@ -375,16 +377,16 @@ const UserAgentActivity: React.FC<UserAgentActivityProps> = ({ accessToken, user
<div className="space-y-6">
<div className="flex justify-between items-start">
<div>
<Title>Summary by User Agent</Title>
<Subtitle>Performance metrics for different user agents</Subtitle>
<Title>{t("userAgentActivity.summaryTitle")}</Title>
<Subtitle>{t("userAgentActivity.summarySubtitle")}</Subtitle>
</div>
{/* User Agent Filter */}
<div className="w-96">
<Text className="text-sm font-medium block mb-2">Filter by User Agents</Text>
<Text className="text-sm font-medium block mb-2">{t("userAgentActivity.filterLabel")}</Text>
<Select
mode="multiple"
placeholder="All User Agents"
placeholder={t("userAgentActivity.filterPlaceholder")}
value={selectedTags}
onChange={setSelectedTags}
style={{ width: "100%" }}
@ -425,15 +427,15 @@ const UserAgentActivity: React.FC<UserAgentActivityProps> = ({ accessToken, user
</Tooltip>
<div className="mt-4 space-y-3">
<div>
<Text className="text-sm text-gray-600">Success Requests</Text>
<Text className="text-sm text-gray-600">{t("userAgentActivity.successRequests")}</Text>
<Metric className="text-lg">{formatAbbreviatedNumber(tag.successful_requests)}</Metric>
</div>
<div>
<Text className="text-sm text-gray-600">Total Tokens</Text>
<Text className="text-sm text-gray-600">{t("userAgentActivity.totalTokens")}</Text>
<Metric className="text-lg">{formatAbbreviatedNumber(tag.total_tokens)}</Metric>
</div>
<div>
<Text className="text-sm text-gray-600">Total Cost</Text>
<Text className="text-sm text-gray-600">{t("userAgentActivity.totalCost")}</Text>
<Metric className="text-lg">${formatAbbreviatedNumber(tag.total_spend, 4)}</Metric>
</div>
</div>
@ -443,18 +445,18 @@ const UserAgentActivity: React.FC<UserAgentActivityProps> = ({ accessToken, user
{/* Fill remaining slots if less than 4 agents */}
{Array.from({ length: Math.max(0, 4 - (summaryData.results || []).length) }).map((_, index) => (
<Card key={`empty-${index}`}>
<Title>No Data</Title>
<Title>{t("common.noData")}</Title>
<div className="mt-4 space-y-3">
<div>
<Text className="text-sm text-gray-600">Success Requests</Text>
<Text className="text-sm text-gray-600">{t("userAgentActivity.successRequests")}</Text>
<Metric className="text-lg">-</Metric>
</div>
<div>
<Text className="text-sm text-gray-600">Total Tokens</Text>
<Text className="text-sm text-gray-600">{t("userAgentActivity.totalTokens")}</Text>
<Metric className="text-lg">-</Metric>
</div>
<div>
<Text className="text-sm text-gray-600">Total Cost</Text>
<Text className="text-sm text-gray-600">{t("userAgentActivity.totalCost")}</Text>
<Metric className="text-lg">-</Metric>
</div>
</div>
@ -469,29 +471,29 @@ const UserAgentActivity: React.FC<UserAgentActivityProps> = ({ accessToken, user
<Card>
<TabGroup>
<TabList className="mb-6">
<Tab>DAU/WAU/MAU</Tab>
<Tab>Per User Usage (Last 30 Days)</Tab>
<Tab>{t("userAgentActivity.tabDauWauMau")}</Tab>
<Tab>{t("userAgentActivity.tabPerUserUsage")}</Tab>
</TabList>
<TabPanels>
{/* DAU/WAU/MAU Tab Panel */}
<TabPanel>
<div className="mb-6">
<Title>DAU, WAU & MAU per Agent</Title>
<Subtitle>Active users across different time periods</Subtitle>
<Title>{t("userAgentActivity.dauWauMauTitle")}</Title>
<Subtitle>{t("userAgentActivity.dauWauMauSubtitle")}</Subtitle>
</div>
<TabGroup>
<TabList className="mb-6">
<Tab>DAU</Tab>
<Tab>WAU</Tab>
<Tab>MAU</Tab>
<Tab>{t("userAgentActivity.tabDau")}</Tab>
<Tab>{t("userAgentActivity.tabWau")}</Tab>
<Tab>{t("userAgentActivity.tabMau")}</Tab>
</TabList>
<TabPanels>
<TabPanel>
<div className="mb-4">
<Title className="text-lg">Daily Active Users - Last 7 Days</Title>
<Title className="text-lg">{t("userAgentActivity.dailyActiveUsers")}</Title>
</div>
{dauLoading ? (
<ChartLoader isDateChanging={false} />
@ -510,7 +512,7 @@ const UserAgentActivity: React.FC<UserAgentActivityProps> = ({ accessToken, user
<TabPanel>
<div className="mb-4">
<Title className="text-lg">Weekly Active Users - Last 7 Weeks</Title>
<Title className="text-lg">{t("userAgentActivity.weeklyActiveUsers")}</Title>
</div>
{wauLoading ? (
<ChartLoader isDateChanging={false} />
@ -529,7 +531,7 @@ const UserAgentActivity: React.FC<UserAgentActivityProps> = ({ accessToken, user
<TabPanel>
<div className="mb-4">
<Title className="text-lg">Monthly Active Users - Last 7 Months</Title>
<Title className="text-lg">{t("userAgentActivity.monthlyActiveUsers")}</Title>
</div>
{mauLoading ? (
<ChartLoader isDateChanging={false} />

View file

@ -1863,5 +1863,57 @@
"resetSuccess": "Theme settings reset to default!",
"resetFailed": "Failed to reset theme settings",
"resetToDefault": "Reset to Default"
},
"usage": {
"tabAllUp": "All Up",
"tabTeamBased": "Team Based Usage",
"tabCustomer": "Customer Usage",
"tabTagBased": "Tag Based Usage",
"tabCost": "Cost",
"tabActivity": "Activity",
"dbLimitTitle": "Database Query Limit Reached",
"dbLimitRows": "SpendLogs in DB has {{count}} rows.",
"dbLimitGuide": "Please follow our guide to view usage when SpendLogs has more than 1M rows.",
"viewUsageGuide": "View Usage Guide",
"projectSpend": "Project Spend {{month}} 1 - {{days}}",
"monthlySpend": "Monthly Spend",
"topVirtualKeys": "Top Virtual Keys",
"topModels": "Top Models",
"spendByProvider": "Spend by Provider",
"colProvider": "Provider",
"colSpend": "Spend",
"lessThanZero": "less than 0.00",
"apiRequests": "API Requests {{count}}",
"tokens": "Tokens {{count}}",
"totalSpendPerTeam": "Total Spend Per Team",
"dailySpendPerTeam": "Daily Spend Per Team",
"customersDesc": "Customers of your LLM API calls. Tracked when a `user` param is passed in your LLM calls <docsLink>docs here</docsLink>",
"selectKey": "Select Key",
"allKeys": "All Keys",
"colCustomer": "Customer",
"colTotalEvents": "Total Events",
"allTags": "All Tags",
"enterpriseOnlyFeature": "(Enterprise only Feature)",
"spendPerTag": "Spend Per Tag",
"spendPerTagDesc": "Get Started by Tracking cost per tag <docsLink>here</docsLink>"
},
"userAgentActivity": {
"summaryTitle": "Summary by User Agent",
"summarySubtitle": "Performance metrics for different user agents",
"filterLabel": "Filter by User Agents",
"filterPlaceholder": "All User Agents",
"successRequests": "Success Requests",
"totalTokens": "Total Tokens",
"totalCost": "Total Cost",
"tabDauWauMau": "DAU/WAU/MAU",
"tabPerUserUsage": "Per User Usage (Last 30 Days)",
"dauWauMauTitle": "DAU, WAU & MAU per Agent",
"dauWauMauSubtitle": "Active users across different time periods",
"tabDau": "DAU",
"tabWau": "WAU",
"tabMau": "MAU",
"dailyActiveUsers": "Daily Active Users - Last 7 Days",
"weeklyActiveUsers": "Weekly Active Users - Last 7 Weeks",
"monthlyActiveUsers": "Monthly Active Users - Last 7 Months"
}
}

View file

@ -1863,5 +1863,57 @@
"resetSuccess": "主题设置已重置为默认!",
"resetFailed": "重置主题设置失败",
"resetToDefault": "重置为默认"
},
"usage": {
"tabAllUp": "总览",
"tabTeamBased": "按团队用量",
"tabCustomer": "客户用量",
"tabTagBased": "按标签用量",
"tabCost": "花费",
"tabActivity": "活动",
"dbLimitTitle": "已达数据库查询限制",
"dbLimitRows": "数据库中的 SpendLogs 已有 {{count}} 行。",
"dbLimitGuide": "当 SpendLogs 超过 100 万行时,请参阅我们的指南查看用量。",
"viewUsageGuide": "查看用量指南",
"projectSpend": "项目花费 {{month}} 1 - {{days}}",
"monthlySpend": "月度花费",
"topVirtualKeys": "热门虚拟密钥",
"topModels": "热门模型",
"spendByProvider": "按提供商花费",
"colProvider": "提供商",
"colSpend": "花费",
"lessThanZero": "小于 0.00",
"apiRequests": "API 请求数 {{count}}",
"tokens": "Token 数 {{count}}",
"totalSpendPerTeam": "各团队总花费",
"dailySpendPerTeam": "各团队每日花费",
"customersDesc": "您的 LLM API 调用的客户。在 LLM 调用中传入 `user` 参数时进行追踪 <docsLink>文档</docsLink>",
"selectKey": "选择密钥",
"allKeys": "所有密钥",
"colCustomer": "客户",
"colTotalEvents": "总事件数",
"allTags": "所有标签",
"enterpriseOnlyFeature": "(仅限企业版功能)",
"spendPerTag": "按标签花费",
"spendPerTagDesc": "从按标签追踪成本开始 <docsLink>此处</docsLink>"
},
"userAgentActivity": {
"summaryTitle": "按 User Agent 汇总",
"summarySubtitle": "不同 User Agent 的性能指标",
"filterLabel": "按 User Agent 筛选",
"filterPlaceholder": "所有 User Agent",
"successRequests": "成功请求数",
"totalTokens": "总 Token 数",
"totalCost": "总费用",
"tabDauWauMau": "DAU/WAU/MAU",
"tabPerUserUsage": "每用户用量(近 30 天)",
"dauWauMauTitle": "每个 Agent 的 DAU、WAU 与 MAU",
"dauWauMauSubtitle": "不同时间维度的活跃用户数",
"tabDau": "DAU",
"tabWau": "WAU",
"tabMau": "MAU",
"dailyActiveUsers": "每日活跃用户 - 近 7 天",
"weeklyActiveUsers": "每周活跃用户 - 近 7 周",
"monthlyActiveUsers": "每月活跃用户 - 近 7 个月"
}
}