mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for usage page batch 1
This commit is contained in:
parent
08f5303270
commit
e4f613b5df
9 changed files with 408 additions and 110 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { BarChart, Card, Title } from "@tremor/react";
|
||||
import { CustomLegend, CustomTooltip } from "@/components/common_components/chartUtils";
|
||||
import { MetricWithMetadata } from "../../../types";
|
||||
|
|
@ -8,6 +9,7 @@ interface EndpointUsageBarChartProps {
|
|||
}
|
||||
|
||||
const EndpointUsageBarChart: React.FC<EndpointUsageBarChartProps> = ({ endpointData }) => {
|
||||
const { t } = useTranslation();
|
||||
const dataToUse = endpointData || {};
|
||||
|
||||
// Transform endpoint data into chart format
|
||||
|
|
@ -28,7 +30,7 @@ const EndpointUsageBarChart: React.FC<EndpointUsageBarChartProps> = ({ endpointD
|
|||
return (
|
||||
<Card>
|
||||
<div className="flex justify-between items-center">
|
||||
<Title>Success vs Failed Requests by Endpoint</Title>
|
||||
<Title>{t("usagePage.endpointUsageBarChart.title")}</Title>
|
||||
<CustomLegend
|
||||
categories={["metrics.successful_requests", "metrics.failed_requests"]}
|
||||
colors={["green", "red"]}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Card, LineChart, Title } from "@tremor/react";
|
||||
import { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DailyData } from "../../../types";
|
||||
|
||||
interface EndpointUsageLineChartProps {
|
||||
|
|
@ -43,6 +44,7 @@ function transformDailyDataToChart(dailyData: DailyData[]): Array<Record<string,
|
|||
}
|
||||
|
||||
export function EndpointUsageLineChart({ dailyData, endpointData }: EndpointUsageLineChartProps) {
|
||||
const { t } = useTranslation();
|
||||
const chartData = useMemo(() => {
|
||||
if (!dailyData?.results || dailyData.results.length === 0) {
|
||||
return [];
|
||||
|
|
@ -64,7 +66,7 @@ export function EndpointUsageLineChart({ dailyData, endpointData }: EndpointUsag
|
|||
return (
|
||||
<Card className="mb-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<Title>Endpoint Usage Trends</Title>
|
||||
<Title>{t("usagePage.endpointUsageLineChart.title")}</Title>
|
||||
</div>
|
||||
<LineChart
|
||||
className="h-80"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import useTeams from "@/app/(dashboard)/hooks/useTeams";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { formatNumberWithCommas } from "@/utils/dataUtils";
|
||||
import {
|
||||
BarChart,
|
||||
|
|
@ -101,6 +102,7 @@ const ENTITY_FETCH_FNS: Record<EntityType, (...args: any[]) => Promise<any>> = {
|
|||
};
|
||||
|
||||
const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, entityId, entityList, dateValue }) => {
|
||||
const { t } = useTranslation();
|
||||
const { teams } = useTeams();
|
||||
const [selectedTags, setSelectedTags] = useState<string[]>([]);
|
||||
const [topKeysLimit, setTopKeysLimit] = useState<number>(5);
|
||||
|
|
@ -386,11 +388,11 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
|||
};
|
||||
|
||||
const getFilterLabel = (entityType: string) => {
|
||||
return `Filter by ${entityType}`;
|
||||
return t("usagePage.entityUsage.filterLabel", { entityType });
|
||||
};
|
||||
|
||||
const getFilterPlaceholder = (entityType: string) => {
|
||||
return `Select ${entityType} to filter...`;
|
||||
return t("usagePage.entityUsage.filterPlaceholder", { entityType });
|
||||
};
|
||||
|
||||
const capitalizedEntityLabel = entityType.charAt(0).toUpperCase() + entityType.slice(1);
|
||||
|
|
@ -406,16 +408,20 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
|||
<div className="flex items-center justify-between">
|
||||
<span>
|
||||
<LoadingOutlined spin className="mr-2" />
|
||||
Currently fetching spend data: fetched {progress.currentPage} / {progress.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,{" "}
|
||||
<a href={window.location.href} target="_blank" rel="noopener noreferrer">
|
||||
open a new tab <ExportOutlined />
|
||||
</a>
|
||||
.
|
||||
<Trans
|
||||
i18nKey="usagePage.entityUsage.fetchingSpendData"
|
||||
values={{ currentPage: progress.currentPage, totalPages: progress.totalPages }}
|
||||
components={{
|
||||
a: (
|
||||
<a href={window.location.href} target="_blank" rel="noopener noreferrer">
|
||||
<ExportOutlined />
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
<Button type="primary" danger onClick={cancel}>
|
||||
Stop
|
||||
{t("usagePage.entityUsage.stopButton")}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -428,7 +434,10 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
|||
className="mb-2"
|
||||
message={
|
||||
<span>
|
||||
Showing partial data ({progress.currentPage}/{progress.totalPages} pages loaded)
|
||||
{t("usagePage.entityUsage.partialData", {
|
||||
currentPage: progress.currentPage,
|
||||
totalPages: progress.totalPages,
|
||||
})}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
|
|
@ -442,16 +451,20 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
|||
<div className="flex items-center justify-between">
|
||||
<span>
|
||||
<LoadingOutlined spin className="mr-2" />
|
||||
Currently fetching agent data: fetched {agentProgress.currentPage} / {agentProgress.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,{" "}
|
||||
<a href={window.location.href} target="_blank" rel="noopener noreferrer">
|
||||
open a new tab <ExportOutlined />
|
||||
</a>
|
||||
.
|
||||
<Trans
|
||||
i18nKey="usagePage.entityUsage.fetchingAgentData"
|
||||
values={{ currentPage: agentProgress.currentPage, totalPages: agentProgress.totalPages }}
|
||||
components={{
|
||||
a: (
|
||||
<a href={window.location.href} target="_blank" rel="noopener noreferrer">
|
||||
<ExportOutlined />
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
<Button type="primary" danger onClick={agentCancel}>
|
||||
Stop
|
||||
{t("usagePage.entityUsage.stopButton")}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -464,14 +477,17 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
|||
className="mb-2"
|
||||
message={
|
||||
<span>
|
||||
Showing partial agent data ({agentProgress.currentPage}/{agentProgress.totalPages} pages loaded)
|
||||
{t("usagePage.entityUsage.partialAgentData", {
|
||||
currentPage: agentProgress.currentPage,
|
||||
totalPages: agentProgress.totalPages,
|
||||
})}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{entityType === "team" && (
|
||||
<div className="mb-4">
|
||||
<Text className="mb-2">Filter by team</Text>
|
||||
<Text className="mb-2">{t("usagePage.entityUsage.filterByTeam")}</Text>
|
||||
<TeamMultiSelect value={selectedTags} onChange={setSelectedTags} />
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -490,11 +506,15 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
|||
/>
|
||||
<TabGroup>
|
||||
<TabList variant="solid" className="mt-1">
|
||||
<Tab>Cost</Tab>
|
||||
<Tab>{entityType === "agent" ? "Request / Token Consumption" : "Model Activity"}</Tab>
|
||||
{entityType === "team" ? <Tab>Agent Activity</Tab> : <></>}
|
||||
<Tab>Key Activity</Tab>
|
||||
<Tab>Endpoint Activity</Tab>
|
||||
<Tab>{t("usagePage.entityUsage.tabCost")}</Tab>
|
||||
<Tab>
|
||||
{entityType === "agent"
|
||||
? t("usagePage.entityUsage.tabRequestTokenConsumption")
|
||||
: t("usagePage.entityUsage.tabModelActivity")}
|
||||
</Tab>
|
||||
{entityType === "team" ? <Tab>{t("usagePage.entityUsage.tabAgentActivity")}</Tab> : <></>}
|
||||
<Tab>{t("usagePage.entityUsage.tabKeyActivity")}</Tab>
|
||||
<Tab>{t("usagePage.entityUsage.tabEndpointActivity")}</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
|
|
@ -502,34 +522,34 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
|||
{/* Total Spend Card */}
|
||||
<Col numColSpan={2}>
|
||||
<Card>
|
||||
<Title>{capitalizedEntityLabel} Spend Overview</Title>
|
||||
<Title>{t("usagePage.entityUsage.spendOverview", { entity: capitalizedEntityLabel })}</Title>
|
||||
<Grid numItems={5} className="gap-4 mt-4">
|
||||
<Card>
|
||||
<Title>Total Spend</Title>
|
||||
<Title>{t("usagePage.entityUsage.totalSpend")}</Title>
|
||||
<Text className="text-2xl font-bold mt-2">
|
||||
${formatNumberWithCommas(spendData.metadata.total_spend, 2)}
|
||||
</Text>
|
||||
</Card>
|
||||
<Card>
|
||||
<Title>Total Requests</Title>
|
||||
<Title>{t("usagePage.entityUsage.totalRequests")}</Title>
|
||||
<Text className="text-2xl font-bold mt-2">
|
||||
{spendData.metadata.total_api_requests.toLocaleString()}
|
||||
</Text>
|
||||
</Card>
|
||||
<Card>
|
||||
<Title>Successful Requests</Title>
|
||||
<Title>{t("usagePage.entityUsage.successfulRequests")}</Title>
|
||||
<Text className="text-2xl font-bold mt-2 text-green-600">
|
||||
{spendData.metadata.total_successful_requests.toLocaleString()}
|
||||
</Text>
|
||||
</Card>
|
||||
<Card>
|
||||
<Title>Failed Requests</Title>
|
||||
<Title>{t("usagePage.entityUsage.failedRequests")}</Title>
|
||||
<Text className="text-2xl font-bold mt-2 text-red-600">
|
||||
{spendData.metadata.total_failed_requests.toLocaleString()}
|
||||
</Text>
|
||||
</Card>
|
||||
<Card>
|
||||
<Title>Total Tokens</Title>
|
||||
<Title>{t("usagePage.entityUsage.totalTokens")}</Title>
|
||||
<Text className="text-2xl font-bold mt-2">
|
||||
{spendData.metadata.total_tokens.toLocaleString()}
|
||||
</Text>
|
||||
|
|
@ -541,7 +561,7 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
|||
{/* Daily Spend Chart */}
|
||||
<Col numColSpan={2}>
|
||||
<Card>
|
||||
<Title>Daily Spend</Title>
|
||||
<Title>{t("usagePage.entityUsage.dailySpend")}</Title>
|
||||
<BarChart
|
||||
data={[...spendData.results].sort(
|
||||
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(),
|
||||
|
|
@ -559,16 +579,37 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
|||
return (
|
||||
<div className="bg-white p-4 shadow-lg rounded-lg border">
|
||||
<p className="font-bold">{data.date}</p>
|
||||
<p className="text-cyan-500">Total Spend: ${formatNumberWithCommas(data.metrics.spend, 2)}</p>
|
||||
<p className="text-gray-600">Total Requests: {data.metrics.api_requests}</p>
|
||||
<p className="text-gray-600">Successful: {data.metrics.successful_requests}</p>
|
||||
<p className="text-gray-600">Failed: {data.metrics.failed_requests}</p>
|
||||
<p className="text-gray-600">Total Tokens: {data.metrics.total_tokens}</p>
|
||||
<p className="text-cyan-500">
|
||||
{t("usagePage.entityUsage.tooltipTotalSpend", {
|
||||
amount: formatNumberWithCommas(data.metrics.spend, 2),
|
||||
})}
|
||||
</p>
|
||||
<p className="text-gray-600">
|
||||
Total {capitalizedEntityLabel}s: {entityCount}
|
||||
{t("usagePage.entityUsage.tooltipTotalRequests", {
|
||||
count: data.metrics.api_requests,
|
||||
})}
|
||||
</p>
|
||||
<p className="text-gray-600">
|
||||
{t("usagePage.entityUsage.tooltipSuccessful", {
|
||||
count: data.metrics.successful_requests,
|
||||
})}
|
||||
</p>
|
||||
<p className="text-gray-600">
|
||||
{t("usagePage.entityUsage.tooltipFailed", { count: data.metrics.failed_requests })}
|
||||
</p>
|
||||
<p className="text-gray-600">
|
||||
{t("usagePage.entityUsage.tooltipTotalTokens", { count: data.metrics.total_tokens })}
|
||||
</p>
|
||||
<p className="text-gray-600">
|
||||
{t("usagePage.entityUsage.tooltipTotalEntities", {
|
||||
entity: capitalizedEntityLabel,
|
||||
count: entityCount,
|
||||
})}
|
||||
</p>
|
||||
<div className="mt-2 border-t pt-2">
|
||||
<p className="font-semibold">Spend by {capitalizedEntityLabel}:</p>
|
||||
<p className="font-semibold">
|
||||
{t("usagePage.entityUsage.tooltipSpendByEntity", { entity: capitalizedEntityLabel })}
|
||||
</p>
|
||||
{Object.entries(data.breakdown.entities || {})
|
||||
.sort(([, a], [, b]) => {
|
||||
const spendA = (a as EntityMetrics).metrics.spend;
|
||||
|
|
@ -586,7 +627,9 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
|||
);
|
||||
})}
|
||||
{entityCount > 5 && (
|
||||
<p className="text-sm text-gray-500 italic">...and {entityCount - 5} more</p>
|
||||
<p className="text-sm text-gray-500 italic">
|
||||
{t("usagePage.entityUsage.tooltipAndMore", { count: entityCount - 5 })}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -601,16 +644,21 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
|||
<Card>
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<Title>Spend Per {capitalizedEntityLabel}</Title>
|
||||
<Subtitle className="text-xs">Showing Top 5 by Spend</Subtitle>
|
||||
<Title>{t("usagePage.entityUsage.spendPerEntity", { entity: capitalizedEntityLabel })}</Title>
|
||||
<Subtitle className="text-xs">{t("usagePage.entityUsage.showingTop5BySpend")}</Subtitle>
|
||||
<div className="flex items-center text-sm text-gray-500">
|
||||
<span>Get Started by Tracking cost per {capitalizedEntityLabel} </span>
|
||||
<a
|
||||
href="https://docs.litellm.ai/docs/proxy/enterprise#spend-tracking"
|
||||
className="text-blue-500 hover:text-blue-700 ml-1"
|
||||
>
|
||||
here
|
||||
</a>
|
||||
<Trans
|
||||
i18nKey="usagePage.entityUsage.getStartedTracking"
|
||||
values={{ entity: capitalizedEntityLabel }}
|
||||
components={{
|
||||
a: (
|
||||
<a
|
||||
href="https://docs.litellm.ai/docs/proxy/enterprise#spend-tracking"
|
||||
className="text-blue-500 hover:text-blue-700 ml-1"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Grid numItems={2} className="gap-6">
|
||||
|
|
@ -631,13 +679,31 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
|||
return (
|
||||
<div className="bg-white p-4 shadow-lg rounded-lg border">
|
||||
<p className="font-bold">{data.metadata.alias}</p>
|
||||
<p className="text-cyan-500">Spend: ${formatNumberWithCommas(data.metrics.spend, 4)}</p>
|
||||
<p className="text-gray-600">Requests: {data.metrics.api_requests.toLocaleString()}</p>
|
||||
<p className="text-green-600">
|
||||
Successful: {data.metrics.successful_requests.toLocaleString()}
|
||||
<p className="text-cyan-500">
|
||||
{t("usagePage.entityUsage.tooltipSpend", {
|
||||
amount: formatNumberWithCommas(data.metrics.spend, 4),
|
||||
})}
|
||||
</p>
|
||||
<p className="text-gray-600">
|
||||
{t("usagePage.entityUsage.tooltipRequests", {
|
||||
count: data.metrics.api_requests.toLocaleString(),
|
||||
})}
|
||||
</p>
|
||||
<p className="text-green-600">
|
||||
{t("usagePage.entityUsage.tooltipSuccessful", {
|
||||
count: data.metrics.successful_requests.toLocaleString(),
|
||||
})}
|
||||
</p>
|
||||
<p className="text-red-600">
|
||||
{t("usagePage.entityUsage.tooltipFailed", {
|
||||
count: data.metrics.failed_requests.toLocaleString(),
|
||||
})}
|
||||
</p>
|
||||
<p className="text-gray-600">
|
||||
{t("usagePage.entityUsage.tooltipTokens", {
|
||||
count: data.metrics.total_tokens.toLocaleString(),
|
||||
})}
|
||||
</p>
|
||||
<p className="text-red-600">Failed: {data.metrics.failed_requests.toLocaleString()}</p>
|
||||
<p className="text-gray-600">Tokens: {data.metrics.total_tokens.toLocaleString()}</p>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
|
|
@ -649,10 +715,14 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
|||
<TableHead>
|
||||
<TableRow>
|
||||
<TableHeaderCell>{capitalizedEntityLabel}</TableHeaderCell>
|
||||
<TableHeaderCell>Spend</TableHeaderCell>
|
||||
<TableHeaderCell className="text-green-600">Successful</TableHeaderCell>
|
||||
<TableHeaderCell className="text-red-600">Failed</TableHeaderCell>
|
||||
<TableHeaderCell>Tokens</TableHeaderCell>
|
||||
<TableHeaderCell>{t("usagePage.entityUsage.colSpend")}</TableHeaderCell>
|
||||
<TableHeaderCell className="text-green-600">
|
||||
{t("usagePage.entityUsage.colSuccessful")}
|
||||
</TableHeaderCell>
|
||||
<TableHeaderCell className="text-red-600">
|
||||
{t("usagePage.entityUsage.colFailed")}
|
||||
</TableHeaderCell>
|
||||
<TableHeaderCell>{t("usagePage.entityUsage.colTokens")}</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
|
|
@ -683,7 +753,7 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
|||
{/* Top API Keys */}
|
||||
<Col numColSpan={1}>
|
||||
<Card>
|
||||
<Title>Top Virtual Keys</Title>
|
||||
<Title>{t("usagePage.entityUsage.topVirtualKeys")}</Title>
|
||||
<TopKeyView
|
||||
topKeys={getTopAPIKeys()}
|
||||
teams={null}
|
||||
|
|
@ -697,7 +767,11 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
|||
{/* Top Models */}
|
||||
<Col numColSpan={1}>
|
||||
<Card>
|
||||
<Title>{entityType === "agent" ? "Top Agents" : "Top Models"}</Title>
|
||||
<Title>
|
||||
{entityType === "agent"
|
||||
? t("usagePage.entityUsage.topAgents")
|
||||
: t("usagePage.entityUsage.topModels")}
|
||||
</Title>
|
||||
<TopModelView
|
||||
topModels={getTopModels()}
|
||||
topModelsLimit={topModelsLimit}
|
||||
|
|
@ -710,7 +784,7 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
|||
{entityType === "team" && (
|
||||
<Col numColSpan={2}>
|
||||
<Card>
|
||||
<Title>Top Agents Driving Spend</Title>
|
||||
<Title>{t("usagePage.entityUsage.topAgentsDrivingSpend")}</Title>
|
||||
<TopModelView
|
||||
topModels={getTopAgents()}
|
||||
topModelsLimit={topAgentsLimit}
|
||||
|
|
@ -724,7 +798,7 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
|||
<Col numColSpan={2}>
|
||||
<Card>
|
||||
<div className="flex flex-col space-y-4">
|
||||
<Title>Provider Usage</Title>
|
||||
<Title>{t("usagePage.entityUsage.providerUsage")}</Title>
|
||||
<Grid numItems={2}>
|
||||
<Col numColSpan={1}>
|
||||
<DonutChart
|
||||
|
|
@ -740,11 +814,15 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
|||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableHeaderCell>Provider</TableHeaderCell>
|
||||
<TableHeaderCell>Spend</TableHeaderCell>
|
||||
<TableHeaderCell className="text-green-600">Successful</TableHeaderCell>
|
||||
<TableHeaderCell className="text-red-600">Failed</TableHeaderCell>
|
||||
<TableHeaderCell>Tokens</TableHeaderCell>
|
||||
<TableHeaderCell>{t("usagePage.entityUsage.colProvider")}</TableHeaderCell>
|
||||
<TableHeaderCell>{t("usagePage.entityUsage.colSpend")}</TableHeaderCell>
|
||||
<TableHeaderCell className="text-green-600">
|
||||
{t("usagePage.entityUsage.colSuccessful")}
|
||||
</TableHeaderCell>
|
||||
<TableHeaderCell className="text-red-600">
|
||||
{t("usagePage.entityUsage.colFailed")}
|
||||
</TableHeaderCell>
|
||||
<TableHeaderCell>{t("usagePage.entityUsage.colTokens")}</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
|
|
@ -755,7 +833,9 @@ const EntityUsage: React.FC<EntityUsageProps> = ({ accessToken, entityType, enti
|
|||
{provider.provider && (
|
||||
<img
|
||||
src={getProviderLogoAndName(provider.provider).logo}
|
||||
alt={`${provider.provider} logo`}
|
||||
alt={t("usagePage.entityUsage.providerLogoAlt", {
|
||||
provider: provider.provider,
|
||||
})}
|
||||
className="w-4 h-4"
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { formatNumberWithCommas } from "@/utils/dataUtils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import {
|
||||
Card,
|
||||
|
|
@ -35,6 +36,7 @@ interface SpendByProviderProps {
|
|||
}
|
||||
|
||||
const SpendByProvider: React.FC<SpendByProviderProps> = ({ loading, isDateChanging, providerSpend }) => {
|
||||
const { t } = useTranslation();
|
||||
const [includeZeroSpend, setIncludeZeroSpend] = useState(false);
|
||||
const [includeUnknown, setIncludeUnknown] = useState(false);
|
||||
|
||||
|
|
@ -58,16 +60,16 @@ const SpendByProvider: React.FC<SpendByProviderProps> = ({ loading, isDateChangi
|
|||
return (
|
||||
<Card className="h-full">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Title>Spend by Provider</Title>
|
||||
<Title>{t("usagePage.spendByProvider.title")}</Title>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<label className="text-sm text-gray-700">Show Zero Spend</label>
|
||||
<label className="text-sm text-gray-700">{t("usagePage.spendByProvider.showZeroSpend")}</label>
|
||||
<Switch checked={includeZeroSpend} onChange={setIncludeZeroSpend} />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1">
|
||||
<label className="text-sm text-gray-700">Show Unknown</label>
|
||||
<Tooltip title="Requests that failed to route to a provider">
|
||||
<label className="text-sm text-gray-700">{t("usagePage.spendByProvider.showUnknown")}</label>
|
||||
<Tooltip title={t("usagePage.spendByProvider.unknownTooltip")}>
|
||||
<InfoCircleOutlined className="text-gray-400 hover:text-gray-600" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
|
@ -93,11 +95,13 @@ const SpendByProvider: React.FC<SpendByProviderProps> = ({ loading, isDateChangi
|
|||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableHeaderCell>Provider</TableHeaderCell>
|
||||
<TableHeaderCell>Spend</TableHeaderCell>
|
||||
<TableHeaderCell className="text-green-600">Successful</TableHeaderCell>
|
||||
<TableHeaderCell className="text-red-600">Failed</TableHeaderCell>
|
||||
<TableHeaderCell>Tokens</TableHeaderCell>
|
||||
<TableHeaderCell>{t("usagePage.spendByProvider.colProvider")}</TableHeaderCell>
|
||||
<TableHeaderCell>{t("usagePage.spendByProvider.colSpend")}</TableHeaderCell>
|
||||
<TableHeaderCell className="text-green-600">
|
||||
{t("usagePage.spendByProvider.colSuccessful")}
|
||||
</TableHeaderCell>
|
||||
<TableHeaderCell className="text-red-600">{t("usagePage.spendByProvider.colFailed")}</TableHeaderCell>
|
||||
<TableHeaderCell>{t("usagePage.spendByProvider.colTokens")}</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/outline";
|
||||
import { BarChart, Button } from "@tremor/react";
|
||||
import { Segmented, Tooltip } from "antd";
|
||||
|
|
@ -19,6 +20,7 @@ interface TopKeyViewProps {
|
|||
}
|
||||
|
||||
const TopKeyView: React.FC<TopKeyViewProps> = ({ topKeys, teams, showTags = false, topKeysLimit, setTopKeysLimit }) => {
|
||||
const { t } = useTranslation();
|
||||
const { accessToken, userRole, userId: userID, premiumUser } = useAuthorized();
|
||||
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
|
||||
const [selectedKey, setSelectedKey] = useState<string | null>(null);
|
||||
|
|
@ -81,7 +83,7 @@ const TopKeyView: React.FC<TopKeyViewProps> = ({ topKeys, teams, showTags = fals
|
|||
// Define columns for the table view
|
||||
const baseColumns = [
|
||||
{
|
||||
header: "Key ID",
|
||||
header: t("usagePage.topKeyView.colKeyId"),
|
||||
accessorKey: "api_key",
|
||||
cell: (info: any) => (
|
||||
<div className="overflow-hidden">
|
||||
|
|
@ -99,14 +101,14 @@ const TopKeyView: React.FC<TopKeyViewProps> = ({ topKeys, teams, showTags = fals
|
|||
),
|
||||
},
|
||||
{
|
||||
header: "Key Alias",
|
||||
header: t("usagePage.topKeyView.colKeyAlias"),
|
||||
accessorKey: "key_alias",
|
||||
cell: (info: any) => info.getValue() || "-",
|
||||
},
|
||||
];
|
||||
|
||||
const tagsColumn = {
|
||||
header: "Tags",
|
||||
header: t("usagePage.topKeyView.colTags"),
|
||||
accessorKey: "tags",
|
||||
cell: (info: any) => {
|
||||
const tags = info.getValue() as TagUsage[] | undefined;
|
||||
|
|
@ -130,10 +132,10 @@ const TopKeyView: React.FC<TopKeyViewProps> = ({ topKeys, teams, showTags = fals
|
|||
title={
|
||||
<div>
|
||||
<div>
|
||||
<span className="text-gray-300">Tag Name:</span> {tag.tag}
|
||||
<span className="text-gray-300">{t("usagePage.topKeyView.tagName")}</span> {tag.tag}
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-300">Spend:</span>{" "}
|
||||
<span className="text-gray-300">{t("usagePage.topKeyView.tagSpend")}</span>{" "}
|
||||
{tag.usage > 0 && tag.usage < 0.01 ? "<$0.01" : `$${formatNumberWithCommas(tag.usage, 2)}`}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -146,7 +148,7 @@ const TopKeyView: React.FC<TopKeyViewProps> = ({ topKeys, teams, showTags = fals
|
|||
<button
|
||||
onClick={() => toggleTagsExpansion(apiKey)}
|
||||
className="ml-1 p-1 hover:bg-gray-200 rounded-full transition-colors"
|
||||
title={isExpanded ? "Show fewer tags" : "Show all tags"}
|
||||
title={isExpanded ? t("usagePage.topKeyView.showFewerTags") : t("usagePage.topKeyView.showAllTags")}
|
||||
>
|
||||
{isExpanded ? (
|
||||
<ChevronUpIcon className="h-3 w-3 text-gray-500" />
|
||||
|
|
@ -162,7 +164,7 @@ const TopKeyView: React.FC<TopKeyViewProps> = ({ topKeys, teams, showTags = fals
|
|||
};
|
||||
|
||||
const spendColumn = {
|
||||
header: "Spend (USD)",
|
||||
header: t("usagePage.topKeyView.colSpendUsd"),
|
||||
accessorKey: "spend",
|
||||
cell: (info: any) => {
|
||||
const value = info.getValue();
|
||||
|
|
@ -195,13 +197,13 @@ const TopKeyView: React.FC<TopKeyViewProps> = ({ topKeys, teams, showTags = fals
|
|||
onClick={() => setViewMode("table")}
|
||||
className={`px-3 py-1 text-sm rounded-md ${viewMode === "table" ? "bg-blue-100 text-blue-700" : "bg-gray-100 text-gray-700"}`}
|
||||
>
|
||||
Table View
|
||||
{t("usagePage.topKeyView.tableView")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode("chart")}
|
||||
className={`px-3 py-1 text-sm rounded-md ${viewMode === "chart" ? "bg-blue-100 text-blue-700" : "bg-gray-100 text-gray-700"}`}
|
||||
>
|
||||
Chart View
|
||||
{t("usagePage.topKeyView.chartView")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -228,15 +230,15 @@ const TopKeyView: React.FC<TopKeyViewProps> = ({ topKeys, teams, showTags = fals
|
|||
<div className="relative z-50 p-3 bg-black/90 shadow-lg rounded-lg text-white max-w-xs">
|
||||
<div className="space-y-1.5">
|
||||
<div className="text-sm">
|
||||
<span className="text-gray-300">Key Alias: </span>
|
||||
<span className="text-gray-300">{t("usagePage.topKeyView.tooltipKeyAlias")} </span>
|
||||
<span className="font-mono text-gray-100 break-all">{item?.key_alias}</span>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<span className="text-gray-300">Key ID: </span>
|
||||
<span className="text-gray-300">{t("usagePage.topKeyView.tooltipKeyId")} </span>
|
||||
<span className="font-mono text-gray-100 break-all">{item?.api_key}</span>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<span className="text-gray-300">Spend: </span>
|
||||
<span className="text-gray-300">{t("usagePage.topKeyView.tooltipSpend")} </span>
|
||||
<span className="text-white font-medium">${formatNumberWithCommas(item?.spend, 2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { BarChart } from "@tremor/react";
|
||||
import { Segmented } from "antd";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { formatNumberWithCommas } from "../../../../utils/dataUtils";
|
||||
import { DataTable } from "../../../view_logs/table";
|
||||
|
||||
|
|
@ -19,16 +20,17 @@ interface TopModelViewProps {
|
|||
}
|
||||
|
||||
export default function TopModelView({ topModels, topModelsLimit, setTopModelsLimit }: TopModelViewProps) {
|
||||
const { t } = useTranslation();
|
||||
const [modelViewMode, setModelViewMode] = useState<"chart" | "table">("table");
|
||||
|
||||
const columns = [
|
||||
{
|
||||
header: "Model",
|
||||
header: t("usagePage.topModelView.colModel"),
|
||||
accessorKey: "key",
|
||||
cell: (info: any) => info.getValue() || "-",
|
||||
},
|
||||
{
|
||||
header: "Spend (USD)",
|
||||
header: t("usagePage.topModelView.colSpendUsd"),
|
||||
accessorKey: "spend",
|
||||
cell: (info: any) => {
|
||||
const value = info.getValue();
|
||||
|
|
@ -36,17 +38,17 @@ export default function TopModelView({ topModels, topModelsLimit, setTopModelsLi
|
|||
},
|
||||
},
|
||||
{
|
||||
header: "Successful",
|
||||
header: t("usagePage.topModelView.colSuccessful"),
|
||||
accessorKey: "successful_requests",
|
||||
cell: (info: any) => <span className="text-green-600">{info.getValue()?.toLocaleString() || 0}</span>,
|
||||
},
|
||||
{
|
||||
header: "Failed",
|
||||
header: t("usagePage.topModelView.colFailed"),
|
||||
accessorKey: "failed_requests",
|
||||
cell: (info: any) => <span className="text-red-600">{info.getValue()?.toLocaleString() || 0}</span>,
|
||||
},
|
||||
{
|
||||
header: "Tokens",
|
||||
header: t("usagePage.topModelView.colTokens"),
|
||||
accessorKey: "tokens",
|
||||
cell: (info: any) => info.getValue()?.toLocaleString() || 0,
|
||||
},
|
||||
|
|
@ -71,13 +73,13 @@ export default function TopModelView({ topModels, topModelsLimit, setTopModelsLi
|
|||
onClick={() => setModelViewMode("table")}
|
||||
className={`px-3 py-1 text-sm rounded-md ${modelViewMode === "table" ? "bg-blue-100 text-blue-700" : "bg-gray-100 text-gray-700"}`}
|
||||
>
|
||||
Table View
|
||||
{t("usagePage.topModelView.tableView")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setModelViewMode("chart")}
|
||||
className={`px-3 py-1 text-sm rounded-md ${modelViewMode === "chart" ? "bg-blue-100 text-blue-700" : "bg-gray-100 text-gray-700"}`}
|
||||
>
|
||||
Chart View
|
||||
{t("usagePage.topModelView.chartView")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ import { formatNumberWithCommas } from "@/utils/dataUtils";
|
|||
import { BarChart, Card, Title } from "@tremor/react";
|
||||
import { Table } from "antd";
|
||||
import type { ColumnsType } from "antd/es/table";
|
||||
import React, { useState } from "react";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { TFunction } from "i18next";
|
||||
import { TopModelData } from "../types";
|
||||
|
||||
interface KeyModelUsageViewProps {
|
||||
|
|
@ -13,33 +15,33 @@ const VISIBLE_ROWS = 5;
|
|||
// antd Table with size="small" has a row height of ~39px
|
||||
const ANTD_SMALL_TABLE_ROW_HEIGHT = 39;
|
||||
|
||||
const columns: ColumnsType<TopModelData> = [
|
||||
const getColumns = (t: TFunction): ColumnsType<TopModelData> => [
|
||||
{
|
||||
title: "Model",
|
||||
title: t("usagePage.keyModelUsageView.colModel"),
|
||||
dataIndex: "model",
|
||||
key: "model",
|
||||
render: (value) => value || "-",
|
||||
},
|
||||
{
|
||||
title: "Spend (USD)",
|
||||
title: t("usagePage.keyModelUsageView.colSpendUsd"),
|
||||
dataIndex: "spend",
|
||||
key: "spend",
|
||||
render: (value) => `$${formatNumberWithCommas(value, 2)}`,
|
||||
},
|
||||
{
|
||||
title: "Successful",
|
||||
title: t("usagePage.keyModelUsageView.colSuccessful"),
|
||||
dataIndex: "successful_requests",
|
||||
key: "successful_requests",
|
||||
render: (value) => <span className="text-green-600">{value?.toLocaleString() || 0}</span>,
|
||||
},
|
||||
{
|
||||
title: "Failed",
|
||||
title: t("usagePage.keyModelUsageView.colFailed"),
|
||||
dataIndex: "failed_requests",
|
||||
key: "failed_requests",
|
||||
render: (value) => <span className="text-red-600">{value?.toLocaleString() || 0}</span>,
|
||||
},
|
||||
{
|
||||
title: "Tokens",
|
||||
title: t("usagePage.keyModelUsageView.colTokens"),
|
||||
dataIndex: "tokens",
|
||||
key: "tokens",
|
||||
render: (value) => value?.toLocaleString() || 0,
|
||||
|
|
@ -47,6 +49,8 @@ const columns: ColumnsType<TopModelData> = [
|
|||
];
|
||||
|
||||
const KeyModelUsageView: React.FC<KeyModelUsageViewProps> = ({ topModels }) => {
|
||||
const { t } = useTranslation();
|
||||
const columns = useMemo(() => getColumns(t), [t]);
|
||||
const [viewMode, setViewMode] = useState<"chart" | "table">("table");
|
||||
|
||||
if (topModels.length === 0) {
|
||||
|
|
@ -56,19 +60,19 @@ const KeyModelUsageView: React.FC<KeyModelUsageViewProps> = ({ topModels }) => {
|
|||
return (
|
||||
<Card className="mt-4">
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<Title>Model Usage</Title>
|
||||
<Title>{t("usagePage.keyModelUsageView.title")}</Title>
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
onClick={() => setViewMode("table")}
|
||||
className={`px-3 py-1 text-sm rounded-md ${viewMode === "table" ? "bg-blue-100 text-blue-700" : "bg-gray-100 text-gray-700"}`}
|
||||
>
|
||||
Table
|
||||
{t("usagePage.keyModelUsageView.tableView")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode("chart")}
|
||||
className={`px-3 py-1 text-sm rounded-md ${viewMode === "chart" ? "bg-blue-100 text-blue-700" : "bg-gray-100 text-gray-700"}`}
|
||||
>
|
||||
Chart
|
||||
{t("usagePage.keyModelUsageView.chartView")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4868,5 +4868,106 @@
|
|||
"invalidPercentageRange": "Percentage must be between 0% and 1000%",
|
||||
"invalidFixedAmount": "Fixed amount must be non-negative"
|
||||
}
|
||||
},
|
||||
"usagePage": {
|
||||
"endpointUsageBarChart": {
|
||||
"title": "Success vs Failed Requests by Endpoint"
|
||||
},
|
||||
"endpointUsageLineChart": {
|
||||
"title": "Endpoint Usage Trends"
|
||||
},
|
||||
"entityUsage": {
|
||||
"filterLabel": "Filter by {{entityType}}",
|
||||
"filterPlaceholder": "Select {{entityType}} to filter...",
|
||||
"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, <a>open a new tab</a>.",
|
||||
"partialData": "Showing partial data ({{currentPage}}/{{totalPages}} pages loaded)",
|
||||
"fetchingAgentData": "Currently fetching agent 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, <a>open a new tab</a>.",
|
||||
"partialAgentData": "Showing partial agent data ({{currentPage}}/{{totalPages}} pages loaded)",
|
||||
"filterByTeam": "Filter by team",
|
||||
"stopButton": "Stop",
|
||||
"tabCost": "Cost",
|
||||
"tabRequestTokenConsumption": "Request / Token Consumption",
|
||||
"tabModelActivity": "Model Activity",
|
||||
"tabAgentActivity": "Agent Activity",
|
||||
"tabKeyActivity": "Key Activity",
|
||||
"tabEndpointActivity": "Endpoint Activity",
|
||||
"spendOverview": "{{entity}} Spend Overview",
|
||||
"totalSpend": "Total Spend",
|
||||
"totalRequests": "Total Requests",
|
||||
"successfulRequests": "Successful Requests",
|
||||
"failedRequests": "Failed Requests",
|
||||
"totalTokens": "Total Tokens",
|
||||
"dailySpend": "Daily Spend",
|
||||
"tooltipTotalSpend": "Total Spend: ${{amount}}",
|
||||
"tooltipTotalRequests": "Total Requests: {{count}}",
|
||||
"tooltipSuccessful": "Successful: {{count}}",
|
||||
"tooltipFailed": "Failed: {{count}}",
|
||||
"tooltipTotalTokens": "Total Tokens: {{count}}",
|
||||
"tooltipTotalEntities": "Total {{entity}}s: {{count}}",
|
||||
"tooltipSpendByEntity": "Spend by {{entity}}:",
|
||||
"tooltipAndMore": "...and {{count}} more",
|
||||
"spendPerEntity": "Spend Per {{entity}}",
|
||||
"showingTop5BySpend": "Showing Top 5 by Spend",
|
||||
"getStartedTracking": "Get Started by Tracking cost per {{entity}} <a>here</a>",
|
||||
"tooltipSpend": "Spend: ${{amount}}",
|
||||
"tooltipRequests": "Requests: {{count}}",
|
||||
"tooltipTokens": "Tokens: {{count}}",
|
||||
"colSpend": "Spend",
|
||||
"colSuccessful": "Successful",
|
||||
"colFailed": "Failed",
|
||||
"colTokens": "Tokens",
|
||||
"colProvider": "Provider",
|
||||
"topVirtualKeys": "Top Virtual Keys",
|
||||
"topAgents": "Top Agents",
|
||||
"topModels": "Top Models",
|
||||
"topAgentsDrivingSpend": "Top Agents Driving Spend",
|
||||
"providerUsage": "Provider Usage",
|
||||
"providerLogoAlt": "{{provider}} logo"
|
||||
},
|
||||
"spendByProvider": {
|
||||
"title": "Spend by Provider",
|
||||
"showZeroSpend": "Show Zero Spend",
|
||||
"showUnknown": "Show Unknown",
|
||||
"unknownTooltip": "Requests that failed to route to a provider",
|
||||
"colProvider": "Provider",
|
||||
"colSpend": "Spend",
|
||||
"colSuccessful": "Successful",
|
||||
"colFailed": "Failed",
|
||||
"colTokens": "Tokens"
|
||||
},
|
||||
"topKeyView": {
|
||||
"colKeyId": "Key ID",
|
||||
"colKeyAlias": "Key Alias",
|
||||
"colTags": "Tags",
|
||||
"colSpendUsd": "Spend (USD)",
|
||||
"tagName": "Tag Name:",
|
||||
"tagSpend": "Spend:",
|
||||
"showFewerTags": "Show fewer tags",
|
||||
"showAllTags": "Show all tags",
|
||||
"tooltipKeyAlias": "Key Alias:",
|
||||
"tooltipKeyId": "Key ID:",
|
||||
"tooltipSpend": "Spend:",
|
||||
"tableView": "Table View",
|
||||
"chartView": "Chart View"
|
||||
},
|
||||
"topModelView": {
|
||||
"colModel": "Model",
|
||||
"colSpendUsd": "Spend (USD)",
|
||||
"colSuccessful": "Successful",
|
||||
"colFailed": "Failed",
|
||||
"colTokens": "Tokens",
|
||||
"tableView": "Table View",
|
||||
"chartView": "Chart View"
|
||||
},
|
||||
"keyModelUsageView": {
|
||||
"title": "Model Usage",
|
||||
"colModel": "Model",
|
||||
"colSpendUsd": "Spend (USD)",
|
||||
"colSuccessful": "Successful",
|
||||
"colFailed": "Failed",
|
||||
"colTokens": "Tokens",
|
||||
"tableView": "Table",
|
||||
"chartView": "Chart"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4868,5 +4868,106 @@
|
|||
"invalidPercentageRange": "百分比必须在 0% 到 1000% 之间",
|
||||
"invalidFixedAmount": "固定金额不能为负数"
|
||||
}
|
||||
},
|
||||
"usagePage": {
|
||||
"endpointUsageBarChart": {
|
||||
"title": "按端点统计成功与失败请求"
|
||||
},
|
||||
"endpointUsageLineChart": {
|
||||
"title": "端点用量趋势"
|
||||
},
|
||||
"entityUsage": {
|
||||
"filterLabel": "按 {{entityType}} 筛选",
|
||||
"filterPlaceholder": "选择 {{entityType}} 进行筛选...",
|
||||
"fetchingSpendData": "正在获取花费数据:已获取 {{currentPage}} / {{totalPages}} 页。图表会随数据加载定期更新。离开本页将停止并重置加载。如需继续使用界面,请 <a>新标签页打开</a>。",
|
||||
"partialData": "显示部分数据(已加载 {{currentPage}}/{{totalPages}} 页)",
|
||||
"fetchingAgentData": "正在获取 Agent 数据:已获取 {{currentPage}} / {{totalPages}} 页。图表会随数据加载定期更新。离开本页将停止并重置加载。如需继续使用界面,请 <a>新标签页打开</a>。",
|
||||
"partialAgentData": "显示部分 Agent 数据(已加载 {{currentPage}}/{{totalPages}} 页)",
|
||||
"filterByTeam": "按团队筛选",
|
||||
"stopButton": "停止",
|
||||
"tabCost": "花费",
|
||||
"tabRequestTokenConsumption": "请求 / Token 消耗",
|
||||
"tabModelActivity": "模型活动",
|
||||
"tabAgentActivity": "Agent 活动",
|
||||
"tabKeyActivity": "密钥活动",
|
||||
"tabEndpointActivity": "端点活动",
|
||||
"spendOverview": "{{entity}} 花费概览",
|
||||
"totalSpend": "总花费",
|
||||
"totalRequests": "总请求数",
|
||||
"successfulRequests": "成功请求数",
|
||||
"failedRequests": "失败请求数",
|
||||
"totalTokens": "总 Token 数",
|
||||
"dailySpend": "每日花费",
|
||||
"tooltipTotalSpend": "总花费:${{amount}}",
|
||||
"tooltipTotalRequests": "总请求数:{{count}}",
|
||||
"tooltipSuccessful": "成功:{{count}}",
|
||||
"tooltipFailed": "失败:{{count}}",
|
||||
"tooltipTotalTokens": "总 Token 数:{{count}}",
|
||||
"tooltipTotalEntities": "{{entity}} 总数:{{count}}",
|
||||
"tooltipSpendByEntity": "按 {{entity}} 统计花费:",
|
||||
"tooltipAndMore": "...以及另外 {{count}} 个",
|
||||
"spendPerEntity": "每个 {{entity}} 的花费",
|
||||
"showingTop5BySpend": "显示花费前 5",
|
||||
"getStartedTracking": "开始按 {{entity}} 跟踪成本,<a>点击这里</a>",
|
||||
"tooltipSpend": "花费:${{amount}}",
|
||||
"tooltipRequests": "请求数:{{count}}",
|
||||
"tooltipTokens": "Token 数:{{count}}",
|
||||
"colSpend": "花费",
|
||||
"colSuccessful": "成功",
|
||||
"colFailed": "失败",
|
||||
"colTokens": "Token",
|
||||
"colProvider": "提供商",
|
||||
"topVirtualKeys": "热门虚拟密钥",
|
||||
"topAgents": "热门 Agent",
|
||||
"topModels": "热门模型",
|
||||
"topAgentsDrivingSpend": "花费最高的 Agent",
|
||||
"providerUsage": "提供商用量",
|
||||
"providerLogoAlt": "{{provider}} 图标"
|
||||
},
|
||||
"spendByProvider": {
|
||||
"title": "按提供商统计花费",
|
||||
"showZeroSpend": "显示零花费",
|
||||
"showUnknown": "显示未知",
|
||||
"unknownTooltip": "路由到提供商失败的请求",
|
||||
"colProvider": "提供商",
|
||||
"colSpend": "花费",
|
||||
"colSuccessful": "成功",
|
||||
"colFailed": "失败",
|
||||
"colTokens": "Token"
|
||||
},
|
||||
"topKeyView": {
|
||||
"colKeyId": "密钥 ID",
|
||||
"colKeyAlias": "密钥别名",
|
||||
"colTags": "标签",
|
||||
"colSpendUsd": "花费(USD)",
|
||||
"tagName": "标签名:",
|
||||
"tagSpend": "花费:",
|
||||
"showFewerTags": "显示更少标签",
|
||||
"showAllTags": "显示全部标签",
|
||||
"tooltipKeyAlias": "密钥别名:",
|
||||
"tooltipKeyId": "密钥 ID:",
|
||||
"tooltipSpend": "花费:",
|
||||
"tableView": "表格视图",
|
||||
"chartView": "图表视图"
|
||||
},
|
||||
"topModelView": {
|
||||
"colModel": "模型",
|
||||
"colSpendUsd": "花费(USD)",
|
||||
"colSuccessful": "成功",
|
||||
"colFailed": "失败",
|
||||
"colTokens": "Token",
|
||||
"tableView": "表格视图",
|
||||
"chartView": "图表视图"
|
||||
},
|
||||
"keyModelUsageView": {
|
||||
"title": "模型用量",
|
||||
"colModel": "模型",
|
||||
"colSpendUsd": "花费(USD)",
|
||||
"colSuccessful": "成功",
|
||||
"colFailed": "失败",
|
||||
"colTokens": "Token",
|
||||
"tableView": "表格",
|
||||
"chartView": "图表"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue