feat(ui): i18n for agents config and info views

This commit is contained in:
吹雪 2026-06-11 02:32:38 +09:00
parent a65632e342
commit 268b34774c
9 changed files with 530 additions and 185 deletions

View file

@ -1,6 +1,7 @@
"use client";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { Alert, Button, Checkbox, Collapse, Empty, Input, Space, Spin, Switch, Tag, Tooltip, Typography } from "antd";
// Empty is used in the skills panel below.
import {
@ -61,6 +62,7 @@ const AgentCardDiscovery: React.FC<AgentCardDiscoveryProps> = ({
discoveryRequest,
savedAgentCard,
}) => {
const { t } = useTranslation();
// When the parent drives discovery, ``manualUrl`` is unused — the URL
// comes from ``discoveryRequest.url`` directly. When the parent hasn't
// supplied a plan, the admin types into this field manually.
@ -115,14 +117,16 @@ const AgentCardDiscovery: React.FC<AgentCardDiscoveryProps> = ({
const handleDiscover = useCallback(async () => {
if (!accessToken) {
setError("No access token available");
setError(t("agentsPage.agentCardDiscovery.noAccessToken"));
onApplyRef.current(null);
return;
}
const trimmed = effectiveUrl.trim();
if (!trimmed) {
setError(
isParentDriven ? "Fill in the agent's connection details above first" : "Enter the agent's base URL first",
isParentDriven
? t("agentsPage.agentCardDiscovery.fillConnectionDetails")
: t("agentsPage.agentCardDiscovery.enterBaseUrl"),
);
setCard(null);
onApplyRef.current(null);
@ -150,7 +154,7 @@ const AgentCardDiscovery: React.FC<AgentCardDiscoveryProps> = ({
resetSelections(response.agent_card);
} catch (e: any) {
if (requestId !== discoverRequestIdRef.current) return;
setError(e?.message ? String(e.message) : "Failed to discover agent card");
setError(e?.message ? String(e.message) : t("agentsPage.agentCardDiscovery.failedToDiscover"));
setCard(null);
lastSyncedSelectionRef.current = null;
onApplyRef.current(null);
@ -169,7 +173,7 @@ const AgentCardDiscovery: React.FC<AgentCardDiscoveryProps> = ({
// during editing), which would re-fire the auto-discover effect and
// wipe in-progress user selections.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [accessToken, effectiveUrl, isParentDriven, discoveryMode, discoveryParamsKey]);
}, [accessToken, effectiveUrl, isParentDriven, discoveryMode, discoveryParamsKey, t]);
// Auto-discover when the URL (or parent plan) becomes available. Debounce
// is applied uniformly so rapid changes from a watched parent form (e.g.
@ -239,19 +243,19 @@ const AgentCardDiscovery: React.FC<AgentCardDiscoveryProps> = ({
<div className="border border-gray-200 rounded-lg p-4 bg-gray-50 mb-4">
<div className="flex items-center gap-2 mb-2">
<LinkOutlined className="text-indigo-600" />
<Text strong>Discover from agent URL</Text>
<Tooltip title="LiteLLM will fetch /.well-known/agent-card.json from this URL and let you pick which skills and capabilities to expose through the proxy.">
<Text strong>{t("agentsPage.agentCardDiscovery.discoverFromUrl")}</Text>
<Tooltip title={t("agentsPage.agentCardDiscovery.discoverTooltip")}>
<InfoCircleOutlined className="text-gray-400" />
</Tooltip>
</div>
{isParentDriven ? (
<>
<Paragraph className="text-xs text-gray-500 mb-2">
Using the connection details you entered above. We&apos;ll fetch:
{t("agentsPage.agentCardDiscovery.usingConnectionDetails")}
</Paragraph>
<div className="bg-white border border-gray-200 rounded px-3 py-2 mb-3 font-mono text-xs text-gray-700 break-all">
{discoveryRequest!.display_url || effectiveUrl || (
<span className="text-gray-400 italic">Fill in the fields above first</span>
<span className="text-gray-400 italic">{t("agentsPage.agentCardDiscovery.fillFieldsAbove")}</span>
)}
</div>
<div className="flex justify-end">
@ -262,15 +266,14 @@ const AgentCardDiscovery: React.FC<AgentCardDiscoveryProps> = ({
onClick={handleDiscover}
disabled={!effectiveUrl.trim()}
>
{card ? "Re-discover" : "Discover"}
{card ? t("agentsPage.agentCardDiscovery.rediscover") : t("agentsPage.agentCardDiscovery.discover")}
</Button>
</div>
</>
) : (
<>
<Paragraph className="text-xs text-gray-500 mb-3">
Paste the upstream agent&apos;s base URL. We&apos;ll try <code>/.well-known/agent-card.json</code>,{" "}
<code>/.well-known/agent.json</code>, and <code>/agent.json</code> in order.
{t("agentsPage.agentCardDiscovery.pasteUrlHint")}
</Paragraph>
<Space.Compact style={{ width: "100%" }}>
@ -288,7 +291,7 @@ const AgentCardDiscovery: React.FC<AgentCardDiscoveryProps> = ({
loading={loading}
onClick={handleDiscover}
>
{card ? "Re-discover" : "Discover"}
{card ? t("agentsPage.agentCardDiscovery.rediscover") : t("agentsPage.agentCardDiscovery.discover")}
</Button>
</Space.Compact>
</>
@ -298,7 +301,7 @@ const AgentCardDiscovery: React.FC<AgentCardDiscoveryProps> = ({
<Alert
className="mt-3"
type="error"
message="Discovery failed"
message={t("agentsPage.agentCardDiscovery.discoveryFailed")}
description={error}
showIcon
closable
@ -317,7 +320,7 @@ const AgentCardDiscovery: React.FC<AgentCardDiscoveryProps> = ({
<div className="flex items-center justify-between mb-3">
<Space>
<CheckCircleTwoTone twoToneColor="#52c41a" />
<Text strong>Upstream card loaded</Text>
<Text strong>{t("agentsPage.agentCardDiscovery.upstreamCardLoaded")}</Text>
{card.version && <Tag color="blue">v{card.version}</Tag>}
{card.provider?.organization && <Tag color="purple">{card.provider.organization}</Tag>}
</Space>
@ -325,16 +328,22 @@ const AgentCardDiscovery: React.FC<AgentCardDiscoveryProps> = ({
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 mb-4">
<div>
<label className="text-xs font-medium text-gray-600 block mb-1">Name (shown to API clients)</label>
<Input value={editedName} onChange={(e) => setEditedName(e.target.value)} placeholder="Agent name" />
<label className="text-xs font-medium text-gray-600 block mb-1">
{t("agentsPage.agentCardDiscovery.nameLabel")}
</label>
<Input
value={editedName}
onChange={(e) => setEditedName(e.target.value)}
placeholder={t("agentsPage.agentCardDiscovery.namePlaceholder")}
/>
</div>
<div>
<label className="text-xs font-medium text-gray-600 block mb-1">Description</label>
<label className="text-xs font-medium text-gray-600 block mb-1">{t("common.description")}</label>
<Input.TextArea
value={editedDescription}
onChange={(e) => setEditedDescription(e.target.value)}
rows={2}
placeholder="What this agent does"
placeholder={t("agentsPage.agentCardDiscovery.descriptionPlaceholder")}
/>
</div>
</div>
@ -344,15 +353,18 @@ const AgentCardDiscovery: React.FC<AgentCardDiscoveryProps> = ({
key="skills"
header={
<Space>
<Text strong>Skills</Text>
<Text strong>{t("agentsPage.agentCardDiscovery.skillsTitle")}</Text>
<Tag>
{selectedSkillCount} / {skillCount} selected
{t("agentsPage.agentCardDiscovery.skillsSelected", {
selected: selectedSkillCount,
total: skillCount,
})}
</Tag>
</Space>
}
>
{skillCount === 0 ? (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="Upstream card has no skills" />
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t("agentsPage.agentCardDiscovery.noSkills")} />
) : (
<div className="space-y-2">
{(card.skills ?? []).map((skill, idx) => {
@ -379,7 +391,7 @@ const AgentCardDiscovery: React.FC<AgentCardDiscoveryProps> = ({
{skill.description && (
<Paragraph
className="text-xs text-gray-500 mt-1 mb-0"
ellipsis={{ rows: 2, expandable: true, symbol: "more" }}
ellipsis={{ rows: 2, expandable: true, symbol: t("agentsPage.agentCardDiscovery.more") }}
>
{skill.description}
</Paragraph>
@ -396,8 +408,8 @@ const AgentCardDiscovery: React.FC<AgentCardDiscoveryProps> = ({
key="capabilities"
header={
<Space>
<Text strong>Capabilities</Text>
<Tooltip title="Only capabilities LiteLLM can faithfully proxy today are listed. Others (push notifications, extensions) are coming soon.">
<Text strong>{t("agentsPage.agentCardDiscovery.capabilitiesTitle")}</Text>
<Tooltip title={t("agentsPage.agentCardDiscovery.capabilitiesTooltip")}>
<InfoCircleOutlined className="text-gray-400" />
</Tooltip>
</Space>
@ -417,7 +429,7 @@ const AgentCardDiscovery: React.FC<AgentCardDiscoveryProps> = ({
</Text>
{!upstreamHas && (
<Tag className="ml-2" color="default">
not advertised upstream
{t("agentsPage.agentCardDiscovery.notAdvertisedUpstream")}
</Tag>
)}
</div>

View file

@ -2,6 +2,7 @@
* Shared configuration for agent form fields
* Used across create, view, and update operations
*/
import type { TFunction } from "i18next";
export interface FieldConfig {
name: string;
@ -22,7 +23,9 @@ export interface SectionConfig {
defaultExpanded?: boolean;
}
export const AGENT_FORM_CONFIG: {
export const getAgentFormConfig = (
t: TFunction,
): {
basic: SectionConfig;
skills: SectionConfig;
capabilities: SectionConfig;
@ -30,45 +33,45 @@ export const AGENT_FORM_CONFIG: {
litellm: SectionConfig;
cost: SectionConfig;
tracing: SectionConfig;
} = {
} => ({
basic: {
key: "basic",
title: "Basic Information",
title: t("agentsPage.agentConfig.basicTitle"),
defaultExpanded: true,
fields: [
{
name: "name",
label: "Display Name",
label: t("agentsPage.agentConfig.displayNameLabel"),
type: "text",
required: true,
placeholder: "e.g., Customer Support Agent",
placeholder: t("agentsPage.agentConfig.displayNamePlaceholder"),
},
{
name: "description",
label: "Description",
label: t("common.description"),
type: "textarea",
required: true,
placeholder: "Describe what this agent does...",
placeholder: t("agentsPage.agentConfig.descriptionPlaceholder"),
rows: 3,
},
{
name: "url",
label: "URL",
label: t("agentsPage.agentConfig.urlLabel"),
type: "url",
required: false,
placeholder: "http://localhost:9999/",
tooltip: "Base URL where the agent is hosted (optional)",
placeholder: t("agentsPage.agentConfig.urlPlaceholder"),
tooltip: t("agentsPage.agentConfig.urlTooltip"),
},
{
name: "version",
label: "Version",
label: t("agentsPage.agentConfig.versionLabel"),
type: "text",
placeholder: "1.0.0",
defaultValue: "1.0.0",
},
{
name: "protocolVersion",
label: "Protocol Version",
label: t("agentsPage.agentConfig.protocolVersionLabel"),
type: "text",
placeholder: "1.0",
defaultValue: "1.0",
@ -77,11 +80,11 @@ export const AGENT_FORM_CONFIG: {
},
skills: {
key: "skills",
title: "Skills",
title: t("agentsPage.agentConfig.skillsTitle"),
fields: [
{
name: "skills",
label: "Skills",
label: t("agentsPage.agentConfig.skillsTitle"),
type: "list",
defaultValue: [],
},
@ -89,159 +92,152 @@ export const AGENT_FORM_CONFIG: {
},
capabilities: {
key: "capabilities",
title: "Capabilities",
title: t("agentsPage.agentConfig.capabilitiesTitle"),
fields: [
{
name: "streaming",
label: "Streaming",
label: t("agentsPage.agentConfig.streamingLabel"),
type: "switch",
defaultValue: false,
},
{
name: "pushNotifications",
label: "Push Notifications",
label: t("agentsPage.agentConfig.pushNotificationsLabel"),
type: "switch",
},
{
name: "stateTransitionHistory",
label: "State Transition History",
label: t("agentsPage.agentConfig.stateTransitionHistoryLabel"),
type: "switch",
},
],
},
optional: {
key: "optional",
title: "Optional Settings",
title: t("agentsPage.agentConfig.optionalTitle"),
fields: [
{
name: "iconUrl",
label: "Icon URL",
label: t("agentsPage.agentConfig.iconUrlLabel"),
type: "url",
placeholder: "https://example.com/icon.png",
},
{
name: "documentationUrl",
label: "Documentation URL",
label: t("agentsPage.agentConfig.documentationUrlLabel"),
type: "url",
placeholder: "https://docs.example.com",
},
{
name: "supportsAuthenticatedExtendedCard",
label: "Supports Authenticated Extended Card",
label: t("agentsPage.agentConfig.supportsAuthCardLabel"),
type: "switch",
},
],
},
litellm: {
key: "litellm",
title: "LiteLLM Parameters",
title: t("agentsPage.agentConfig.litellmTitle"),
fields: [
{
name: "model",
label: "Model (Optional)",
label: t("agentsPage.agentConfig.modelOptionalLabel"),
type: "text",
},
{
name: "make_public",
label: "Make Public",
label: t("agentsPage.agentConfig.makePublicLabel"),
type: "switch",
},
],
},
cost: {
key: "cost",
title: "Cost Configuration",
title: t("agentsPage.agentConfig.costTitle"),
fields: [
{
name: "cost_per_query",
label: "Cost Per Query ($)",
label: t("agentsPage.agentConfig.costPerQueryLabel"),
type: "text",
placeholder: "0.0",
tooltip: "Fixed cost per query",
tooltip: t("agentsPage.agentConfig.costPerQueryTooltip"),
},
{
name: "input_cost_per_token",
label: "Input Cost Per Token ($)",
label: t("agentsPage.agentConfig.inputCostPerTokenLabel"),
type: "text",
placeholder: "0.000001",
tooltip: "Cost per input token",
tooltip: t("agentsPage.agentConfig.inputCostPerTokenTooltip"),
},
{
name: "output_cost_per_token",
label: "Output Cost Per Token ($)",
label: t("agentsPage.agentConfig.outputCostPerTokenLabel"),
type: "text",
placeholder: "0.000002",
tooltip: "Cost per output token",
tooltip: t("agentsPage.agentConfig.outputCostPerTokenTooltip"),
},
],
},
tracing: {
key: "tracing",
title: "Tracing",
title: t("agentsPage.agentConfig.tracingTitle"),
fields: [
{
name: "enable_tracing",
label: "Enable Tracing",
label: t("agentsPage.agentConfig.enableTracingLabel"),
type: "switch",
defaultValue: false,
tooltip: "Enable request tracing for this agent",
tooltip: t("agentsPage.agentConfig.enableTracingTooltip"),
},
],
},
};
});
export const SKILL_FIELD_CONFIG = {
export const getSkillFieldConfig = (t: TFunction) => ({
id: {
name: "id",
label: "Skill ID",
label: t("agentsPage.agentConfig.skillIdLabel"),
required: true,
placeholder: "e.g., hello_world",
placeholder: t("agentsPage.agentConfig.skillIdPlaceholder"),
},
name: {
name: "name",
label: "Skill Name",
label: t("agentsPage.agentConfig.skillNameLabel"),
required: true,
placeholder: "e.g., Returns hello world",
placeholder: t("agentsPage.agentConfig.skillNamePlaceholder"),
},
description: {
name: "description",
label: "Description",
label: t("common.description"),
required: true,
placeholder: "What this skill does",
placeholder: t("agentsPage.agentConfig.skillDescriptionPlaceholder"),
rows: 2,
},
tags: {
name: "tags",
label: "Tags",
label: t("agentsPage.agentConfig.skillTagsLabel"),
required: true,
placeholder: "Type a tag and press Enter",
placeholder: t("agentsPage.agentConfig.skillTagsPlaceholder"),
},
examples: {
name: "examples",
label: "Examples",
placeholder: "Type an example and press Enter",
label: t("agentsPage.agentConfig.skillExamplesLabel"),
placeholder: t("agentsPage.agentConfig.skillExamplesPlaceholder"),
},
};
});
/**
* Get default form values from configuration
*/
export const getDefaultFormValues = () => {
const defaults: any = {
defaultInputModes: ["text"],
defaultOutputModes: ["text"],
};
Object.values(AGENT_FORM_CONFIG).forEach((section) => {
section.fields.forEach((field) => {
if (field.defaultValue !== undefined) {
defaults[field.name] = field.defaultValue;
}
});
});
return defaults;
};
export const getDefaultFormValues = () => ({
defaultInputModes: ["text"],
defaultOutputModes: ["text"],
version: "1.0.0",
protocolVersion: "1.0",
streaming: false,
skills: [],
enable_tracing: false,
});
/**
* Build agent data from form values according to AgentConfig spec

View file

@ -1,4 +1,5 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { Title } from "@tremor/react";
import { Descriptions } from "antd";
import { Agent } from "./types";
@ -8,6 +9,7 @@ interface AgentCostViewProps {
}
const AgentCostView: React.FC<AgentCostViewProps> = ({ agent }) => {
const { t } = useTranslation();
const params = agent.litellm_params;
if (
@ -20,16 +22,22 @@ const AgentCostView: React.FC<AgentCostViewProps> = ({ agent }) => {
return (
<div style={{ marginTop: 24 }}>
<Title>Cost Configuration</Title>
<Title>{t("agentsPage.agentCostView.title")}</Title>
<Descriptions bordered column={1} style={{ marginTop: 16 }}>
{params.cost_per_query !== undefined && (
<Descriptions.Item label="Cost Per Query">${params.cost_per_query}</Descriptions.Item>
<Descriptions.Item label={t("agentsPage.agentCostView.costPerQuery")}>
${params.cost_per_query}
</Descriptions.Item>
)}
{params.input_cost_per_token !== undefined && (
<Descriptions.Item label="Input Cost Per Token">${params.input_cost_per_token}</Descriptions.Item>
<Descriptions.Item label={t("agentsPage.agentCostView.inputCostPerToken")}>
${params.input_cost_per_token}
</Descriptions.Item>
)}
{params.output_cost_per_token !== undefined && (
<Descriptions.Item label="Output Cost Per Token">${params.output_cost_per_token}</Descriptions.Item>
<Descriptions.Item label={t("agentsPage.agentCostView.outputCostPerToken")}>
${params.output_cost_per_token}
</Descriptions.Item>
)}
</Descriptions>
</div>

View file

@ -1,8 +1,9 @@
import React from "react";
import React, { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Form, Input, Switch, Collapse, Select, Space, Tooltip } from "antd";
import { Button as AntButton } from "antd";
import { PlusOutlined, MinusCircleOutlined, InfoCircleOutlined } from "@ant-design/icons";
import { AGENT_FORM_CONFIG, SKILL_FIELD_CONFIG } from "./agent_config";
import { getAgentFormConfig, getSkillFieldConfig } from "./agent_config";
import CostConfigFields from "./cost_config_fields";
@ -13,29 +14,32 @@ interface AgentFormFieldsProps {
visiblePanels?: string[];
}
/**
* Reusable form fields component for agent forms
* Uses shared configuration from agent_config.ts
*/
const AgentFormFields: React.FC<AgentFormFieldsProps> = ({ showAgentName = true, visiblePanels }) => {
const { t } = useTranslation();
const AGENT_FORM_CONFIG = useMemo(() => getAgentFormConfig(t), [t]);
const SKILL_FIELD_CONFIG = useMemo(() => getSkillFieldConfig(t), [t]);
const shouldShow = (key: string) => !visiblePanels || visiblePanels.includes(key);
return (
<>
{showAgentName && (
<Form.Item
label="Agent Name"
label={t("agentsPage.agentFormFields.agentNameLabel")}
name="agent_name"
rules={[{ required: true, message: "Please enter a unique agent name" }]}
tooltip="Unique identifier for the agent"
rules={[{ required: true, message: t("agentsPage.agentFormFields.agentNameRequired") }]}
tooltip={t("agentsPage.agentFormFields.agentNameTooltip")}
>
<Input placeholder="e.g., customer-support-agent" />
<Input placeholder={t("agentsPage.agentFormFields.agentNamePlaceholder")} />
</Form.Item>
)}
<Collapse defaultActiveKey={["basic"]} style={{ marginBottom: 16 }}>
{/* Basic Information */}
{shouldShow(AGENT_FORM_CONFIG.basic.key) && (
<Panel header={`${AGENT_FORM_CONFIG.basic.title} (Required)`} key={AGENT_FORM_CONFIG.basic.key}>
<Panel
header={`${AGENT_FORM_CONFIG.basic.title} (${t("common.required")})`}
key={AGENT_FORM_CONFIG.basic.key}
>
{AGENT_FORM_CONFIG.basic.fields.map((field) => (
<Form.Item
key={field.name}
@ -43,7 +47,14 @@ const AgentFormFields: React.FC<AgentFormFieldsProps> = ({ showAgentName = true,
name={field.name}
rules={
field.required
? [{ required: true, message: `Please enter ${field.label.toLowerCase()}` }]
? [
{
required: true,
message: t("agentsPage.agentFormFields.pleaseEnterField", {
field: field.label.toLowerCase(),
}),
},
]
: undefined
}
tooltip={field.tooltip}
@ -60,7 +71,7 @@ const AgentFormFields: React.FC<AgentFormFieldsProps> = ({ showAgentName = true,
{/* Skills */}
{shouldShow(AGENT_FORM_CONFIG.skills.key) && (
<Panel header={`${AGENT_FORM_CONFIG.skills.title}`} key={AGENT_FORM_CONFIG.skills.key}>
<Panel header={AGENT_FORM_CONFIG.skills.title} key={AGENT_FORM_CONFIG.skills.key}>
<Form.List name="skills">
{(fields, { add, remove }) => (
<>
@ -73,7 +84,7 @@ const AgentFormFields: React.FC<AgentFormFieldsProps> = ({ showAgentName = true,
{...field}
label={SKILL_FIELD_CONFIG.id.label}
name={[field.name, "id"]}
rules={[{ required: SKILL_FIELD_CONFIG.id.required, message: "Required" }]}
rules={[{ required: SKILL_FIELD_CONFIG.id.required, message: t("common.required") }]}
>
<Input placeholder={SKILL_FIELD_CONFIG.id.placeholder} />
</Form.Item>
@ -82,7 +93,7 @@ const AgentFormFields: React.FC<AgentFormFieldsProps> = ({ showAgentName = true,
{...field}
label={SKILL_FIELD_CONFIG.name.label}
name={[field.name, "name"]}
rules={[{ required: SKILL_FIELD_CONFIG.name.required, message: "Required" }]}
rules={[{ required: SKILL_FIELD_CONFIG.name.required, message: t("common.required") }]}
>
<Input placeholder={SKILL_FIELD_CONFIG.name.placeholder} />
</Form.Item>
@ -91,7 +102,7 @@ const AgentFormFields: React.FC<AgentFormFieldsProps> = ({ showAgentName = true,
{...field}
label={SKILL_FIELD_CONFIG.description.label}
name={[field.name, "description"]}
rules={[{ required: SKILL_FIELD_CONFIG.description.required, message: "Required" }]}
rules={[{ required: SKILL_FIELD_CONFIG.description.required, message: t("common.required") }]}
>
<Input.TextArea
rows={SKILL_FIELD_CONFIG.description.rows}
@ -103,7 +114,7 @@ const AgentFormFields: React.FC<AgentFormFieldsProps> = ({ showAgentName = true,
{...field}
label={SKILL_FIELD_CONFIG.tags.label}
name={[field.name, "tags"]}
rules={[{ required: SKILL_FIELD_CONFIG.tags.required, message: "Required" }]}
rules={[{ required: SKILL_FIELD_CONFIG.tags.required, message: t("common.required") }]}
>
<Select
mode="tags"
@ -123,12 +134,12 @@ const AgentFormFields: React.FC<AgentFormFieldsProps> = ({ showAgentName = true,
</Form.Item>
<AntButton type="link" danger onClick={() => remove(field.name)} icon={<MinusCircleOutlined />}>
Remove Skill
{t("agentsPage.agentFormFields.removeSkill")}
</AntButton>
</div>
))}
<AntButton type="dashed" onClick={() => add()} icon={<PlusOutlined />} style={{ width: "100%" }}>
Add Skill
{t("agentsPage.agentFormFields.addSkill")}
</AntButton>
</>
)}
@ -188,13 +199,13 @@ const AgentFormFields: React.FC<AgentFormFieldsProps> = ({ showAgentName = true,
{/* Authentication Headers */}
{shouldShow("auth_headers") && (
<Panel header="Authentication Headers" key="auth_headers">
<Panel header={t("agentsPage.agentFormFields.authHeadersTitle")} key="auth_headers">
{/* Static Headers */}
<Form.Item
label={
<span>
Static Headers{" "}
<Tooltip title="Headers always sent to the backend agent, regardless of the client request. Admin-configured, static wins on conflict.">
{t("agentsPage.agentFormFields.staticHeadersLabel")}{" "}
<Tooltip title={t("agentsPage.agentFormFields.staticHeadersTooltip")}>
<InfoCircleOutlined style={{ color: "#8c8c8c" }} />
</Tooltip>
</span>
@ -208,22 +219,28 @@ const AgentFormFields: React.FC<AgentFormFieldsProps> = ({ showAgentName = true,
<Form.Item
{...restField}
name={[name, "header"]}
rules={[{ required: true, message: "Header name required" }]}
rules={[{ required: true, message: t("agentsPage.agentFormFields.headerNameRequired") }]}
>
<Input placeholder="Header name (e.g. Authorization)" style={{ width: 220 }} />
<Input
placeholder={t("agentsPage.agentFormFields.headerNamePlaceholder")}
style={{ width: 220 }}
/>
</Form.Item>
<Form.Item
{...restField}
name={[name, "value"]}
rules={[{ required: true, message: "Value required" }]}
rules={[{ required: true, message: t("agentsPage.agentFormFields.headerValueRequired") }]}
>
<Input placeholder="Value (e.g. Bearer token123)" style={{ width: 260 }} />
<Input
placeholder={t("agentsPage.agentFormFields.headerValuePlaceholder")}
style={{ width: 260 }}
/>
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)} style={{ color: "#ff4d4f" }} />
</Space>
))}
<AntButton type="dashed" onClick={() => add()} icon={<PlusOutlined />} style={{ width: "100%" }}>
Add Static Header
{t("agentsPage.agentFormFields.addStaticHeader")}
</AntButton>
</>
)}
@ -234,8 +251,8 @@ const AgentFormFields: React.FC<AgentFormFieldsProps> = ({ showAgentName = true,
<Form.Item
label={
<span>
Forward Client Headers{" "}
<Tooltip title="Header names to extract from the client's request and forward to the agent. Type a name and press Enter.">
{t("agentsPage.agentFormFields.forwardClientHeadersLabel")}{" "}
<Tooltip title={t("agentsPage.agentFormFields.forwardClientHeadersTooltip")}>
<InfoCircleOutlined style={{ color: "#8c8c8c" }} />
</Tooltip>
</span>
@ -245,7 +262,7 @@ const AgentFormFields: React.FC<AgentFormFieldsProps> = ({ showAgentName = true,
<Select
mode="tags"
style={{ width: "100%" }}
placeholder="e.g. x-api-key, Authorization"
placeholder={t("agentsPage.agentFormFields.forwardClientHeadersPlaceholder")}
tokenSeparators={[","]}
/>
</Form.Item>

View file

@ -1,4 +1,5 @@
import React, { useState, useEffect, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Card, Title, Text, Button as TremorButton, Tab, TabGroup, TabList, TabPanel, TabPanels } from "@tremor/react";
import { Form, Input, InputNumber, Button as AntButton, Spin, Descriptions, Divider } from "antd";
import MessageManager from "@/components/molecules/message_manager";
@ -21,6 +22,7 @@ interface AgentInfoViewProps {
}
const AgentInfoView: React.FC<AgentInfoViewProps> = ({ agentId, onClose, accessToken, isAdmin }) => {
const { t } = useTranslation();
const [agent, setAgent] = useState<Agent | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [isEditing, setIsEditing] = useState(false);
@ -73,7 +75,7 @@ const AgentInfoView: React.FC<AgentInfoViewProps> = ({ agentId, onClose, accessT
}
} catch (error) {
console.error("Error fetching agent info:", error);
MessageManager.error("Failed to load agent information");
MessageManager.error(t("agentsPage.agentInfo.failedToLoad"));
} finally {
setIsLoading(false);
}
@ -153,12 +155,12 @@ const AgentInfoView: React.FC<AgentInfoViewProps> = ({ agentId, onClose, accessT
}
await patchAgentCall(accessToken, agentId, updateData);
MessageManager.success("Agent updated successfully");
MessageManager.success(t("agentsPage.agentInfo.updateSuccess"));
setIsEditing(false);
fetchAgentInfo();
} catch (error) {
console.error("Error updating agent:", error);
MessageManager.error("Failed to update agent");
MessageManager.error(t("agentsPage.agentInfo.updateFailed"));
} finally {
setIsSaving(false);
}
@ -177,9 +179,9 @@ const AgentInfoView: React.FC<AgentInfoViewProps> = ({ agentId, onClose, accessT
if (!agent) {
return (
<div className="p-4">
<div className="text-center">Agent not found</div>
<div className="text-center">{t("agentsPage.agentInfo.agentNotFound")}</div>
<TremorButton onClick={onClose} className="mt-4">
Back to Agents List
{t("agentsPage.agentInfo.backToList")}
</TremorButton>
</div>
);
@ -196,65 +198,91 @@ const AgentInfoView: React.FC<AgentInfoViewProps> = ({ agentId, onClose, accessT
<div className="p-4">
<div>
<TremorButton icon={ArrowLeftIcon} variant="light" onClick={onClose} className="mb-4">
Back to Agents
{t("agentsPage.agentInfo.backToAgents")}
</TremorButton>
<Title>{agent.agent_name || "Unnamed Agent"}</Title>
<Title>{agent.agent_name || t("agentsPage.agentInfo.unnamedAgent")}</Title>
<Text className="text-gray-500 font-mono">{agent.agent_id}</Text>
</div>
<TabGroup>
<TabList className="mb-4">
<Tab key="overview">Overview</Tab>
{isAdmin ? <Tab key="settings">Settings</Tab> : <></>}
<Tab key="overview">{t("agentsPage.agentInfo.overviewTab")}</Tab>
{isAdmin ? <Tab key="settings">{t("agentsPage.agentInfo.settingsTab")}</Tab> : <></>}
</TabList>
<TabPanels>
{/* Overview Panel */}
<TabPanel>
<Descriptions bordered column={1}>
<Descriptions.Item label="Agent ID">{agent.agent_id}</Descriptions.Item>
<Descriptions.Item label="Agent Name">{agent.agent_name}</Descriptions.Item>
<Descriptions.Item label="Display Name">{agent.agent_card_params?.name || "-"}</Descriptions.Item>
<Descriptions.Item label="Description">{agent.agent_card_params?.description || "-"}</Descriptions.Item>
<Descriptions.Item label="URL">{agent.agent_card_params?.url || "-"}</Descriptions.Item>
<Descriptions.Item label="Version">{agent.agent_card_params?.version || "-"}</Descriptions.Item>
<Descriptions.Item label="Protocol Version">
<Descriptions.Item label={t("agentsPage.agentInfo.agentIdLabel")}>{agent.agent_id}</Descriptions.Item>
<Descriptions.Item label={t("agentsPage.agentInfo.agentNameLabel")}>{agent.agent_name}</Descriptions.Item>
<Descriptions.Item label={t("agentsPage.agentInfo.displayNameLabel")}>
{agent.agent_card_params?.name || "-"}
</Descriptions.Item>
<Descriptions.Item label={t("common.description")}>
{agent.agent_card_params?.description || "-"}
</Descriptions.Item>
<Descriptions.Item label={t("agentsPage.agentInfo.urlLabel")}>
{agent.agent_card_params?.url || "-"}
</Descriptions.Item>
<Descriptions.Item label={t("agentsPage.agentInfo.versionLabel")}>
{agent.agent_card_params?.version || "-"}
</Descriptions.Item>
<Descriptions.Item label={t("agentsPage.agentInfo.protocolVersionLabel")}>
{agent.agent_card_params?.protocolVersion || "-"}
</Descriptions.Item>
<Descriptions.Item label="Streaming">
{agent.agent_card_params?.capabilities?.streaming ? "Yes" : "No"}
<Descriptions.Item label={t("agentsPage.agentInfo.streamingLabel")}>
{agent.agent_card_params?.capabilities?.streaming ? t("common.yes") : t("common.no")}
</Descriptions.Item>
{agent.agent_card_params?.capabilities?.pushNotifications && (
<Descriptions.Item label="Push Notifications">Yes</Descriptions.Item>
<Descriptions.Item label={t("agentsPage.agentInfo.pushNotificationsLabel")}>
{t("common.yes")}
</Descriptions.Item>
)}
{agent.agent_card_params?.capabilities?.stateTransitionHistory && (
<Descriptions.Item label="State Transition History">Yes</Descriptions.Item>
<Descriptions.Item label={t("agentsPage.agentInfo.stateTransitionHistoryLabel")}>
{t("common.yes")}
</Descriptions.Item>
)}
<Descriptions.Item label="Skills">
{agent.agent_card_params?.skills?.length || 0} configured
<Descriptions.Item label={t("agentsPage.agentInfo.skillsLabel")}>
{t("agentsPage.agentInfo.skillsConfigured", {
count: agent.agent_card_params?.skills?.length || 0,
})}
</Descriptions.Item>
{agent.litellm_params?.model && (
<Descriptions.Item label="Model">{agent.litellm_params.model}</Descriptions.Item>
<Descriptions.Item label={t("agentsPage.agentInfo.modelLabel")}>
{agent.litellm_params.model}
</Descriptions.Item>
)}
{agent.litellm_params?.make_public !== undefined && (
<Descriptions.Item label="Make Public">
{agent.litellm_params.make_public ? "Yes" : "No"}
<Descriptions.Item label={t("agentsPage.agentInfo.makePublicLabel")}>
{agent.litellm_params.make_public ? t("common.yes") : t("common.no")}
</Descriptions.Item>
)}
{agent.agent_card_params?.iconUrl && (
<Descriptions.Item label="Icon URL">{agent.agent_card_params.iconUrl}</Descriptions.Item>
<Descriptions.Item label={t("agentsPage.agentInfo.iconUrlLabel")}>
{agent.agent_card_params.iconUrl}
</Descriptions.Item>
)}
{agent.agent_card_params?.documentationUrl && (
<Descriptions.Item label="Documentation URL">
<Descriptions.Item label={t("agentsPage.agentInfo.documentationUrlLabel")}>
{agent.agent_card_params.documentationUrl}
</Descriptions.Item>
)}
<Descriptions.Item label="TPM Limit">{agent.tpm_limit ?? "Unlimited"}</Descriptions.Item>
<Descriptions.Item label="RPM Limit">{agent.rpm_limit ?? "Unlimited"}</Descriptions.Item>
<Descriptions.Item label="Session TPM Limit">{agent.session_tpm_limit ?? "Unlimited"}</Descriptions.Item>
<Descriptions.Item label="Session RPM Limit">{agent.session_rpm_limit ?? "Unlimited"}</Descriptions.Item>
<Descriptions.Item label="Created At">{formatDate(agent.created_at)}</Descriptions.Item>
<Descriptions.Item label="Updated At">{formatDate(agent.updated_at)}</Descriptions.Item>
<Descriptions.Item label={t("agentsPage.agentInfo.tpmLimitLabel")}>
{agent.tpm_limit ?? t("agentsPage.agentInfo.unlimited")}
</Descriptions.Item>
<Descriptions.Item label={t("agentsPage.agentInfo.rpmLimitLabel")}>
{agent.rpm_limit ?? t("agentsPage.agentInfo.unlimited")}
</Descriptions.Item>
<Descriptions.Item label={t("agentsPage.agentInfo.sessionTpmLimitLabel")}>
{agent.session_tpm_limit ?? t("agentsPage.agentInfo.unlimited")}
</Descriptions.Item>
<Descriptions.Item label={t("agentsPage.agentInfo.sessionRpmLimitLabel")}>
{agent.session_rpm_limit ?? t("agentsPage.agentInfo.unlimited")}
</Descriptions.Item>
<Descriptions.Item label={t("common.createdAt")}>{formatDate(agent.created_at)}</Descriptions.Item>
<Descriptions.Item label={t("common.updatedAt")}>{formatDate(agent.updated_at)}</Descriptions.Item>
</Descriptions>
{agent.object_permission &&
@ -263,22 +291,22 @@ const AgentInfoView: React.FC<AgentInfoViewProps> = ({ agentId, onClose, accessT
(agent.object_permission.mcp_tool_permissions &&
Object.keys(agent.object_permission.mcp_tool_permissions).length > 0)) && (
<div style={{ marginTop: 24 }}>
<Title>MCP Tool Permissions</Title>
<Title>{t("agentsPage.agentInfo.mcpToolPermissionsTitle")}</Title>
<Descriptions bordered column={1} style={{ marginTop: 16 }}>
{agent.object_permission.mcp_servers && agent.object_permission.mcp_servers.length > 0 && (
<Descriptions.Item label="MCP Servers">
<Descriptions.Item label={t("agentsPage.agentInfo.mcpServersLabel")}>
{agent.object_permission.mcp_servers.join(", ")}
</Descriptions.Item>
)}
{agent.object_permission.mcp_access_groups &&
agent.object_permission.mcp_access_groups.length > 0 && (
<Descriptions.Item label="MCP Access Groups">
<Descriptions.Item label={t("agentsPage.agentInfo.mcpAccessGroupsLabel")}>
{agent.object_permission.mcp_access_groups.join(", ")}
</Descriptions.Item>
)}
{agent.object_permission.mcp_tool_permissions &&
Object.keys(agent.object_permission.mcp_tool_permissions).length > 0 && (
<Descriptions.Item label="Tool permissions per server">
<Descriptions.Item label={t("agentsPage.agentInfo.toolPermissionsPerServerLabel")}>
<div className="space-y-1">
{Object.entries(agent.object_permission.mcp_tool_permissions).map(([serverId, tools]) => (
<div key={serverId}>
@ -297,23 +325,27 @@ const AgentInfoView: React.FC<AgentInfoViewProps> = ({ agentId, onClose, accessT
{agent.agent_card_params?.skills && agent.agent_card_params.skills.length > 0 && (
<div style={{ marginTop: 24 }}>
<Title>Skills</Title>
<Title>{t("agentsPage.agentInfo.skillsSectionTitle")}</Title>
<Descriptions bordered column={1} style={{ marginTop: 16 }}>
{agent.agent_card_params.skills.map((skill: any, index: number) => (
<Descriptions.Item label={skill.name || `Skill ${index + 1}`} key={index}>
<Descriptions.Item
label={skill.name || t("agentsPage.agentInfo.skillFallbackLabel", { index: index + 1 })}
key={index}
>
<div>
<div>
<strong>ID:</strong> {skill.id}
<strong>{t("agentsPage.agentInfo.skillIdField")}:</strong> {skill.id}
</div>
<div>
<strong>Description:</strong> {skill.description}
<strong>{t("common.description")}:</strong> {skill.description}
</div>
<div>
<strong>Tags:</strong> {Array.isArray(skill.tags) ? skill.tags.join(", ") : skill.tags}
<strong>{t("agentsPage.agentInfo.skillTagsField")}:</strong>{" "}
{Array.isArray(skill.tags) ? skill.tags.join(", ") : skill.tags}
</div>
{skill.examples && skill.examples.length > 0 && (
<div>
<strong>Examples:</strong>{" "}
<strong>{t("agentsPage.agentInfo.skillExamplesField")}:</strong>{" "}
{Array.isArray(skill.examples) ? skill.examples.join(", ") : skill.examples}
</div>
)}
@ -330,7 +362,7 @@ const AgentInfoView: React.FC<AgentInfoViewProps> = ({ agentId, onClose, accessT
<TabPanel>
<Card>
<div className="flex justify-between items-center mb-4">
<Title>Agent Settings</Title>
<Title>{t("agentsPage.agentInfo.agentSettingsTitle")}</Title>
{!isEditing && (
<TremorButton
onClick={() => {
@ -338,14 +370,14 @@ const AgentInfoView: React.FC<AgentInfoViewProps> = ({ agentId, onClose, accessT
setIsEditing(true);
}}
>
Edit Settings
{t("agentsPage.agentInfo.editSettings")}
</TremorButton>
)}
</div>
{isEditing ? (
<Form form={form} layout="vertical" onFinish={handleUpdate}>
<Form.Item label="Agent ID">
<Form.Item label={t("agentsPage.agentInfo.agentIdLabel")}>
<Input value={agent.agent_id} disabled />
</Form.Item>
@ -369,21 +401,21 @@ const AgentInfoView: React.FC<AgentInfoViewProps> = ({ agentId, onClose, accessT
)}
<Divider />
<Title className="mb-4">Rate Limits</Title>
<Title className="mb-4">{t("agentsPage.agentInfo.rateLimitsTitle")}</Title>
<div className="grid grid-cols-2 gap-4">
<Form.Item label="TPM Limit" name="tpm_limit">
<InputNumber className="w-full" min={0} placeholder="Unlimited" />
<Form.Item label={t("agentsPage.agentInfo.tpmLimitLabel")} name="tpm_limit">
<InputNumber className="w-full" min={0} placeholder={t("agentsPage.agentInfo.unlimited")} />
</Form.Item>
<Form.Item label="RPM Limit" name="rpm_limit">
<InputNumber className="w-full" min={0} placeholder="Unlimited" />
<Form.Item label={t("agentsPage.agentInfo.rpmLimitLabel")} name="rpm_limit">
<InputNumber className="w-full" min={0} placeholder={t("agentsPage.agentInfo.unlimited")} />
</Form.Item>
</div>
<div className="grid grid-cols-2 gap-4">
<Form.Item label="Session TPM Limit" name="session_tpm_limit">
<InputNumber className="w-full" min={0} placeholder="Unlimited" />
<Form.Item label={t("agentsPage.agentInfo.sessionTpmLimitLabel")} name="session_tpm_limit">
<InputNumber className="w-full" min={0} placeholder={t("agentsPage.agentInfo.unlimited")} />
</Form.Item>
<Form.Item label="Session RPM Limit" name="session_rpm_limit">
<InputNumber className="w-full" min={0} placeholder="Unlimited" />
<Form.Item label={t("agentsPage.agentInfo.sessionRpmLimitLabel")} name="session_rpm_limit">
<InputNumber className="w-full" min={0} placeholder={t("agentsPage.agentInfo.unlimited")} />
</Form.Item>
</div>
@ -395,13 +427,13 @@ const AgentInfoView: React.FC<AgentInfoViewProps> = ({ agentId, onClose, accessT
fetchAgentInfo();
}}
>
Cancel
{t("common.cancel")}
</AntButton>
<TremorButton loading={isSaving}>Save Changes</TremorButton>
<TremorButton loading={isSaving}>{t("agentsPage.agentInfo.saveChanges")}</TremorButton>
</div>
</Form>
) : (
<Text>Click &quot;Edit Settings&quot; to modify agent configuration.</Text>
<Text>{t("agentsPage.agentInfo.editSettingsHint")}</Text>
)}
</Card>
</TabPanel>

View file

@ -1,11 +1,13 @@
import React from "react";
import { Form, Input } from "antd";
import { AGENT_FORM_CONFIG } from "./agent_config";
import { useTranslation } from "react-i18next";
import { getAgentFormConfig } from "./agent_config";
const CostConfigFields: React.FC = () => {
const { t } = useTranslation();
return (
<>
{AGENT_FORM_CONFIG.cost.fields.map((field) => (
{getAgentFormConfig(t).cost.fields.map((field) => (
<Form.Item key={field.name} label={field.label} name={field.name} tooltip={field.tooltip}>
<Input placeholder={field.placeholder} type="number" step="0.000001" />
</Form.Item>

View file

@ -1,7 +1,8 @@
import React from "react";
import { Form, Input, Select, Collapse } from "antd";
import { useTranslation } from "react-i18next";
import { AgentCreateInfo, AgentCredentialFieldMetadata } from "../networking";
import { AGENT_FORM_CONFIG } from "./agent_config";
import { getAgentFormConfig } from "./agent_config";
import CostConfigFields from "./cost_config_fields";
const { Panel } = Collapse;
@ -16,6 +17,7 @@ interface DynamicAgentFormFieldsProps {
* credential fields defined by the agent type metadata.
*/
const DynamicAgentFormFields: React.FC<DynamicAgentFormFieldsProps> = ({ agentTypeInfo }) => {
const { t } = useTranslation();
return (
<>
<Form.Item
@ -59,7 +61,7 @@ const DynamicAgentFormFields: React.FC<DynamicAgentFormFieldsProps> = ({ agentTy
))}
<Collapse style={{ marginBottom: 16 }}>
<Panel header={AGENT_FORM_CONFIG.cost.title} key={AGENT_FORM_CONFIG.cost.key}>
<Panel header={getAgentFormConfig(t).cost.title} key={getAgentFormConfig(t).cost.key}>
<CostConfigFields />
</Panel>
</Collapse>

View file

@ -6033,6 +6033,144 @@
"noKeyAssigned": "No key assigned",
"statusActive": "Active",
"statusNeedsSetup": "Needs Setup"
},
"agentConfig": {
"basicTitle": "Basic Information",
"displayNameLabel": "Display Name",
"displayNamePlaceholder": "e.g., Customer Support Agent",
"descriptionPlaceholder": "Describe what this agent does...",
"urlLabel": "URL",
"urlPlaceholder": "http://localhost:9999/",
"urlTooltip": "Base URL where the agent is hosted (optional)",
"versionLabel": "Version",
"protocolVersionLabel": "Protocol Version",
"skillsTitle": "Skills",
"capabilitiesTitle": "Capabilities",
"streamingLabel": "Streaming",
"pushNotificationsLabel": "Push Notifications",
"stateTransitionHistoryLabel": "State Transition History",
"optionalTitle": "Optional Settings",
"iconUrlLabel": "Icon URL",
"documentationUrlLabel": "Documentation URL",
"supportsAuthCardLabel": "Supports Authenticated Extended Card",
"litellmTitle": "LiteLLM Parameters",
"modelOptionalLabel": "Model (Optional)",
"makePublicLabel": "Make Public",
"costTitle": "Cost Configuration",
"costPerQueryLabel": "Cost Per Query ($)",
"costPerQueryTooltip": "Fixed cost per query",
"inputCostPerTokenLabel": "Input Cost Per Token ($)",
"inputCostPerTokenTooltip": "Cost per input token",
"outputCostPerTokenLabel": "Output Cost Per Token ($)",
"outputCostPerTokenTooltip": "Cost per output token",
"tracingTitle": "Tracing",
"enableTracingLabel": "Enable Tracing",
"enableTracingTooltip": "Enable request tracing for this agent",
"skillIdLabel": "Skill ID",
"skillIdPlaceholder": "e.g., hello_world",
"skillNameLabel": "Skill Name",
"skillNamePlaceholder": "e.g., Returns hello world",
"skillDescriptionPlaceholder": "What this skill does",
"skillTagsLabel": "Tags",
"skillTagsPlaceholder": "Type a tag and press Enter",
"skillExamplesLabel": "Examples",
"skillExamplesPlaceholder": "Type an example and press Enter"
},
"agentFormFields": {
"agentNameLabel": "Agent Name",
"agentNameRequired": "Please enter a unique agent name",
"agentNameTooltip": "Unique identifier for the agent",
"agentNamePlaceholder": "e.g., customer-support-agent",
"pleaseEnterField": "Please enter {{field}}",
"removeSkill": "Remove Skill",
"addSkill": "Add Skill",
"authHeadersTitle": "Authentication Headers",
"staticHeadersLabel": "Static Headers",
"staticHeadersTooltip": "Headers always sent to the backend agent, regardless of the client request. Admin-configured, static wins on conflict.",
"headerNameRequired": "Header name required",
"headerNamePlaceholder": "Header name (e.g. Authorization)",
"headerValueRequired": "Value required",
"headerValuePlaceholder": "Value (e.g. Bearer token123)",
"addStaticHeader": "Add Static Header",
"forwardClientHeadersLabel": "Forward Client Headers",
"forwardClientHeadersTooltip": "Header names to extract from the client's request and forward to the agent. Type a name and press Enter.",
"forwardClientHeadersPlaceholder": "e.g. x-api-key, Authorization"
},
"agentCostView": {
"title": "Cost Configuration",
"costPerQuery": "Cost Per Query",
"inputCostPerToken": "Input Cost Per Token",
"outputCostPerToken": "Output Cost Per Token"
},
"agentCardDiscovery": {
"noAccessToken": "No access token available",
"fillConnectionDetails": "Fill in the agent's connection details above first",
"enterBaseUrl": "Enter the agent's base URL first",
"failedToDiscover": "Failed to discover agent card",
"discoverFromUrl": "Discover from agent URL",
"discoverTooltip": "LiteLLM will fetch /.well-known/agent-card.json from this URL and let you pick which skills and capabilities to expose through the proxy.",
"usingConnectionDetails": "Using the connection details you entered above. We'll fetch:",
"fillFieldsAbove": "Fill in the fields above first",
"rediscover": "Re-discover",
"discover": "Discover",
"pasteUrlHint": "Paste the upstream agent's base URL. We'll try /.well-known/agent-card.json, /.well-known/agent.json, and /agent.json in order.",
"discoveryFailed": "Discovery failed",
"upstreamCardLoaded": "Upstream card loaded",
"nameLabel": "Name (shown to API clients)",
"namePlaceholder": "Agent name",
"descriptionPlaceholder": "What this agent does",
"skillsTitle": "Skills",
"skillsSelected": "{{selected}} / {{total}} selected",
"noSkills": "Upstream card has no skills",
"more": "more",
"capabilitiesTitle": "Capabilities",
"capabilitiesTooltip": "Only capabilities LiteLLM can faithfully proxy today are listed. Others (push notifications, extensions) are coming soon.",
"notAdvertisedUpstream": "not advertised upstream"
},
"agentInfo": {
"failedToLoad": "Failed to load agent information",
"updateSuccess": "Agent updated successfully",
"updateFailed": "Failed to update agent",
"agentNotFound": "Agent not found",
"backToList": "Back to Agents List",
"backToAgents": "Back to Agents",
"unnamedAgent": "Unnamed Agent",
"overviewTab": "Overview",
"settingsTab": "Settings",
"agentIdLabel": "Agent ID",
"agentNameLabel": "Agent Name",
"displayNameLabel": "Display Name",
"urlLabel": "URL",
"versionLabel": "Version",
"protocolVersionLabel": "Protocol Version",
"streamingLabel": "Streaming",
"pushNotificationsLabel": "Push Notifications",
"stateTransitionHistoryLabel": "State Transition History",
"skillsLabel": "Skills",
"skillsConfigured": "{{count}} configured",
"modelLabel": "Model",
"makePublicLabel": "Make Public",
"iconUrlLabel": "Icon URL",
"documentationUrlLabel": "Documentation URL",
"tpmLimitLabel": "TPM Limit",
"rpmLimitLabel": "RPM Limit",
"sessionTpmLimitLabel": "Session TPM Limit",
"sessionRpmLimitLabel": "Session RPM Limit",
"unlimited": "Unlimited",
"mcpToolPermissionsTitle": "MCP Tool Permissions",
"mcpServersLabel": "MCP Servers",
"mcpAccessGroupsLabel": "MCP Access Groups",
"toolPermissionsPerServerLabel": "Tool permissions per server",
"skillsSectionTitle": "Skills",
"skillFallbackLabel": "Skill {{index}}",
"skillIdField": "ID",
"skillTagsField": "Tags",
"skillExamplesField": "Examples",
"agentSettingsTitle": "Agent Settings",
"editSettings": "Edit Settings",
"editSettingsHint": "Click \"Edit Settings\" to modify agent configuration.",
"rateLimitsTitle": "Rate Limits",
"saveChanges": "Save Changes"
}
}
}

View file

@ -6033,6 +6033,144 @@
"noKeyAssigned": "未分配密钥",
"statusActive": "活跃",
"statusNeedsSetup": "待配置"
},
"agentConfig": {
"basicTitle": "基本信息",
"displayNameLabel": "显示名称",
"displayNamePlaceholder": "例如:客服 Agent",
"descriptionPlaceholder": "描述此 Agent 的功能...",
"urlLabel": "URL",
"urlPlaceholder": "http://localhost:9999/",
"urlTooltip": "Agent 所在的基础 URL(可选)",
"versionLabel": "版本",
"protocolVersionLabel": "协议版本",
"skillsTitle": "工具",
"capabilitiesTitle": "能力",
"streamingLabel": "流式输出",
"pushNotificationsLabel": "推送通知",
"stateTransitionHistoryLabel": "状态转换历史",
"optionalTitle": "可选设置",
"iconUrlLabel": "图标 URL",
"documentationUrlLabel": "文档 URL",
"supportsAuthCardLabel": "支持认证扩展卡片",
"litellmTitle": "LiteLLM 参数",
"modelOptionalLabel": "模型(可选)",
"makePublicLabel": "公开",
"costTitle": "花费配置",
"costPerQueryLabel": "每次查询花费($)",
"costPerQueryTooltip": "每次查询的固定花费",
"inputCostPerTokenLabel": "输入 Token 花费($)",
"inputCostPerTokenTooltip": "每个输入 Token 的花费",
"outputCostPerTokenLabel": "输出 Token 花费($)",
"outputCostPerTokenTooltip": "每个输出 Token 的花费",
"tracingTitle": "追踪",
"enableTracingLabel": "启用追踪",
"enableTracingTooltip": "为此 Agent 启用请求追踪",
"skillIdLabel": "工具 ID",
"skillIdPlaceholder": "例如:hello_world",
"skillNameLabel": "工具名称",
"skillNamePlaceholder": "例如:返回 hello world",
"skillDescriptionPlaceholder": "此工具的功能",
"skillTagsLabel": "标签",
"skillTagsPlaceholder": "输入标签后按 Enter",
"skillExamplesLabel": "示例",
"skillExamplesPlaceholder": "输入示例后按 Enter"
},
"agentFormFields": {
"agentNameLabel": "Agent 名称",
"agentNameRequired": "请输入唯一的 Agent 名称",
"agentNameTooltip": "Agent 的唯一标识符",
"agentNamePlaceholder": "例如:customer-support-agent",
"pleaseEnterField": "请输入{{field}}",
"removeSkill": "移除工具",
"addSkill": "添加工具",
"authHeadersTitle": "认证请求头",
"staticHeadersLabel": "静态请求头",
"staticHeadersTooltip": "始终发送给后端 Agent 的请求头,与客户端请求无关。管理员配置,冲突时静态配置优先。",
"headerNameRequired": "请求头名称不能为空",
"headerNamePlaceholder": "请求头名称(例如 Authorization)",
"headerValueRequired": "值不能为空",
"headerValuePlaceholder": "值(例如 Bearer token123)",
"addStaticHeader": "添加静态请求头",
"forwardClientHeadersLabel": "转发客户端请求头",
"forwardClientHeadersTooltip": "从客户端请求中提取并转发给 Agent 的请求头名称。输入名称后按 Enter。",
"forwardClientHeadersPlaceholder": "例如:x-api-key、Authorization"
},
"agentCostView": {
"title": "花费配置",
"costPerQuery": "每次查询花费",
"inputCostPerToken": "输入 Token 花费",
"outputCostPerToken": "输出 Token 花费"
},
"agentCardDiscovery": {
"noAccessToken": "无可用访问令牌",
"fillConnectionDetails": "请先填写上方的 Agent 连接详情",
"enterBaseUrl": "请先输入 Agent 的基础 URL",
"failedToDiscover": "发现 Agent 卡片失败",
"discoverFromUrl": "从 Agent URL 发现",
"discoverTooltip": "LiteLLM 将从此 URL 获取 /.well-known/agent-card.json,并让您选择通过代理开放的工具和能力。",
"usingConnectionDetails": "使用上方填写的连接详情,将获取:",
"fillFieldsAbove": "请先填写上方字段",
"rediscover": "重新发现",
"discover": "发现",
"pasteUrlHint": "粘贴上游 Agent 的基础 URL,将依次尝试 /.well-known/agent-card.json、/.well-known/agent.json 和 /agent.json。",
"discoveryFailed": "发现失败",
"upstreamCardLoaded": "已加载上游卡片",
"nameLabel": "名称(对 API 客户端显示)",
"namePlaceholder": "Agent 名称",
"descriptionPlaceholder": "此 Agent 的功能",
"skillsTitle": "工具",
"skillsSelected": "{{selected}} / {{total}} 已选",
"noSkills": "上游卡片无工具",
"more": "展开",
"capabilitiesTitle": "能力",
"capabilitiesTooltip": "仅列出 LiteLLM 当前可以忠实代理的能力,其他能力(推送通知、扩展)即将推出。",
"notAdvertisedUpstream": "上游未声明"
},
"agentInfo": {
"failedToLoad": "加载 Agent 信息失败",
"updateSuccess": "Agent 更新成功",
"updateFailed": "更新 Agent 失败",
"agentNotFound": "未找到 Agent",
"backToList": "返回 Agent 列表",
"backToAgents": "返回 Agent 列表",
"unnamedAgent": "未命名 Agent",
"overviewTab": "概览",
"settingsTab": "设置",
"agentIdLabel": "Agent ID",
"agentNameLabel": "Agent 名称",
"displayNameLabel": "显示名称",
"urlLabel": "URL",
"versionLabel": "版本",
"protocolVersionLabel": "协议版本",
"streamingLabel": "流式输出",
"pushNotificationsLabel": "推送通知",
"stateTransitionHistoryLabel": "状态转换历史",
"skillsLabel": "工具",
"skillsConfigured": "已配置 {{count}} 个",
"modelLabel": "模型",
"makePublicLabel": "公开",
"iconUrlLabel": "图标 URL",
"documentationUrlLabel": "文档 URL",
"tpmLimitLabel": "TPM 限制",
"rpmLimitLabel": "RPM 限制",
"sessionTpmLimitLabel": "会话 TPM 限制",
"sessionRpmLimitLabel": "会话 RPM 限制",
"unlimited": "无限制",
"mcpToolPermissionsTitle": "MCP 工具权限",
"mcpServersLabel": "MCP 服务器",
"mcpAccessGroupsLabel": "MCP 访问组",
"toolPermissionsPerServerLabel": "每服务器工具权限",
"skillsSectionTitle": "工具",
"skillFallbackLabel": "工具 {{index}}",
"skillIdField": "ID",
"skillTagsField": "标签",
"skillExamplesField": "示例",
"agentSettingsTitle": "Agent 设置",
"editSettings": "编辑设置",
"editSettingsHint": "点击「编辑设置」修改 Agent 配置。",
"rateLimitsTitle": "速率限制",
"saveChanges": "保存更改"
}
}
}