feat(ui): i18n for activity metrics, pass-through endpoints, and agents panel

This commit is contained in:
吹雪 2026-06-10 13:00:51 +09:00
parent 1746865a7b
commit b2abda084f
5 changed files with 314 additions and 113 deletions

View file

@ -3,6 +3,7 @@ import { resolveTeamAliasFromTeamID } from "@/utils/teamUtils";
import { AreaChart, BarChart, Card, Grid, Text, Title } from "@tremor/react";
import { Collapse } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { CustomLegend, CustomTooltip } from "./common_components/chartUtils";
import { Team } from "./key_team_helpers/key_list";
import KeyModelUsageView from "./UsagePage/components/KeyModelUsageView";
@ -23,47 +24,61 @@ const ModelSection = ({
metrics: ModelActivityData;
hidePromptCachingMetrics?: boolean;
}) => {
const { t } = useTranslation();
return (
<div className="space-y-2">
{/* Summary Cards */}
<Grid numItems={4} className="gap-4">
<Card>
<Text>Total Requests</Text>
<Text>{t("activityMetrics.totalRequests")}</Text>
<Title>{metrics.total_requests.toLocaleString()}</Title>
</Card>
<Card>
<Text>Total Successful Requests</Text>
<Text>{t("activityMetrics.totalSuccessfulRequests")}</Text>
<Title>{metrics.total_successful_requests.toLocaleString()}</Title>
</Card>
<Card>
<Text>Total Tokens</Text>
<Text>
{t("common.total")} {t("activityMetrics.tokens")}
</Text>
<Title>{metrics.total_tokens.toLocaleString()}</Title>
<Text>{Math.round(metrics.total_tokens / metrics.total_successful_requests)} avg per successful request</Text>
<Text>
{Math.round(metrics.total_tokens / metrics.total_successful_requests)}{" "}
{t("activityMetrics.avgPerSuccessfulRequest")}
</Text>
</Card>
<Card>
<Text>Total Spend</Text>
<Text>{t("activityMetrics.totalSpend")}</Text>
<Title>${formatNumberWithCommas(metrics.total_spend, 2)}</Title>
<Text>
${formatNumberWithCommas(metrics.total_spend / metrics.total_successful_requests, 3)} per successful request
${formatNumberWithCommas(metrics.total_spend / metrics.total_successful_requests, 3)}{" "}
{t("activityMetrics.perSuccessfulRequest")}
</Text>
</Card>
</Grid>
{metrics.top_api_keys && metrics.top_api_keys.length > 0 && (
<Card className="mt-4">
<Title>Top Virtual Keys by Spend</Title>
<Title>{t("activityMetrics.topVirtualKeysBySpend")}</Title>
<div className="mt-3">
<div className="grid grid-cols-1 gap-2">
{metrics.top_api_keys.map((keyData, index) => (
<div key={keyData.api_key} className="flex justify-between items-center p-3 bg-gray-50 rounded-lg">
<div>
<Text className="font-medium">{keyData.key_alias || `${keyData.api_key.substring(0, 10)}...`}</Text>
{keyData.team_id && <Text className="text-xs text-gray-500">Team: {keyData.team_id}</Text>}
{keyData.team_id && (
<Text className="text-xs text-gray-500">
{t("activityMetrics.teamLabel", { teamId: keyData.team_id })}
</Text>
)}
</div>
<div className="text-right">
<Text className="font-medium">${formatNumberWithCommas(keyData.spend, 2)}</Text>
<Text className="text-xs text-gray-500">
{keyData.requests.toLocaleString()} requests | {keyData.tokens.toLocaleString()} tokens
{t("activityMetrics.requestsAndTokens", {
requests: keyData.requests.toLocaleString(),
tokens: keyData.tokens.toLocaleString(),
})}
</Text>
</div>
</div>
@ -78,7 +93,7 @@ const ModelSection = ({
{/* Spend per day - Full width card */}
<Card className="mt-4">
<div className="flex justify-between items-center">
<Title>Spend per day</Title>
<Title>{t("activityMetrics.spendPerDay")}</Title>
<CustomLegend categories={["metrics.spend"]} colors={["green"]} />
</div>
<BarChart
@ -96,7 +111,9 @@ const ModelSection = ({
<Grid numItems={2} className="gap-4 mt-4">
<Card>
<div className="flex justify-between items-center">
<Title>Total Tokens</Title>
<Title>
{t("common.total")} {t("activityMetrics.tokens")}
</Title>
<CustomLegend
categories={["metrics.prompt_tokens", "metrics.completion_tokens", "metrics.total_tokens"]}
colors={["blue", "cyan", "indigo"]}
@ -116,7 +133,7 @@ const ModelSection = ({
<Card>
<div className="flex justify-between items-center">
<Title>Requests per day</Title>
<Title>{t("activityMetrics.requestsPerDay")}</Title>
<CustomLegend categories={["metrics.api_requests"]} colors={["blue"]} />
</div>
<BarChart
@ -133,7 +150,7 @@ const ModelSection = ({
<Card>
<div className="flex justify-between items-center">
<Title>Success vs Failed Requests</Title>
<Title>{t("activityMetrics.successVsFailedRequests")}</Title>
<CustomLegend
categories={["metrics.successful_requests", "metrics.failed_requests"]}
colors={["green", "red"]}
@ -154,15 +171,23 @@ const ModelSection = ({
{!hidePromptCachingMetrics && (
<Card>
<div className="flex justify-between items-center">
<Title>Prompt Caching Metrics</Title>
<Title>{t("activityMetrics.promptCachingMetrics")}</Title>
<CustomLegend
categories={["metrics.cache_read_input_tokens", "metrics.cache_creation_input_tokens"]}
colors={["cyan", "purple"]}
/>
</div>
<div className="mb-2">
<Text>Cache Read: {metrics.total_cache_read_input_tokens?.toLocaleString() || 0} tokens</Text>
<Text>Cache Creation: {metrics.total_cache_creation_input_tokens?.toLocaleString() || 0} tokens</Text>
<Text>
{t("activityMetrics.cacheRead", {
count: metrics.total_cache_read_input_tokens?.toLocaleString() || 0,
})}
</Text>
<Text>
{t("activityMetrics.cacheCreation", {
count: metrics.total_cache_creation_input_tokens?.toLocaleString() || 0,
})}
</Text>
</div>
<AreaChart
className="mt-4"
@ -182,6 +207,7 @@ const ModelSection = ({
};
export const ActivityMetrics: React.FC<ActivityMetricsProps> = ({ modelMetrics, hidePromptCachingMetrics = false }) => {
const { t } = useTranslation();
const modelNames = Object.keys(modelMetrics).sort((a, b) => {
if (a === "") return 1;
if (b === "") return -1;
@ -257,22 +283,24 @@ export const ActivityMetrics: React.FC<ActivityMetricsProps> = ({ modelMetrics,
<div className="space-y-8">
{/* Global Summary */}
<div className="border rounded-lg p-4">
<Title>Overall Usage</Title>
<Title>{t("activityMetrics.overallUsage")}</Title>
<Grid numItems={4} className="gap-4 mb-4">
<Card>
<Text>Total Requests</Text>
<Text>{t("activityMetrics.totalRequests")}</Text>
<Title>{totalMetrics.total_requests.toLocaleString()}</Title>
</Card>
<Card>
<Text>Total Successful Requests</Text>
<Text>{t("activityMetrics.totalSuccessfulRequests")}</Text>
<Title>{totalMetrics.total_successful_requests.toLocaleString()}</Title>
</Card>
<Card>
<Text>Total Tokens</Text>
<Text>
{t("common.total")} {t("activityMetrics.tokens")}
</Text>
<Title>{totalMetrics.total_tokens.toLocaleString()}</Title>
</Card>
<Card>
<Text>Total Spend</Text>
<Text>{t("activityMetrics.totalSpend")}</Text>
<Title>${formatNumberWithCommas(totalMetrics.total_spend, 2)}</Title>
</Card>
</Grid>
@ -280,7 +308,7 @@ export const ActivityMetrics: React.FC<ActivityMetricsProps> = ({ modelMetrics,
<Grid numItems={2} className="gap-4">
<Card>
<div className="flex justify-between items-center">
<Title>Total Tokens Over Time</Title>
<Title>{t("activityMetrics.totalTokensOverTime")}</Title>
<CustomLegend
categories={["metrics.prompt_tokens", "metrics.completion_tokens", "metrics.total_tokens"]}
colors={["blue", "cyan", "indigo"]}
@ -299,7 +327,7 @@ export const ActivityMetrics: React.FC<ActivityMetricsProps> = ({ modelMetrics,
</Card>
<Card>
<div className="flex justify-between items-center">
<Title>Total Requests Over Time</Title>
<Title>{t("activityMetrics.totalRequestsOverTime")}</Title>
<CustomLegend
categories={["metrics.successful_requests", "metrics.failed_requests"]}
colors={["emerald", "red"]}
@ -326,16 +354,18 @@ export const ActivityMetrics: React.FC<ActivityMetricsProps> = ({ modelMetrics,
key={modelName}
header={
<div className="flex justify-between items-center w-full">
<Title>{modelMetrics[modelName].label || "Unknown Item"}</Title>
<Title>{modelMetrics[modelName].label || t("activityMetrics.unknownItem")}</Title>
<div className="flex space-x-4 text-sm text-gray-500">
<span>${formatNumberWithCommas(modelMetrics[modelName].total_spend, 2)}</span>
<span>{modelMetrics[modelName].total_requests.toLocaleString()} requests</span>
<span>
{modelMetrics[modelName].total_requests.toLocaleString()} {t("activityMetrics.requests")}
</span>
</div>
</div>
}
>
<ModelSection
modelName={modelName || "Unknown Model"}
modelName={modelName || t("activityMetrics.unknownModel")}
metrics={modelMetrics[modelName]}
hidePromptCachingMetrics={hidePromptCachingMetrics}
/>

View file

@ -16,6 +16,7 @@ import RoutePreview from "./route_preview";
import NotificationsManager from "./molecules/notifications_manager";
import PassThroughSecuritySection from "./common_components/PassThroughSecuritySection";
import PassThroughGuardrailsSection from "./common_components/PassThroughGuardrailsSection";
import { useTranslation } from "react-i18next";
const { Option } = Select2;
const HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH"];
@ -34,6 +35,7 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
passThroughItems,
premiumUser = false,
}) => {
const { t } = useTranslation();
const [form] = Form.useForm();
const [isModalVisible, setIsModalVisible] = useState(false);
const [isLoading, setIsLoading] = useState(false);
@ -95,7 +97,7 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
const updatedPassThroughSettings = [...passThroughItems, createdEndpoint];
setPassThroughItems(updatedPassThroughSettings);
NotificationsManager.success("Pass-through endpoint created successfully");
NotificationsManager.success(t("addPassThrough.createSuccess"));
form.resetFields();
setPathValue("");
setTargetValue("");
@ -104,7 +106,7 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
setGuardrails({});
setIsModalVisible(false);
} catch (error) {
NotificationsManager.fromBackend("Error creating pass-through endpoint: " + error);
NotificationsManager.fromBackend(t("addPassThrough.createFailed", { error }));
} finally {
setIsLoading(false);
}
@ -112,19 +114,19 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text);
NotificationsManager.success("Copied to clipboard!");
NotificationsManager.success(t("common.copied"));
};
return (
<div>
<Button className="mx-auto mb-4 mt-4" onClick={() => setIsModalVisible(true)}>
+ Add Pass-Through Endpoint
{t("addPassThrough.addButton")}
</Button>
<Modal
title={
<div className="flex items-center space-x-3 pb-4 border-b border-gray-100">
<ApiOutlined className="text-xl text-blue-500" />
<h2 className="text-xl font-semibold text-gray-900">Add Pass-Through Endpoint</h2>
<h2 className="text-xl font-semibold text-gray-900">{t("addPassThrough.modalTitle")}</h2>
</div>
}
open={isModalVisible}
@ -139,8 +141,8 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
>
<div className="mt-6">
<Alert
message="What is a Pass-Through Endpoint?"
description="Route requests from your LiteLLM proxy to any external API. Perfect for custom models, image generation APIs, or any service you want to proxy through LiteLLM."
message={t("addPassThrough.alertMessage")}
description={t("addPassThrough.alertDescription")}
type="info"
showIcon
className="mb-6"
@ -159,19 +161,17 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
>
{/* Route Configuration Section */}
<Card className="p-5">
<Title className="text-lg font-semibold text-gray-900 mb-2">Route Configuration</Title>
<Subtitle className="text-gray-600 mb-5">
Configure how requests to your domain will be forwarded to the target API
</Subtitle>
<Title className="text-lg font-semibold text-gray-900 mb-2">{t("addPassThrough.routeConfigTitle")}</Title>
<Subtitle className="text-gray-600 mb-5">{t("addPassThrough.routeConfigSubtitle")}</Subtitle>
<div className="space-y-5">
<Form.Item
label={<span className="text-sm font-medium text-gray-700">Path Prefix</span>}
name="path"
rules={[{ required: true, message: "Path is required", pattern: /^\// }]}
extra={
<div className="text-xs text-gray-500 mt-1">Example: /bria, /adobe-photoshop, /elasticsearch</div>
label={
<span className="text-sm font-medium text-gray-700">{t("addPassThrough.pathPrefixLabel")}</span>
}
name="path"
rules={[{ required: true, message: t("addPassThrough.pathRequired"), pattern: /^\// }]}
extra={<div className="text-xs text-gray-500 mt-1">{t("addPassThrough.pathExample")}</div>}
className="mb-4"
>
<div className="flex items-center">
@ -185,13 +185,15 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
</Form.Item>
<Form.Item
label={<span className="text-sm font-medium text-gray-700">Target URL</span>}
label={
<span className="text-sm font-medium text-gray-700">{t("addPassThrough.targetUrlLabel")}</span>
}
name="target"
rules={[
{ required: true, message: "Target URL is required" },
{ type: "url", message: "Please enter a valid URL" },
{ required: true, message: t("addPassThrough.targetUrlRequired") },
{ type: "url", message: t("addPassThrough.targetUrlInvalid") },
]}
extra={<div className="text-xs text-gray-500 mt-1">Example:https://engine.prod.bria-api.com</div>}
extra={<div className="text-xs text-gray-500 mt-1">{t("addPassThrough.targetUrlExample")}</div>}
className="mb-4"
>
<TextInput
@ -207,8 +209,8 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
<Form.Item
label={
<span className="text-sm font-medium text-gray-700 flex items-center">
HTTP Methods (Optional)
<Tooltip title="Select specific HTTP methods. Leave empty to support all methods (GET, POST, PUT, DELETE, PATCH). Useful when the same path needs different targets for different methods.">
{t("addPassThrough.httpMethodsLabel")}
<Tooltip title={t("addPassThrough.httpMethodsTooltip")}>
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
</Tooltip>
</span>
@ -217,15 +219,15 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
extra={
<div className="text-xs text-gray-500 mt-1">
{selectedMethods.length === 0
? "All HTTP methods supported (default)"
: `Only ${selectedMethods.join(", ")} requests will be routed to this endpoint`}
? t("addPassThrough.allMethodsDefault")
: t("addPassThrough.selectedMethodsDesc", { methods: selectedMethods.join(", ") })}
</div>
}
className="mb-4"
>
<Select2
mode="multiple"
placeholder="Select methods (leave empty for all)"
placeholder={t("addPassThrough.selectMethodsPlaceholder")}
value={selectedMethods}
onChange={setSelectedMethods}
allowClear
@ -241,10 +243,8 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
<div className="flex items-center justify-between py-3">
<div>
<div className="text-sm font-medium text-gray-700">Include Subpaths</div>
<div className="text-xs text-gray-500 mt-0.5">
Forward all subpaths to the target API (recommended for REST APIs)
</div>
<div className="text-sm font-medium text-gray-700">{t("addPassThrough.includeSubpathsLabel")}</div>
<div className="text-xs text-gray-500 mt-0.5">{t("addPassThrough.includeSubpathsDesc")}</div>
</div>
<Form.Item name="include_subpath" valuePropName="checked" className="mb-0">
<Switch checked={includeSubpath} onChange={setIncludeSubpath} />
@ -258,26 +258,26 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
{/* Headers Section */}
<Card className="p-6">
<Title className="text-lg font-semibold text-gray-900 mb-2">Headers</Title>
<Subtitle className="text-gray-600 mb-6">
Add headers that will be sent with every request to the target API
</Subtitle>
<Title className="text-lg font-semibold text-gray-900 mb-2">
{t("addPassThrough.headersSectionTitle")}
</Title>
<Subtitle className="text-gray-600 mb-6">{t("addPassThrough.headersSectionSubtitle")}</Subtitle>
<Form.Item
label={
<span className="text-sm font-medium text-gray-700 flex items-center">
Authentication Headers
<Tooltip title="Authentication and other headers to forward with requests">
{t("addPassThrough.authHeadersLabel")}
<Tooltip title={t("addPassThrough.authHeadersTooltip")}>
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
</Tooltip>
</span>
}
name="headers"
rules={[{ required: true, message: "Please configure the headers" }]}
rules={[{ required: true, message: t("addPassThrough.headersRequired") }]}
extra={
<div className="text-xs text-gray-500 mt-2">
<div className="font-medium mb-1">Add authentication tokens and other required headers</div>
<div>Common examples: auth_token, Authorization, x-api-key</div>
<div className="font-medium mb-1">{t("addPassThrough.headersExtraTitle")}</div>
<div>{t("addPassThrough.headersExtraExamples")}</div>
</div>
}
>
@ -287,16 +287,16 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
{/* Default Query Parameters Section */}
<Card className="p-6">
<Title className="text-lg font-semibold text-gray-900 mb-2">Default Query Parameters</Title>
<Subtitle className="text-gray-600 mb-6">
Add query parameters that will be automatically sent with every request to the target API
</Subtitle>
<Title className="text-lg font-semibold text-gray-900 mb-2">
{t("addPassThrough.queryParamsSectionTitle")}
</Title>
<Subtitle className="text-gray-600 mb-6">{t("addPassThrough.queryParamsSectionSubtitle")}</Subtitle>
<Form.Item
label={
<span className="text-sm font-medium text-gray-700 flex items-center">
Default Query Parameters (Optional)
<Tooltip title="Query parameters that will be added to all requests. Clients can override these by providing their own values.">
{t("addPassThrough.queryParamsLabel")}
<Tooltip title={t("addPassThrough.queryParamsTooltip")}>
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
</Tooltip>
</span>
@ -304,8 +304,8 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
name="default_query_params"
extra={
<div className="text-xs text-gray-500 mt-2">
<div className="font-medium mb-1">Parameters are sent with all GET, POST, PUT, PATCH requests</div>
<div>Client parameters override defaults. Examples: version=v1, format=json, key=default</div>
<div className="font-medium mb-1">{t("addPassThrough.queryParamsExtraTitle")}</div>
<div>{t("addPassThrough.queryParamsExtraExamples")}</div>
</div>
}
>
@ -328,24 +328,22 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
{/* Billing Section */}
<Card className="p-6">
<Title className="text-lg font-semibold text-gray-900 mb-2">Billing</Title>
<Subtitle className="text-gray-600 mb-6">Optional cost tracking for this endpoint</Subtitle>
<Title className="text-lg font-semibold text-gray-900 mb-2">
{t("addPassThrough.billingSectionTitle")}
</Title>
<Subtitle className="text-gray-600 mb-6">{t("addPassThrough.billingSectionSubtitle")}</Subtitle>
<Form.Item
label={
<span className="text-sm font-medium text-gray-700 flex items-center">
Cost Per Request (USD)
<Tooltip title="Optional: Track costs for requests to this endpoint">
{t("addPassThrough.costPerRequestLabel")}
<Tooltip title={t("addPassThrough.costPerRequestTooltip")}>
<InfoCircleOutlined className="ml-2 text-gray-400 hover:text-gray-600" />
</Tooltip>
</span>
}
name="cost_per_request"
extra={
<div className="text-xs text-gray-500 mt-2">
The cost charged for each request through this endpoint
</div>
}
extra={<div className="text-xs text-gray-500 mt-2">{t("addPassThrough.costPerRequestExtra")}</div>}
>
<NumericalInput min={0} step={0.001} precision={4} placeholder="2.0000" size="large" />
</Form.Item>
@ -353,7 +351,7 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
<div className="flex items-center justify-end space-x-3 pt-6 border-t border-gray-100">
<Button variant="secondary" onClick={handleCancel}>
Cancel
{t("common.cancel")}
</Button>
<Button
variant="primary"
@ -363,7 +361,7 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
form.submit();
}}
>
{isLoading ? "Creating..." : "Add Pass-Through Endpoint"}
{isLoading ? t("addPassThrough.creating") : t("addPassThrough.addButton")}
</Button>
</div>
</Form>

View file

@ -22,6 +22,7 @@ import { Agent, AgentKeyInfo } from "./agents/types";
import { Team } from "./key_team_helpers/key_list";
import { formatNumberWithCommas } from "@/utils/dataUtils";
import TableIconActionButton from "./common_components/IconActionButton/TableIconActionButtons/TableIconActionButton";
import { useTranslation } from "react-i18next";
interface AgentsPanelProps {
accessToken: string | null;
@ -34,6 +35,7 @@ interface AgentsResponse {
}
const AgentsPanel: React.FC<AgentsPanelProps> = ({ accessToken, userRole, teams }) => {
const { t } = useTranslation();
const [agentsList, setAgentsList] = useState<Agent[]>([]);
const [keyInfoMap, setKeyInfoMap] = useState<Record<string, AgentKeyInfo>>({});
const [isAddModalVisible, setIsAddModalVisible] = useState(false);
@ -126,11 +128,11 @@ const AgentsPanel: React.FC<AgentsPanelProps> = ({ accessToken, userRole, teams
setIsDeleting(true);
try {
await deleteAgentCall(accessToken, agentToDelete.id);
NotificationsManager.success(`Agent "${agentToDelete.name}" deleted successfully`);
NotificationsManager.success(t("agents.deleteSuccess", { name: agentToDelete.name }));
fetchAgents();
} catch (error) {
console.error("Error deleting agent:", error);
NotificationsManager.fromBackend("Failed to delete agent");
NotificationsManager.fromBackend(t("agents.deleteFailed"));
} finally {
setIsDeleting(false);
setAgentToDelete(null);
@ -152,14 +154,11 @@ const AgentsPanel: React.FC<AgentsPanelProps> = ({ accessToken, userRole, teams
return (
<div className="w-full mx-auto flex-auto overflow-y-auto m-8 p-2">
<div className="flex flex-col gap-2 mb-4">
<h1 className="text-2xl font-bold">Agents</h1>
<p className="text-sm text-gray-600">
List of A2A-spec agents that are available to be used in your organization. Go to AI Hub, to make agents
public.
</p>
<h1 className="text-2xl font-bold">{t("agents.title")}</h1>
<p className="text-sm text-gray-600">{t("agents.subtitle")}</p>
<Alert
message="Why do agents need keys?"
description="Keys scope access to an agent and allow it to call MCP tools. Assign a key when creating an agent or from the Virtual Keys page."
message={t("agents.alertMessage")}
description={t("agents.alertDescription")}
type="info"
showIcon
className="mb-3"
@ -167,13 +166,13 @@ const AgentsPanel: React.FC<AgentsPanelProps> = ({ accessToken, userRole, teams
<div className="mt-2 flex items-center gap-4">
{isAdmin && (
<Button onClick={handleAddAgent} disabled={!accessToken}>
+ Add New Agent
{t("agents.addNewAgent")}
</Button>
)}
<Tooltip title="When enabled, only agents with reachable URLs are shown">
<Tooltip title={t("agents.healthCheckTooltip")}>
<div className="flex items-center gap-2">
<CheckCircleOutlined className={healthCheckEnabled ? "text-green-500" : "text-gray-400"} />
<span className="text-sm text-gray-600">Health Check</span>
<span className="text-sm text-gray-600">{t("agents.healthCheck")}</span>
<Switch
size="small"
checked={healthCheckEnabled}
@ -200,22 +199,20 @@ const AgentsPanel: React.FC<AgentsPanelProps> = ({ accessToken, userRole, teams
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>Agent Name</TableHeaderCell>
<TableHeaderCell>Agent ID</TableHeaderCell>
<TableHeaderCell>Spend (USD)</TableHeaderCell>
<TableHeaderCell>Model</TableHeaderCell>
<TableHeaderCell>Created</TableHeaderCell>
<TableHeaderCell>Status</TableHeaderCell>
{isAdmin && <TableHeaderCell>Actions</TableHeaderCell>}
<TableHeaderCell>{t("agents.colAgentName")}</TableHeaderCell>
<TableHeaderCell>{t("agents.colAgentId")}</TableHeaderCell>
<TableHeaderCell>{t("agents.colSpend")}</TableHeaderCell>
<TableHeaderCell>{t("agents.colModel")}</TableHeaderCell>
<TableHeaderCell>{t("agents.colCreated")}</TableHeaderCell>
<TableHeaderCell>{t("common.status")}</TableHeaderCell>
{isAdmin && <TableHeaderCell>{t("common.actions")}</TableHeaderCell>}
</TableRow>
</TableHead>
<TableBody>
{sortedAgents.length === 0 ? (
<TableRow>
<TableCell colSpan={columnCount}>
<Text className="text-center">
No agents found. Click &quot;+ Add New Agent&quot; to create one.
</Text>
<Text className="text-center">{t("agents.noAgentsFound")}</Text>
</TableCell>
</TableRow>
) : (
@ -241,17 +238,19 @@ const AgentsPanel: React.FC<AgentsPanelProps> = ({ accessToken, userRole, teams
</TableCell>
<TableCell>
<Badge size="xs" color="blue">
{agent.litellm_params?.model || "N/A"}
{agent.litellm_params?.model || t("common.unknown")}
</Badge>
</TableCell>
<TableCell>
<Text>{agent.created_at ? new Date(agent.created_at).toLocaleDateString() : "N/A"}</Text>
<Text>
{agent.created_at ? new Date(agent.created_at).toLocaleDateString() : t("common.unknown")}
</Text>
</TableCell>
<TableCell>
{keyInfoMap[agent.agent_id]?.has_key ? (
<Badge color="green">Active</Badge>
<Badge color="green">{t("common.active")}</Badge>
) : (
<Badge color="yellow">Needs Setup</Badge>
<Badge color="yellow">{t("agents.needsSetup")}</Badge>
)}
</TableCell>
{isAdmin && (
@ -281,16 +280,16 @@ const AgentsPanel: React.FC<AgentsPanelProps> = ({ accessToken, userRole, teams
{agentToDelete && (
<Modal
title="Delete Agent"
title={t("agents.deleteModalTitle")}
open={agentToDelete !== null}
onOk={handleDeleteConfirm}
onCancel={handleDeleteCancel}
confirmLoading={isDeleting}
okText="Delete"
okText={t("common.delete")}
okButtonProps={{ danger: true }}
>
<p>Are you sure you want to delete agent: {agentToDelete.name}?</p>
<p>This action cannot be undone.</p>
<p>{t("agents.deleteConfirm", { name: agentToDelete.name })}</p>
<p>{t("agents.deleteCannotUndo")}</p>
</Modal>
)}
</div>

View file

@ -922,6 +922,93 @@
"monthsRemaining_other": "{{count}} months remaining",
"expiringSoon": "Expiring soon"
},
"activityMetrics": {
"totalRequests": "Total Requests",
"totalSuccessfulRequests": "Total Successful Requests",
"tokens": "Tokens",
"avgPerSuccessfulRequest": "avg per successful request",
"totalSpend": "Total Spend",
"perSuccessfulRequest": "per successful request",
"topVirtualKeysBySpend": "Top Virtual Keys by Spend",
"teamLabel": "Team: {{teamId}}",
"requestsAndTokens": "{{requests}} requests | {{tokens}} tokens",
"spendPerDay": "Spend per day",
"requestsPerDay": "Requests per day",
"successVsFailedRequests": "Success vs Failed Requests",
"promptCachingMetrics": "Prompt Caching Metrics",
"cacheRead": "Cache Read: {{count}} tokens",
"cacheCreation": "Cache Creation: {{count}} tokens",
"overallUsage": "Overall Usage",
"totalTokensOverTime": "Total Tokens Over Time",
"totalRequestsOverTime": "Total Requests Over Time",
"unknownItem": "Unknown Item",
"unknownModel": "Unknown Model",
"requests": "requests"
},
"addPassThrough": {
"addButton": "+ Add Pass-Through Endpoint",
"modalTitle": "Add Pass-Through Endpoint",
"alertMessage": "What is a Pass-Through Endpoint?",
"alertDescription": "Route requests from your LiteLLM proxy to any external API. Perfect for custom models, image generation APIs, or any service you want to proxy through LiteLLM.",
"routeConfigTitle": "Route Configuration",
"routeConfigSubtitle": "Configure how requests to your domain will be forwarded to the target API",
"pathPrefixLabel": "Path Prefix",
"pathRequired": "Path is required",
"pathExample": "Example: /bria, /adobe-photoshop, /elasticsearch",
"targetUrlLabel": "Target URL",
"targetUrlRequired": "Target URL is required",
"targetUrlInvalid": "Please enter a valid URL",
"targetUrlExample": "Example:https://engine.prod.bria-api.com",
"httpMethodsLabel": "HTTP Methods (Optional)",
"httpMethodsTooltip": "Select specific HTTP methods. Leave empty to support all methods (GET, POST, PUT, DELETE, PATCH). Useful when the same path needs different targets for different methods.",
"allMethodsDefault": "All HTTP methods supported (default)",
"selectedMethodsDesc": "Only {{methods}} requests will be routed to this endpoint",
"selectMethodsPlaceholder": "Select methods (leave empty for all)",
"includeSubpathsLabel": "Include Subpaths",
"includeSubpathsDesc": "Forward all subpaths to the target API (recommended for REST APIs)",
"headersSectionTitle": "Headers",
"headersSectionSubtitle": "Add headers that will be sent with every request to the target API",
"authHeadersLabel": "Authentication Headers",
"authHeadersTooltip": "Authentication and other headers to forward with requests",
"headersRequired": "Please configure the headers",
"headersExtraTitle": "Add authentication tokens and other required headers",
"headersExtraExamples": "Common examples: auth_token, Authorization, x-api-key",
"queryParamsSectionTitle": "Default Query Parameters",
"queryParamsSectionSubtitle": "Add query parameters that will be automatically sent with every request to the target API",
"queryParamsLabel": "Default Query Parameters (Optional)",
"queryParamsTooltip": "Query parameters that will be added to all requests. Clients can override these by providing their own values.",
"queryParamsExtraTitle": "Parameters are sent with all GET, POST, PUT, PATCH requests",
"queryParamsExtraExamples": "Client parameters override defaults. Examples: version=v1, format=json, key=default",
"billingSectionTitle": "Billing",
"billingSectionSubtitle": "Optional cost tracking for this endpoint",
"costPerRequestLabel": "Cost Per Request (USD)",
"costPerRequestTooltip": "Optional: Track costs for requests to this endpoint",
"costPerRequestExtra": "The cost charged for each request through this endpoint",
"creating": "Creating...",
"createSuccess": "Pass-through endpoint created successfully",
"createFailed": "Error creating pass-through endpoint: {{error}}"
},
"agents": {
"title": "Agents",
"subtitle": "List of A2A-spec agents that are available to be used in your organization. Go to AI Hub, to make agents public.",
"alertMessage": "Why do agents need keys?",
"alertDescription": "Keys scope access to an agent and allow it to call MCP tools. Assign a key when creating an agent or from the Virtual Keys page.",
"addNewAgent": "+ Add New Agent",
"healthCheck": "Health Check",
"healthCheckTooltip": "When enabled, only agents with reachable URLs are shown",
"colAgentName": "Agent Name",
"colAgentId": "Agent ID",
"colSpend": "Spend (USD)",
"colModel": "Model",
"colCreated": "Created",
"noAgentsFound": "No agents found. Click \"+ Add New Agent\" to create one.",
"needsSetup": "Needs Setup",
"deleteModalTitle": "Delete Agent",
"deleteConfirm": "Are you sure you want to delete agent: {{name}}?",
"deleteCannotUndo": "This action cannot be undone.",
"deleteSuccess": "Agent \"{{name}}\" deleted successfully",
"deleteFailed": "Failed to delete agent"
},
"common": {
"save": "Save",
"cancel": "Cancel",

View file

@ -922,6 +922,93 @@
"monthsRemaining_other": "剩余 {{count}} 个月",
"expiringSoon": "即将过期"
},
"activityMetrics": {
"totalRequests": "总请求数",
"totalSuccessfulRequests": "总成功请求数",
"tokens": "Token",
"avgPerSuccessfulRequest": "每次成功请求平均值",
"totalSpend": "总花费",
"perSuccessfulRequest": "每次成功请求",
"topVirtualKeysBySpend": "花费最多的虚拟密钥",
"teamLabel": "团队:{{teamId}}",
"requestsAndTokens": "{{requests}} 次请求 | {{tokens}} Token",
"spendPerDay": "每日花费",
"requestsPerDay": "每日请求数",
"successVsFailedRequests": "成功 vs 失败请求",
"promptCachingMetrics": "提示词缓存指标",
"cacheRead": "缓存读取:{{count}} Token",
"cacheCreation": "缓存创建:{{count}} Token",
"overallUsage": "整体用量",
"totalTokensOverTime": "Token 总量随时间变化",
"totalRequestsOverTime": "请求总量随时间变化",
"unknownItem": "未知项目",
"unknownModel": "未知模型",
"requests": "次请求"
},
"addPassThrough": {
"addButton": "+ 添加透传端点",
"modalTitle": "添加透传端点",
"alertMessage": "什么是透传端点?",
"alertDescription": "将来自 LiteLLM 代理的请求路由到任意外部 API。适用于自定义模型、图像生成 API 或任何您希望通过 LiteLLM 代理的服务。",
"routeConfigTitle": "路由配置",
"routeConfigSubtitle": "配置如何将您域名上的请求转发到目标 API",
"pathPrefixLabel": "路径前缀",
"pathRequired": "路径为必填项",
"pathExample": "示例:/bria、/adobe-photoshop、/elasticsearch",
"targetUrlLabel": "目标 URL",
"targetUrlRequired": "目标 URL 为必填项",
"targetUrlInvalid": "请输入有效的 URL",
"targetUrlExample": "示例:https://engine.prod.bria-api.com",
"httpMethodsLabel": "HTTP 方法(可选)",
"httpMethodsTooltip": "选择特定的 HTTP 方法。留空则支持所有方法(GET、POST、PUT、DELETE、PATCH)。当同一路径对不同方法需要不同目标时很有用。",
"allMethodsDefault": "支持所有 HTTP 方法(默认)",
"selectedMethodsDesc": "仅 {{methods}} 请求将路由到此端点",
"selectMethodsPlaceholder": "选择方法(留空表示全部)",
"includeSubpathsLabel": "包含子路径",
"includeSubpathsDesc": "将所有子路径转发到目标 API(推荐用于 REST API)",
"headersSectionTitle": "请求头",
"headersSectionSubtitle": "添加每次请求到目标 API 时都会发送的请求头",
"authHeadersLabel": "认证请求头",
"authHeadersTooltip": "随请求转发的认证及其他请求头",
"headersRequired": "请配置请求头",
"headersExtraTitle": "添加认证 Token 和其他必需的请求头",
"headersExtraExamples": "常见示例:auth_token、Authorization、x-api-key",
"queryParamsSectionTitle": "默认查询参数",
"queryParamsSectionSubtitle": "添加每次请求到目标 API 时自动发送的查询参数",
"queryParamsLabel": "默认查询参数(可选)",
"queryParamsTooltip": "将添加到所有请求的查询参数。客户端可通过提供自己的值来覆盖这些默认值。",
"queryParamsExtraTitle": "参数将随所有 GET、POST、PUT、PATCH 请求发送",
"queryParamsExtraExamples": "客户端参数可覆盖默认值。示例:version=v1、format=json、key=default",
"billingSectionTitle": "计费",
"billingSectionSubtitle": "此端点的可选成本追踪",
"costPerRequestLabel": "每次请求费用(USD)",
"costPerRequestTooltip": "可选:追踪此端点的请求费用",
"costPerRequestExtra": "通过此端点每次请求收取的费用",
"creating": "创建中...",
"createSuccess": "透传端点创建成功",
"createFailed": "创建透传端点时出错:{{error}}"
},
"agents": {
"title": "Agents",
"subtitle": "可在您的组织中使用的 A2A 规范 Agent 列表。前往 AI Hub 将 Agent 设为公开。",
"alertMessage": "为什么 Agent 需要密钥?",
"alertDescription": "密钥用于限定对 Agent 的访问范围,并允许其调用 MCP 工具。可在创建 Agent 时或从虚拟密钥页面分配密钥。",
"addNewAgent": "+ 添加新 Agent",
"healthCheck": "健康检查",
"healthCheckTooltip": "启用后,仅显示 URL 可访问的 Agent",
"colAgentName": "Agent 名称",
"colAgentId": "Agent ID",
"colSpend": "花费(USD)",
"colModel": "模型",
"colCreated": "创建时间",
"noAgentsFound": "未找到 Agent。点击「+ 添加新 Agent」以创建一个。",
"needsSetup": "待配置",
"deleteModalTitle": "删除 Agent",
"deleteConfirm": "确定要删除 Agent:{{name}}?",
"deleteCannotUndo": "此操作无法撤销。",
"deleteSuccess": "Agent「{{name}}」已成功删除",
"deleteFailed": "删除 Agent 失败"
},
"common": {
"save": "保存",
"cancel": "取消",