mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for agent form and card
This commit is contained in:
parent
aea41b4ada
commit
a65632e342
4 changed files with 351 additions and 100 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { Modal, Form, Select, Input, Steps, Radio, Tag, Divider, Switch, InputNumber, Collapse } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import MessageManager from "@/components/molecules/message_manager";
|
||||
import { Button } from "@tremor/react";
|
||||
import { CheckCircleFilled, KeyOutlined, RobotOutlined, AppstoreOutlined, InfoCircleOutlined } from "@ant-design/icons";
|
||||
|
|
@ -40,6 +41,7 @@ interface AddAgentFormProps {
|
|||
}
|
||||
|
||||
const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessToken, onSuccess, teams }) => {
|
||||
const { t } = useTranslation();
|
||||
const { userId, userRole } = useAuthorized();
|
||||
const [form] = Form.useForm();
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
|
|
@ -244,7 +246,7 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
|
||||
const handleCreateAgent = async () => {
|
||||
if (!accessToken) {
|
||||
MessageManager.error("No access token available");
|
||||
MessageManager.error(t("agentsPage.addAgentForm.noAccessToken"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -254,7 +256,7 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
const values = { ...form.getFieldsValue(true) };
|
||||
const agentData = buildAgentData(values);
|
||||
if (!agentData) {
|
||||
MessageManager.error("Failed to build agent data");
|
||||
MessageManager.error(t("agentsPage.addAgentForm.failedToBuildAgentData"));
|
||||
setIsSubmitting(false);
|
||||
return;
|
||||
}
|
||||
|
|
@ -330,7 +332,7 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
setCreatedKeyValue(keyResponse.key || null);
|
||||
} else if (keyAssignOption === "existing_key") {
|
||||
if (!selectedExistingKey) {
|
||||
MessageManager.error("Please select an existing key to assign");
|
||||
MessageManager.error(t("agentsPage.addAgentForm.pleaseSelectExistingKey"));
|
||||
setIsSubmitting(false);
|
||||
return;
|
||||
}
|
||||
|
|
@ -347,7 +349,11 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
} catch (error) {
|
||||
console.error("Error creating agent:", error);
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
MessageManager.error(errorMessage ? `Failed to create agent: ${errorMessage}` : "Failed to create agent");
|
||||
MessageManager.error(
|
||||
errorMessage
|
||||
? t("agentsPage.addAgentForm.failedToCreateAgent", { error: errorMessage })
|
||||
: t("agentsPage.addAgentForm.failedToCreateAgentGeneric"),
|
||||
);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
|
|
@ -374,20 +380,23 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
|
||||
const renderEntitlementsStep = () => (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-gray-600">
|
||||
Configure which models, agents, and MCP tools this agent is allowed to use. Leave fields empty to allow all
|
||||
(subject to key/team permissions).
|
||||
</p>
|
||||
<p className="text-sm text-gray-600">{t("agentsPage.addAgentForm.entitlementsIntro")}</p>
|
||||
|
||||
<Form.Item
|
||||
label={<span className="text-sm font-medium text-gray-700">Allowed Models</span>}
|
||||
label={
|
||||
<span className="text-sm font-medium text-gray-700">{t("agentsPage.addAgentForm.allowedModelsLabel")}</span>
|
||||
}
|
||||
name="entitlement_models"
|
||||
tooltip="Restrict which models this agent can call. Leave empty to allow all."
|
||||
tooltip={t("agentsPage.addAgentForm.allowedModelsTooltip")}
|
||||
>
|
||||
<Select
|
||||
mode="tags"
|
||||
style={{ width: "100%" }}
|
||||
placeholder={loadingModels ? "Loading models..." : "Select models (leave empty for all)"}
|
||||
placeholder={
|
||||
loadingModels
|
||||
? t("agentsPage.addAgentForm.loadingModels")
|
||||
: t("agentsPage.addAgentForm.allowedModelsPlaceholder")
|
||||
}
|
||||
tokenSeparators={[","]}
|
||||
loading={loadingModels}
|
||||
showSearch
|
||||
|
|
@ -399,14 +408,20 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={<span className="text-sm font-medium text-gray-700">Allowed Agents (Sub-Agents)</span>}
|
||||
label={
|
||||
<span className="text-sm font-medium text-gray-700">{t("agentsPage.addAgentForm.allowedAgentsLabel")}</span>
|
||||
}
|
||||
name="entitlement_agents"
|
||||
tooltip="Restrict which other agents this agent can invoke as sub-agents. Leave empty to allow all."
|
||||
tooltip={t("agentsPage.addAgentForm.allowedAgentsTooltip")}
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
style={{ width: "100%" }}
|
||||
placeholder={loadingAgents ? "Loading agents..." : "Select agents (leave empty for all)"}
|
||||
placeholder={
|
||||
loadingAgents
|
||||
? t("agentsPage.addAgentForm.loadingAgents")
|
||||
: t("agentsPage.addAgentForm.allowedAgentsPlaceholder")
|
||||
}
|
||||
loading={loadingAgents}
|
||||
showSearch
|
||||
filterOption={(input, option) =>
|
||||
|
|
@ -424,9 +439,9 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
<Form.Item
|
||||
label={
|
||||
<span>
|
||||
Allowed MCP Servers{" "}
|
||||
{t("agentsPage.addAgentForm.allowedMcpServersLabel")}{" "}
|
||||
<InfoCircleOutlined
|
||||
title="Select which MCP servers or access groups this agent can access"
|
||||
title={t("agentsPage.addAgentForm.allowedMcpServersTooltip")}
|
||||
style={{ marginLeft: "4px" }}
|
||||
/>
|
||||
</span>
|
||||
|
|
@ -440,7 +455,7 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
}
|
||||
value={form.getFieldValue("allowed_mcp_servers_and_groups") || { servers: [], accessGroups: [] }}
|
||||
accessToken={accessToken ?? ""}
|
||||
placeholder="Select MCP servers or access groups (optional)"
|
||||
placeholder={t("agentsPage.addAgentForm.mcpServerSelectorPlaceholder")}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="mcp_tool_permissions" initialValue={{}} hidden>
|
||||
|
|
@ -472,16 +487,14 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
const renderObservabilityStep = () => (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-gray-700 mb-3">Tracing</h4>
|
||||
<h4 className="text-sm font-medium text-gray-700 mb-3">{t("agentsPage.addAgentForm.tracingTitle")}</h4>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-700">
|
||||
Require x-litellm-trace-id on calls TO this agent
|
||||
{t("agentsPage.addAgentForm.requireTraceIdInboundLabel")}
|
||||
</span>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
Only accept this agent being invoked with a trace-id (e.g. when used as a sub-agent).
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 mt-1">{t("agentsPage.addAgentForm.requireTraceIdInboundHint")}</p>
|
||||
</div>
|
||||
<Switch checked={requireTraceIdInbound} onChange={setRequireTraceIdInbound} />
|
||||
</div>
|
||||
|
|
@ -489,11 +502,9 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-700">
|
||||
Require x-litellm-trace-id on calls BY this agent
|
||||
{t("agentsPage.addAgentForm.requireTraceIdOutboundLabel")}
|
||||
</span>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
Requires LLM/MCP calls made by this agent to include x-litellm-trace-id for session tracking.
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 mt-1">{t("agentsPage.addAgentForm.requireTraceIdOutboundHint")}</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={requireTraceIdOutbound}
|
||||
|
|
@ -512,67 +523,98 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
<Divider className="my-0" />
|
||||
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-gray-700 mb-3">Budgets & Rate Limits</h4>
|
||||
<h4 className="text-sm font-medium text-gray-700 mb-3">{t("agentsPage.addAgentForm.budgetsTitle")}</h4>
|
||||
<div className="space-y-4">
|
||||
{!requireTraceIdOutbound && (
|
||||
<div className="p-3 bg-yellow-50 border border-yellow-200 rounded-lg text-sm text-yellow-800">
|
||||
Enable "Require x-litellm-trace-id on calls BY this agent" in Tracing to configure budgets and
|
||||
rate limits.
|
||||
{t("agentsPage.addAgentForm.enableTraceIdWarning")}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="text-sm font-medium text-gray-700">Session Budgets</div>
|
||||
<div className="text-sm font-medium text-gray-700">{t("agentsPage.addAgentForm.sessionBudgetsTitle")}</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-gray-600 block mb-1">Max Iterations</label>
|
||||
<label className="text-sm text-gray-600 block mb-1">
|
||||
{t("agentsPage.addAgentForm.maxIterationsLabel")}
|
||||
</label>
|
||||
<InputNumber
|
||||
className="w-full"
|
||||
min={1}
|
||||
placeholder="e.g. 25"
|
||||
placeholder={t("agentsPage.addAgentForm.maxIterationsPlaceholder")}
|
||||
disabled={!requireTraceIdOutbound}
|
||||
value={maxIterations}
|
||||
onChange={(val) => setMaxIterations(val)}
|
||||
/>
|
||||
<p className="text-xs text-gray-400 mt-1">Hard cap on LLM calls per session</p>
|
||||
<p className="text-xs text-gray-400 mt-1">{t("agentsPage.addAgentForm.maxIterationsHint")}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-gray-600 block mb-1">Max Budget Per Session ($)</label>
|
||||
<label className="text-sm text-gray-600 block mb-1">
|
||||
{t("agentsPage.addAgentForm.maxBudgetPerSessionLabel")}
|
||||
</label>
|
||||
<InputNumber
|
||||
className="w-full"
|
||||
min={0.01}
|
||||
step={0.5}
|
||||
placeholder="e.g. 5.00"
|
||||
placeholder={t("agentsPage.addAgentForm.maxBudgetPerSessionPlaceholder")}
|
||||
disabled={!requireTraceIdOutbound}
|
||||
value={maxBudgetPerSession}
|
||||
onChange={(val) => setMaxBudgetPerSession(val)}
|
||||
/>
|
||||
<p className="text-xs text-gray-400 mt-1">Max spend per trace before returning 429</p>
|
||||
<p className="text-xs text-gray-400 mt-1">{t("agentsPage.addAgentForm.maxBudgetPerSessionHint")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider className="my-2" />
|
||||
|
||||
<div className="text-sm font-medium text-gray-700">Agent Rate Limits</div>
|
||||
<p className="text-xs text-gray-500">Global rate limits applied across all callers of this agent.</p>
|
||||
<div className="text-sm font-medium text-gray-700">{t("agentsPage.addAgentForm.agentRateLimitsTitle")}</div>
|
||||
<p className="text-xs text-gray-500">{t("agentsPage.addAgentForm.agentRateLimitsHint")}</p>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Form.Item label="TPM Limit" name="tpm_limit" className="mb-0">
|
||||
<InputNumber className="w-full" min={0} placeholder="e.g. 100000" disabled={!requireTraceIdOutbound} />
|
||||
<Form.Item label={t("agentsPage.addAgentForm.tpmLimitLabel")} name="tpm_limit" className="mb-0">
|
||||
<InputNumber
|
||||
className="w-full"
|
||||
min={0}
|
||||
placeholder={t("agentsPage.addAgentForm.tpmLimitPlaceholder")}
|
||||
disabled={!requireTraceIdOutbound}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="RPM Limit" name="rpm_limit" className="mb-0">
|
||||
<InputNumber className="w-full" min={0} placeholder="e.g. 100" disabled={!requireTraceIdOutbound} />
|
||||
<Form.Item label={t("agentsPage.addAgentForm.rpmLimitLabel")} name="rpm_limit" className="mb-0">
|
||||
<InputNumber
|
||||
className="w-full"
|
||||
min={0}
|
||||
placeholder={t("agentsPage.addAgentForm.rpmLimitPlaceholder")}
|
||||
disabled={!requireTraceIdOutbound}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
<div className="text-sm font-medium text-gray-700 mt-4">Per-Session Rate Limits</div>
|
||||
<p className="text-xs text-gray-500">
|
||||
Rate limits per session (x-litellm-trace-id). Each session gets its own counters.
|
||||
</p>
|
||||
<div className="text-sm font-medium text-gray-700 mt-4">
|
||||
{t("agentsPage.addAgentForm.perSessionRateLimitsTitle")}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">{t("agentsPage.addAgentForm.perSessionRateLimitsHint")}</p>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Form.Item label="Session TPM Limit" name="session_tpm_limit" className="mb-0">
|
||||
<InputNumber className="w-full" min={0} placeholder="e.g. 10000" disabled={!requireTraceIdOutbound} />
|
||||
<Form.Item
|
||||
label={t("agentsPage.addAgentForm.sessionTpmLimitLabel")}
|
||||
name="session_tpm_limit"
|
||||
className="mb-0"
|
||||
>
|
||||
<InputNumber
|
||||
className="w-full"
|
||||
min={0}
|
||||
placeholder={t("agentsPage.addAgentForm.sessionTpmLimitPlaceholder")}
|
||||
disabled={!requireTraceIdOutbound}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="Session RPM Limit" name="session_rpm_limit" className="mb-0">
|
||||
<InputNumber className="w-full" min={0} placeholder="e.g. 20" disabled={!requireTraceIdOutbound} />
|
||||
<Form.Item
|
||||
label={t("agentsPage.addAgentForm.sessionRpmLimitLabel")}
|
||||
name="session_rpm_limit"
|
||||
className="mb-0"
|
||||
>
|
||||
<InputNumber
|
||||
className="w-full"
|
||||
min={0}
|
||||
placeholder={t("agentsPage.addAgentForm.sessionRpmLimitPlaceholder")}
|
||||
disabled={!requireTraceIdOutbound}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -581,10 +623,8 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
<Divider className="my-0" />
|
||||
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-gray-700 mb-3">Guardrails</h4>
|
||||
<p className="text-xs text-gray-500 mb-3">
|
||||
Apply guardrails to this agent. Selected guardrails will run on all calls made by this agent.
|
||||
</p>
|
||||
<h4 className="text-sm font-medium text-gray-700 mb-3">{t("agentsPage.addAgentForm.guardrailsTitle")}</h4>
|
||||
<p className="text-xs text-gray-500 mb-3">{t("agentsPage.addAgentForm.guardrailsHint")}</p>
|
||||
<Form.Item name="guardrails" initialValue={[]}>
|
||||
<GuardrailSelector
|
||||
accessToken={accessToken ?? ""}
|
||||
|
|
@ -666,9 +706,9 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
const renderConfigureStep = () => (
|
||||
<>
|
||||
<Form.Item
|
||||
label={<span className="text-sm font-medium text-gray-700">Agent Type</span>}
|
||||
label={<span className="text-sm font-medium text-gray-700">{t("agentsPage.addAgentForm.agentTypeLabel")}</span>}
|
||||
required
|
||||
tooltip="Select the type of agent you want to create"
|
||||
tooltip={t("agentsPage.addAgentForm.agentTypeTooltip")}
|
||||
>
|
||||
<Select
|
||||
value={agentType}
|
||||
|
|
@ -681,7 +721,9 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
{menu}
|
||||
<Divider style={{ margin: "4px 0" }} />
|
||||
<div className="px-2 py-1">
|
||||
<div className="text-xs text-gray-400 font-medium mb-1 uppercase tracking-wide px-2">Not listed?</div>
|
||||
<div className="text-xs text-gray-400 font-medium mb-1 uppercase tracking-wide px-2">
|
||||
{t("agentsPage.addAgentForm.notListedLabel")}
|
||||
</div>
|
||||
<div
|
||||
className={`flex items-center gap-3 px-2 py-2 rounded cursor-pointer transition-colors ${
|
||||
agentType === CUSTOM_AGENT_TYPE ? "bg-amber-50" : "hover:bg-amber-50"
|
||||
|
|
@ -691,14 +733,14 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
<AppstoreOutlined className="text-amber-600 text-lg" />
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium text-amber-700">Custom / Other</span>
|
||||
<span className="font-medium text-amber-700">
|
||||
{t("agentsPage.addAgentForm.customAgentLabel")}
|
||||
</span>
|
||||
<Tag color="orange" style={{ fontSize: 10, padding: "0 4px" }}>
|
||||
GENERIC
|
||||
</Tag>
|
||||
</div>
|
||||
<div className="text-xs text-amber-600">
|
||||
For agents that don't follow a standard protocol — just needs a virtual key
|
||||
</div>
|
||||
<div className="text-xs text-amber-600">{t("agentsPage.addAgentForm.customAgentDescription")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -732,14 +774,14 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
{agentType === CUSTOM_AGENT_TYPE ? (
|
||||
<div className="space-y-4">
|
||||
<Form.Item
|
||||
label="Agent Name"
|
||||
label={t("agentsPage.addAgentForm.agentNameLabel")}
|
||||
name="agent_name"
|
||||
rules={[{ required: true, message: "Please enter an agent name" }]}
|
||||
rules={[{ required: true, message: t("agentsPage.addAgentForm.agentNameRequired") }]}
|
||||
>
|
||||
<Input placeholder="e.g. my-custom-agent" />
|
||||
<Input placeholder={t("agentsPage.addAgentForm.agentNamePlaceholder")} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Description" name="description">
|
||||
<Input.TextArea placeholder="Describe what this agent does…" rows={3} />
|
||||
<Form.Item label={t("common.description")} name="description">
|
||||
<Input.TextArea placeholder={t("agentsPage.addAgentForm.descriptionPlaceholder")} rows={3} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
) : agentType === "a2a" ? (
|
||||
|
|
@ -757,7 +799,16 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
key={field.key}
|
||||
label={field.label}
|
||||
name={field.key}
|
||||
rules={field.required ? [{ required: true, message: `Please enter ${field.label}` }] : undefined}
|
||||
rules={
|
||||
field.required
|
||||
? [
|
||||
{
|
||||
required: true,
|
||||
message: t("agentsPage.addAgentForm.credentialFieldRequired", { label: field.label }),
|
||||
},
|
||||
]
|
||||
: undefined
|
||||
}
|
||||
tooltip={field.tooltip}
|
||||
initialValue={field.default_value}
|
||||
>
|
||||
|
|
@ -805,9 +856,11 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
</div>
|
||||
|
||||
<Form.Item
|
||||
label={<span className="text-sm font-medium text-gray-700">Assign to Team</span>}
|
||||
label={
|
||||
<span className="text-sm font-medium text-gray-700">{t("agentsPage.addAgentForm.assignToTeamLabel")}</span>
|
||||
}
|
||||
name="team_id"
|
||||
tooltip="Optionally assign this agent to a team. The agent and its key will belong to the selected team."
|
||||
tooltip={t("agentsPage.addAgentForm.assignToTeamTooltip")}
|
||||
>
|
||||
<TeamDropdown />
|
||||
</Form.Item>
|
||||
|
|
@ -834,24 +887,26 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<KeyOutlined className="text-indigo-600" />
|
||||
<span className="font-medium text-gray-900">Create a new key for this agent</span>
|
||||
<span className="font-medium text-gray-900">{t("agentsPage.addAgentForm.createNewKeyLabel")}</span>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 mt-1">A dedicated key scoped to this agent.</p>
|
||||
<p className="text-sm text-gray-500 mt-1">{t("agentsPage.addAgentForm.createNewKeyDescription")}</p>
|
||||
{keyAssignOption === "create_new" && (
|
||||
<div className="mt-3 space-y-3" onClick={(e) => e.stopPropagation()}>
|
||||
<div>
|
||||
<label className="text-sm text-gray-600 block mb-1">Key Name</label>
|
||||
<label className="text-sm text-gray-600 block mb-1">
|
||||
{t("agentsPage.addAgentForm.keyNameLabel")}
|
||||
</label>
|
||||
<Input
|
||||
value={newKeyName}
|
||||
onChange={(e) => setNewKeyName(e.target.value)}
|
||||
placeholder="e.g. my-agent-key"
|
||||
placeholder={t("agentsPage.addAgentForm.keyNamePlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Tag color="green">Recommended</Tag>
|
||||
<Tag color="green">{t("agentsPage.addAgentForm.recommended")}</Tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -873,15 +928,19 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<KeyOutlined className="text-gray-500" />
|
||||
<span className="font-medium text-gray-900">Assign an existing key</span>
|
||||
<span className="font-medium text-gray-900">
|
||||
{t("agentsPage.addAgentForm.assignExistingKeyLabel")}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 mt-1">Re-assign a key you already have to this agent.</p>
|
||||
<p className="text-sm text-gray-500 mt-1">
|
||||
{t("agentsPage.addAgentForm.assignExistingKeyDescription")}
|
||||
</p>
|
||||
{keyAssignOption === "existing_key" && (
|
||||
<div className="mt-3" onClick={(e) => e.stopPropagation()}>
|
||||
<Select
|
||||
showSearch
|
||||
style={{ width: "100%" }}
|
||||
placeholder="Search by key name…"
|
||||
placeholder={t("agentsPage.addAgentForm.searchByKeyName")}
|
||||
loading={loadingKeys}
|
||||
value={selectedExistingKey}
|
||||
onChange={(value) => setSelectedExistingKey(value)}
|
||||
|
|
@ -906,7 +965,7 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
className="text-sm text-gray-500 underline hover:text-gray-700"
|
||||
onClick={() => setKeyAssignOption("skip")}
|
||||
>
|
||||
Skip for now — I'll assign a key later
|
||||
{t("agentsPage.addAgentForm.skipKeyAssign")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -916,7 +975,7 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
const renderReadyStep = () => (
|
||||
<div className="text-center py-6">
|
||||
<CheckCircleFilled className="text-5xl text-green-500 mb-4" style={{ fontSize: 48 }} />
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-2">Agent Created!</h3>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-2">{t("agentsPage.addAgentForm.agentCreatedTitle")}</h3>
|
||||
<div className="flex justify-center mb-4">
|
||||
<Tag icon={<RobotOutlined />} color="purple" className="px-3 py-1 text-sm">
|
||||
{createdAgentName}
|
||||
|
|
@ -929,11 +988,11 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
)}
|
||||
{assignedKeyAlias && (
|
||||
<p className="text-sm text-gray-600 mt-2">
|
||||
Key <span className="font-medium">{assignedKeyAlias}</span> has been assigned to this agent.
|
||||
{t("agentsPage.addAgentForm.keyAssignedMessage", { keyAlias: assignedKeyAlias })}
|
||||
</p>
|
||||
)}
|
||||
{!createdKeyValue && !assignedKeyAlias && keyAssignOption === "skip" && (
|
||||
<p className="text-sm text-gray-500 mt-2">No key assigned. You can create one from the Virtual Keys page.</p>
|
||||
<p className="text-sm text-gray-500 mt-2">{t("agentsPage.addAgentForm.noKeyAssigned")}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
@ -943,7 +1002,7 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
title={
|
||||
<div className="flex items-center space-x-3 pb-4 border-b border-gray-100">
|
||||
{selectedLogo && currentStep < 1 && <img src={selectedLogo} alt="Agent" className="w-6 h-6 object-contain" />}
|
||||
<h2 className="text-xl font-semibold text-gray-900">Add New Agent</h2>
|
||||
<h2 className="text-xl font-semibold text-gray-900">{t("agentsPage.addAgentForm.modalTitle")}</h2>
|
||||
</div>
|
||||
}
|
||||
open={visible}
|
||||
|
|
@ -959,11 +1018,11 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
<div className="mt-4">
|
||||
{/* Step indicator */}
|
||||
<Steps current={currentStep} size="small" className="mb-8">
|
||||
<Step title="Configure" />
|
||||
<Step title="Entitlements" />
|
||||
<Step title="Governance" />
|
||||
<Step title="Agent Management" />
|
||||
<Step title="Ready" />
|
||||
<Step title={t("agentsPage.addAgentForm.stepConfigure")} />
|
||||
<Step title={t("agentsPage.addAgentForm.stepEntitlements")} />
|
||||
<Step title={t("agentsPage.addAgentForm.stepGovernance")} />
|
||||
<Step title={t("agentsPage.addAgentForm.stepAgentManagement")} />
|
||||
<Step title={t("agentsPage.addAgentForm.stepReady")} />
|
||||
</Steps>
|
||||
|
||||
<Form
|
||||
|
|
@ -1005,39 +1064,41 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
|
|||
onClick={handleBack}
|
||||
className="text-sm text-gray-600 border border-gray-300 rounded px-4 py-2 hover:bg-gray-50"
|
||||
>
|
||||
← Back
|
||||
{t("agentsPage.addAgentForm.backButton")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
{currentStep < 4 && (
|
||||
<Button variant="secondary" onClick={handleClose}>
|
||||
Cancel
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
)}
|
||||
{currentStep === 0 && (
|
||||
<Button variant="primary" onClick={handleNext}>
|
||||
Next →
|
||||
{t("agentsPage.addAgentForm.nextButton")}
|
||||
</Button>
|
||||
)}
|
||||
{currentStep === 1 && (
|
||||
<Button variant="primary" onClick={handleNext}>
|
||||
Next →
|
||||
{t("agentsPage.addAgentForm.nextButton")}
|
||||
</Button>
|
||||
)}
|
||||
{currentStep === 2 && (
|
||||
<Button variant="primary" onClick={handleNext}>
|
||||
Next →
|
||||
{t("agentsPage.addAgentForm.nextButton")}
|
||||
</Button>
|
||||
)}
|
||||
{currentStep === 3 && (
|
||||
<Button variant="primary" loading={isSubmitting} onClick={handleCreateAgent}>
|
||||
{isSubmitting ? "Creating..." : "Create Agent →"}
|
||||
{isSubmitting
|
||||
? t("agentsPage.addAgentForm.creatingButton")
|
||||
: t("agentsPage.addAgentForm.createAgentButton")}
|
||||
</Button>
|
||||
)}
|
||||
{currentStep === 4 && (
|
||||
<Button variant="primary" onClick={handleClose}>
|
||||
Done
|
||||
{t("common.done")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React from "react";
|
||||
import { Card, Badge, Tooltip, Button } from "antd";
|
||||
import { CopyOutlined, KeyOutlined, WarningOutlined, DeleteOutlined } from "@ant-design/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Agent, AgentKeyInfo } from "./types";
|
||||
|
||||
interface AgentCardProps {
|
||||
|
|
@ -14,10 +15,15 @@ interface AgentCardProps {
|
|||
}
|
||||
|
||||
const AgentCard: React.FC<AgentCardProps> = ({ agent, keyInfo, onAgentClick, onDeleteClick, isAdmin }) => {
|
||||
const description = agent.agent_card_params?.description || "No description";
|
||||
const { t } = useTranslation();
|
||||
const description = agent.agent_card_params?.description || t("agentsPage.agentCard.noDescription");
|
||||
const url = agent.agent_card_params?.url;
|
||||
const hasKey = keyInfo?.has_key ?? false;
|
||||
const statusBadge = hasKey ? <Badge status="success" text="Active" /> : <Badge status="warning" text="Needs Setup" />;
|
||||
const statusBadge = hasKey ? (
|
||||
<Badge status="success" text={t("agentsPage.agentCard.statusActive")} />
|
||||
) : (
|
||||
<Badge status="warning" text={t("agentsPage.agentCard.statusNeedsSetup")} />
|
||||
);
|
||||
|
||||
const copyToClipboard = (e: React.MouseEvent, text: string) => {
|
||||
e.stopPropagation();
|
||||
|
|
@ -37,7 +43,7 @@ const AgentCard: React.FC<AgentCardProps> = ({ agent, keyInfo, onAgentClick, onD
|
|||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="font-medium text-gray-900 truncate">{agent.agent_name}</span>
|
||||
<Tooltip title="Copy Agent ID">
|
||||
<Tooltip title={t("agentsPage.agentCard.copyAgentId")}>
|
||||
<CopyOutlined
|
||||
onClick={(e) => copyToClipboard(e, agent.agent_id)}
|
||||
className="cursor-pointer text-gray-400 hover:text-blue-500 text-xs shrink-0"
|
||||
|
|
@ -47,7 +53,7 @@ const AgentCard: React.FC<AgentCardProps> = ({ agent, keyInfo, onAgentClick, onD
|
|||
<div className="mt-1">{statusBadge}</div>
|
||||
</div>
|
||||
{isAdmin && onDeleteClick && (
|
||||
<Tooltip title="Delete agent">
|
||||
<Tooltip title={t("agentsPage.agentCard.deleteAgent")}>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
|
|
@ -72,12 +78,12 @@ const AgentCard: React.FC<AgentCardProps> = ({ agent, keyInfo, onAgentClick, onD
|
|||
{hasKey ? (
|
||||
<div className="flex items-center gap-1.5 text-gray-600">
|
||||
<KeyOutlined />
|
||||
<span>{keyInfo?.key_alias || keyInfo?.token_prefix || "Key assigned"}</span>
|
||||
<span>{keyInfo?.key_alias || keyInfo?.token_prefix || t("agentsPage.agentCard.keyAssigned")}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-1.5 text-amber-600">
|
||||
<WarningOutlined />
|
||||
<span>No key assigned</span>
|
||||
<span>{t("agentsPage.agentCard.noKeyAssigned")}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5942,5 +5942,97 @@
|
|||
"updateFailed": "Error updating vector store: {{error}}",
|
||||
"fetchDetailsFailed": "Error fetching vector store details: {{error}}"
|
||||
}
|
||||
},
|
||||
"agentsPage": {
|
||||
"addAgentForm": {
|
||||
"noAccessToken": "No access token available",
|
||||
"failedToBuildAgentData": "Failed to build agent data",
|
||||
"pleaseSelectExistingKey": "Please select an existing key to assign",
|
||||
"failedToCreateAgent": "Failed to create agent: {{error}}",
|
||||
"failedToCreateAgentGeneric": "Failed to create agent",
|
||||
"allowedModelsLabel": "Allowed Models",
|
||||
"allowedModelsTooltip": "Restrict which models this agent can call. Leave empty to allow all.",
|
||||
"allowedModelsPlaceholder": "Select models (leave empty for all)",
|
||||
"loadingModels": "Loading models...",
|
||||
"allowedAgentsLabel": "Allowed Agents (Sub-Agents)",
|
||||
"allowedAgentsTooltip": "Restrict which other agents this agent can invoke as sub-agents. Leave empty to allow all.",
|
||||
"allowedAgentsPlaceholder": "Select agents (leave empty for all)",
|
||||
"loadingAgents": "Loading agents...",
|
||||
"allowedMcpServersLabel": "Allowed MCP Servers",
|
||||
"allowedMcpServersTooltip": "Select which MCP servers or access groups this agent can access",
|
||||
"mcpServerSelectorPlaceholder": "Select MCP servers or access groups (optional)",
|
||||
"entitlementsIntro": "Configure which models, agents, and MCP tools this agent is allowed to use. Leave fields empty to allow all (subject to key/team permissions).",
|
||||
"tracingTitle": "Tracing",
|
||||
"requireTraceIdInboundLabel": "Require x-litellm-trace-id on calls TO this agent",
|
||||
"requireTraceIdInboundHint": "Only accept this agent being invoked with a trace-id (e.g. when used as a sub-agent).",
|
||||
"requireTraceIdOutboundLabel": "Require x-litellm-trace-id on calls BY this agent",
|
||||
"requireTraceIdOutboundHint": "Requires LLM/MCP calls made by this agent to include x-litellm-trace-id for session tracking.",
|
||||
"budgetsTitle": "Budgets & Rate Limits",
|
||||
"enableTraceIdWarning": "Enable \"Require x-litellm-trace-id on calls BY this agent\" in Tracing to configure budgets and rate limits.",
|
||||
"sessionBudgetsTitle": "Session Budgets",
|
||||
"maxIterationsLabel": "Max Iterations",
|
||||
"maxIterationsPlaceholder": "e.g. 25",
|
||||
"maxIterationsHint": "Hard cap on LLM calls per session",
|
||||
"maxBudgetPerSessionLabel": "Max Budget Per Session ($)",
|
||||
"maxBudgetPerSessionPlaceholder": "e.g. 5.00",
|
||||
"maxBudgetPerSessionHint": "Max spend per trace before returning 429",
|
||||
"agentRateLimitsTitle": "Agent Rate Limits",
|
||||
"agentRateLimitsHint": "Global rate limits applied across all callers of this agent.",
|
||||
"tpmLimitLabel": "TPM Limit",
|
||||
"tpmLimitPlaceholder": "e.g. 100000",
|
||||
"rpmLimitLabel": "RPM Limit",
|
||||
"rpmLimitPlaceholder": "e.g. 100",
|
||||
"perSessionRateLimitsTitle": "Per-Session Rate Limits",
|
||||
"perSessionRateLimitsHint": "Rate limits per session (x-litellm-trace-id). Each session gets its own counters.",
|
||||
"sessionTpmLimitLabel": "Session TPM Limit",
|
||||
"sessionTpmLimitPlaceholder": "e.g. 10000",
|
||||
"sessionRpmLimitLabel": "Session RPM Limit",
|
||||
"sessionRpmLimitPlaceholder": "e.g. 20",
|
||||
"guardrailsTitle": "Guardrails",
|
||||
"guardrailsHint": "Apply guardrails to this agent. Selected guardrails will run on all calls made by this agent.",
|
||||
"agentTypeLabel": "Agent Type",
|
||||
"agentTypeTooltip": "Select the type of agent you want to create",
|
||||
"notListedLabel": "Not listed?",
|
||||
"customAgentLabel": "Custom / Other",
|
||||
"customAgentDescription": "For agents that don't follow a standard protocol — just needs a virtual key",
|
||||
"agentNameLabel": "Agent Name",
|
||||
"agentNameRequired": "Please enter an agent name",
|
||||
"agentNamePlaceholder": "e.g. my-custom-agent",
|
||||
"descriptionPlaceholder": "Describe what this agent does…",
|
||||
"assignToTeamLabel": "Assign to Team",
|
||||
"assignToTeamTooltip": "Optionally assign this agent to a team. The agent and its key will belong to the selected team.",
|
||||
"createNewKeyLabel": "Create a new key for this agent",
|
||||
"createNewKeyDescription": "A dedicated key scoped to this agent.",
|
||||
"keyNameLabel": "Key Name",
|
||||
"keyNamePlaceholder": "e.g. my-agent-key",
|
||||
"recommended": "Recommended",
|
||||
"assignExistingKeyLabel": "Assign an existing key",
|
||||
"assignExistingKeyDescription": "Re-assign a key you already have to this agent.",
|
||||
"searchByKeyName": "Search by key name…",
|
||||
"skipKeyAssign": "Skip for now — I'll assign a key later",
|
||||
"agentCreatedTitle": "Agent Created!",
|
||||
"keyAssignedMessage": "Key {{keyAlias}} has been assigned to this agent.",
|
||||
"noKeyAssigned": "No key assigned. You can create one from the Virtual Keys page.",
|
||||
"modalTitle": "Add New Agent",
|
||||
"stepConfigure": "Configure",
|
||||
"stepEntitlements": "Entitlements",
|
||||
"stepGovernance": "Governance",
|
||||
"stepAgentManagement": "Agent Management",
|
||||
"stepReady": "Ready",
|
||||
"backButton": "← Back",
|
||||
"nextButton": "Next →",
|
||||
"createAgentButton": "Create Agent →",
|
||||
"creatingButton": "Creating...",
|
||||
"credentialFieldRequired": "Please enter {{label}}"
|
||||
},
|
||||
"agentCard": {
|
||||
"noDescription": "No description",
|
||||
"copyAgentId": "Copy Agent ID",
|
||||
"deleteAgent": "Delete agent",
|
||||
"keyAssigned": "Key assigned",
|
||||
"noKeyAssigned": "No key assigned",
|
||||
"statusActive": "Active",
|
||||
"statusNeedsSetup": "Needs Setup"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5942,5 +5942,97 @@
|
|||
"updateFailed": "更新向量存储失败:{{error}}",
|
||||
"fetchDetailsFailed": "获取向量存储详情失败:{{error}}"
|
||||
}
|
||||
},
|
||||
"agentsPage": {
|
||||
"addAgentForm": {
|
||||
"noAccessToken": "无可用访问令牌",
|
||||
"failedToBuildAgentData": "构建 Agent 数据失败",
|
||||
"pleaseSelectExistingKey": "请选择要分配的现有密钥",
|
||||
"failedToCreateAgent": "创建 Agent 失败:{{error}}",
|
||||
"failedToCreateAgentGeneric": "创建 Agent 失败",
|
||||
"allowedModelsLabel": "允许的模型",
|
||||
"allowedModelsTooltip": "限制此 Agent 可调用的模型。留空表示允许所有模型。",
|
||||
"allowedModelsPlaceholder": "选择模型(留空表示允许所有)",
|
||||
"loadingModels": "加载模型中...",
|
||||
"allowedAgentsLabel": "允许的 Agent(子 Agent)",
|
||||
"allowedAgentsTooltip": "限制此 Agent 可作为子 Agent 调用的其他 Agent。留空表示允许所有。",
|
||||
"allowedAgentsPlaceholder": "选择 Agent(留空表示允许所有)",
|
||||
"loadingAgents": "加载 Agent 中...",
|
||||
"allowedMcpServersLabel": "允许的 MCP 服务器",
|
||||
"allowedMcpServersTooltip": "选择此 Agent 可访问的 MCP 服务器或访问组",
|
||||
"mcpServerSelectorPlaceholder": "选择 MCP 服务器或访问组(可选)",
|
||||
"entitlementsIntro": "配置此 Agent 允许使用的模型、Agent 和 MCP 工具。留空表示允许所有(受密钥/团队权限约束)。",
|
||||
"tracingTitle": "追踪",
|
||||
"requireTraceIdInboundLabel": "要求调用此 Agent 时携带 x-litellm-trace-id",
|
||||
"requireTraceIdInboundHint": "仅接受携带 trace-id 的调用(例如作为子 Agent 使用时)。",
|
||||
"requireTraceIdOutboundLabel": "要求此 Agent 发出的调用携带 x-litellm-trace-id",
|
||||
"requireTraceIdOutboundHint": "要求此 Agent 发出的 LLM/MCP 调用包含 x-litellm-trace-id 以进行会话追踪。",
|
||||
"budgetsTitle": "预算与速率限制",
|
||||
"enableTraceIdWarning": "请在「追踪」中启用「要求此 Agent 发出的调用携带 x-litellm-trace-id」以配置预算和速率限制。",
|
||||
"sessionBudgetsTitle": "会话预算",
|
||||
"maxIterationsLabel": "最大迭代次数",
|
||||
"maxIterationsPlaceholder": "例如 25",
|
||||
"maxIterationsHint": "每个会话的 LLM 调用硬性上限",
|
||||
"maxBudgetPerSessionLabel": "每会话最大预算($)",
|
||||
"maxBudgetPerSessionPlaceholder": "例如 5.00",
|
||||
"maxBudgetPerSessionHint": "每个 trace 返回 429 前的最大花费",
|
||||
"agentRateLimitsTitle": "Agent 速率限制",
|
||||
"agentRateLimitsHint": "对所有调用此 Agent 的请求方全局生效的速率限制。",
|
||||
"tpmLimitLabel": "TPM 限制",
|
||||
"tpmLimitPlaceholder": "例如 100000",
|
||||
"rpmLimitLabel": "RPM 限制",
|
||||
"rpmLimitPlaceholder": "例如 100",
|
||||
"perSessionRateLimitsTitle": "每会话速率限制",
|
||||
"perSessionRateLimitsHint": "按会话(x-litellm-trace-id)设置的速率限制,每个会话独立计数。",
|
||||
"sessionTpmLimitLabel": "会话 TPM 限制",
|
||||
"sessionTpmLimitPlaceholder": "例如 10000",
|
||||
"sessionRpmLimitLabel": "会话 RPM 限制",
|
||||
"sessionRpmLimitPlaceholder": "例如 20",
|
||||
"guardrailsTitle": "护栏",
|
||||
"guardrailsHint": "为此 Agent 应用护栏。选定的护栏将对此 Agent 的所有调用生效。",
|
||||
"agentTypeLabel": "Agent 类型",
|
||||
"agentTypeTooltip": "选择要创建的 Agent 类型",
|
||||
"notListedLabel": "没有找到?",
|
||||
"customAgentLabel": "自定义 / 其他",
|
||||
"customAgentDescription": "适用于不遵循标准协议的 Agent,只需一个虚拟密钥",
|
||||
"agentNameLabel": "Agent 名称",
|
||||
"agentNameRequired": "请输入 Agent 名称",
|
||||
"agentNamePlaceholder": "例如 my-custom-agent",
|
||||
"descriptionPlaceholder": "描述此 Agent 的功能…",
|
||||
"assignToTeamLabel": "分配到团队",
|
||||
"assignToTeamTooltip": "可选:将此 Agent 分配到团队。Agent 及其密钥将归属于所选团队。",
|
||||
"createNewKeyLabel": "为此 Agent 创建新密钥",
|
||||
"createNewKeyDescription": "专用于此 Agent 的密钥。",
|
||||
"keyNameLabel": "密钥名称",
|
||||
"keyNamePlaceholder": "例如 my-agent-key",
|
||||
"recommended": "推荐",
|
||||
"assignExistingKeyLabel": "分配现有密钥",
|
||||
"assignExistingKeyDescription": "将已有密钥重新分配给此 Agent。",
|
||||
"searchByKeyName": "按密钥名称搜索…",
|
||||
"skipKeyAssign": "暂时跳过,稍后再分配密钥",
|
||||
"agentCreatedTitle": "Agent 已创建!",
|
||||
"keyAssignedMessage": "密钥 {{keyAlias}} 已分配给此 Agent。",
|
||||
"noKeyAssigned": "未分配密钥。您可以在虚拟密钥页面创建密钥。",
|
||||
"modalTitle": "添加新 Agent",
|
||||
"stepConfigure": "配置",
|
||||
"stepEntitlements": "权限",
|
||||
"stepGovernance": "治理",
|
||||
"stepAgentManagement": "Agent 管理",
|
||||
"stepReady": "完成",
|
||||
"backButton": "← 返回",
|
||||
"nextButton": "下一步 →",
|
||||
"createAgentButton": "创建 Agent →",
|
||||
"creatingButton": "创建中...",
|
||||
"credentialFieldRequired": "请输入 {{label}}"
|
||||
},
|
||||
"agentCard": {
|
||||
"noDescription": "无描述",
|
||||
"copyAgentId": "复制 Agent ID",
|
||||
"deleteAgent": "删除 Agent",
|
||||
"keyAssigned": "已分配密钥",
|
||||
"noKeyAssigned": "未分配密钥",
|
||||
"statusActive": "活跃",
|
||||
"statusNeedsSetup": "待配置"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue