mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-05 02:41:56 +00:00
feat(ui): i18n for playground chat_ui batch 1
This commit is contained in:
parent
cd8fbb5709
commit
da509c009a
9 changed files with 333 additions and 100 deletions
|
|
@ -12,6 +12,7 @@ import {
|
|||
FileTextOutlined,
|
||||
RobotOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export interface A2ATaskMetadata {
|
||||
taskId?: string;
|
||||
|
|
@ -80,6 +81,7 @@ const copyToClipboard = (text: string) => {
|
|||
};
|
||||
|
||||
const A2AMetrics: React.FC<A2AMetricsProps> = ({ a2aMetadata, timeToFirstToken, totalLatency }) => {
|
||||
const { t } = useTranslation();
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
|
||||
if (!a2aMetadata && !timeToFirstToken && !totalLatency) return null;
|
||||
|
|
@ -92,7 +94,7 @@ const A2AMetrics: React.FC<A2AMetricsProps> = ({ a2aMetadata, timeToFirstToken,
|
|||
{/* A2A Metadata Header */}
|
||||
<div className="flex items-center mb-2 text-gray-600">
|
||||
<RobotOutlined className="mr-1.5 text-blue-500" />
|
||||
<span className="font-medium text-gray-700">A2A Metadata</span>
|
||||
<span className="font-medium text-gray-700">{t("playground.a2AMetrics.header")}</span>
|
||||
</div>
|
||||
|
||||
{/* Main metrics row */}
|
||||
|
|
@ -119,7 +121,7 @@ const A2AMetrics: React.FC<A2AMetricsProps> = ({ a2aMetadata, timeToFirstToken,
|
|||
|
||||
{/* Latency */}
|
||||
{totalLatency !== undefined && (
|
||||
<Tooltip title="Total latency">
|
||||
<Tooltip title={t("playground.a2AMetrics.totalLatency")}>
|
||||
<span className="flex items-center text-blue-600">
|
||||
<ClockCircleOutlined className="mr-1" />
|
||||
{(totalLatency / 1000).toFixed(2)}s
|
||||
|
|
@ -129,7 +131,7 @@ const A2AMetrics: React.FC<A2AMetricsProps> = ({ a2aMetadata, timeToFirstToken,
|
|||
|
||||
{/* Time to first token */}
|
||||
{timeToFirstToken !== undefined && (
|
||||
<Tooltip title="Time to first token">
|
||||
<Tooltip title={t("playground.a2AMetrics.timeToFirstToken")}>
|
||||
<span className="flex items-center text-green-600">TTFT: {(timeToFirstToken / 1000).toFixed(2)}s</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
|
@ -139,13 +141,14 @@ const A2AMetrics: React.FC<A2AMetricsProps> = ({ a2aMetadata, timeToFirstToken,
|
|||
<div className="flex flex-wrap items-center gap-3 text-gray-500 ml-4 mt-1.5">
|
||||
{/* Task ID */}
|
||||
{taskId && (
|
||||
<Tooltip title={`Click to copy: ${taskId}`}>
|
||||
<Tooltip title={t("playground.a2AMetrics.clickToCopy", { id: taskId })}>
|
||||
<span
|
||||
className="flex items-center cursor-pointer hover:text-gray-700"
|
||||
onClick={() => copyToClipboard(taskId)}
|
||||
>
|
||||
<FileTextOutlined className="mr-1" />
|
||||
Task: {truncateId(taskId)}
|
||||
{t("playground.a2AMetrics.taskPrefix")}
|
||||
{truncateId(taskId)}
|
||||
<CopyOutlined className="ml-1 text-gray-400 hover:text-gray-600" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
|
@ -153,13 +156,14 @@ const A2AMetrics: React.FC<A2AMetricsProps> = ({ a2aMetadata, timeToFirstToken,
|
|||
|
||||
{/* Context/Session ID */}
|
||||
{contextId && (
|
||||
<Tooltip title={`Click to copy: ${contextId}`}>
|
||||
<Tooltip title={t("playground.a2AMetrics.clickToCopy", { id: contextId })}>
|
||||
<span
|
||||
className="flex items-center cursor-pointer hover:text-gray-700"
|
||||
onClick={() => copyToClipboard(contextId)}
|
||||
>
|
||||
<LinkOutlined className="mr-1" />
|
||||
Session: {truncateId(contextId)}
|
||||
{t("playground.a2AMetrics.sessionPrefix")}
|
||||
{truncateId(contextId)}
|
||||
<CopyOutlined className="ml-1 text-gray-400 hover:text-gray-600" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
|
@ -174,7 +178,7 @@ const A2AMetrics: React.FC<A2AMetricsProps> = ({ a2aMetadata, timeToFirstToken,
|
|||
onClick={() => setShowDetails(!showDetails)}
|
||||
>
|
||||
{showDetails ? <DownOutlined /> : <RightOutlined />}
|
||||
<span className="ml-1">Details</span>
|
||||
<span className="ml-1">{t("playground.a2AMetrics.details")}</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -185,7 +189,7 @@ const A2AMetrics: React.FC<A2AMetricsProps> = ({ a2aMetadata, timeToFirstToken,
|
|||
{/* Status message */}
|
||||
{status?.message && (
|
||||
<div className="mb-2">
|
||||
<span className="font-medium text-gray-700">Status Message:</span>
|
||||
<span className="font-medium text-gray-700">{t("playground.a2AMetrics.statusMessage")}</span>
|
||||
<span className="ml-2">{status.message}</span>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -193,7 +197,7 @@ const A2AMetrics: React.FC<A2AMetricsProps> = ({ a2aMetadata, timeToFirstToken,
|
|||
{/* Full IDs */}
|
||||
{taskId && (
|
||||
<div className="mb-1.5 flex items-center">
|
||||
<span className="font-medium text-gray-700 w-24">Task ID:</span>
|
||||
<span className="font-medium text-gray-700 w-24">{t("playground.a2AMetrics.taskId")}</span>
|
||||
<code className="ml-2 px-2 py-1 bg-white border border-gray-200 rounded text-xs font-mono">{taskId}</code>
|
||||
<CopyOutlined
|
||||
className="ml-2 cursor-pointer text-gray-400 hover:text-blue-500"
|
||||
|
|
@ -204,7 +208,7 @@ const A2AMetrics: React.FC<A2AMetricsProps> = ({ a2aMetadata, timeToFirstToken,
|
|||
|
||||
{contextId && (
|
||||
<div className="mb-1.5 flex items-center">
|
||||
<span className="font-medium text-gray-700 w-24">Session ID:</span>
|
||||
<span className="font-medium text-gray-700 w-24">{t("playground.a2AMetrics.sessionId")}</span>
|
||||
<code className="ml-2 px-2 py-1 bg-white border border-gray-200 rounded text-xs font-mono">
|
||||
{contextId}
|
||||
</code>
|
||||
|
|
@ -218,7 +222,7 @@ const A2AMetrics: React.FC<A2AMetricsProps> = ({ a2aMetadata, timeToFirstToken,
|
|||
{/* Metadata fields */}
|
||||
{metadata && Object.keys(metadata).length > 0 && (
|
||||
<div className="mt-3">
|
||||
<span className="font-medium text-gray-700">Custom Metadata:</span>
|
||||
<span className="font-medium text-gray-700">{t("playground.a2AMetrics.customMetadata")}</span>
|
||||
<pre className="mt-1.5 p-2 bg-white border border-gray-200 rounded text-xs font-mono overflow-x-auto whitespace-pre-wrap">
|
||||
{JSON.stringify(metadata, null, 2)}
|
||||
</pre>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { InfoCircleOutlined } from "@ant-design/icons";
|
|||
import { Text } from "@tremor/react";
|
||||
import { Checkbox, InputNumber, Popover, Slider, Tooltip, Typography } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
|
||||
interface AdditionalModelSettingsProps {
|
||||
temperature?: number;
|
||||
|
|
@ -51,6 +52,7 @@ const AdditionalModelSettings: React.FC<AdditionalModelSettingsProps> = ({
|
|||
onMaxTokensChange?.(newValue);
|
||||
};
|
||||
|
||||
const { t } = useTranslation();
|
||||
const disabledOpacity = useAdvancedParams ? 1 : 0.4;
|
||||
const disabledTextColor = useAdvancedParams ? "text-gray-700" : "text-gray-400";
|
||||
|
||||
|
|
@ -65,13 +67,13 @@ const AdditionalModelSettings: React.FC<AdditionalModelSettingsProps> = ({
|
|||
return (
|
||||
<div className="space-y-4 p-4 w-80">
|
||||
<Checkbox checked={useAdvancedParams} onChange={(e) => handleUseAdvancedParamsChange(e.target.checked)}>
|
||||
<span className="font-medium">Use Advanced Parameters</span>
|
||||
<span className="font-medium">{t("playground.additionalModelSettings.useAdvancedParams")}</span>
|
||||
</Checkbox>
|
||||
|
||||
{onMockTestFallbacksChange && (
|
||||
<div className="flex items-center gap-1">
|
||||
<Checkbox checked={mockTestFallbacks ?? false} onChange={(e) => onMockTestFallbacksChange(e.target.checked)}>
|
||||
<span className="font-medium">Simulate failure to test fallbacks</span>
|
||||
<span className="font-medium">{t("playground.additionalModelSettings.simulateFailure")}</span>
|
||||
</Checkbox>
|
||||
<Popover
|
||||
trigger="hover"
|
||||
|
|
@ -79,26 +81,29 @@ const AdditionalModelSettings: React.FC<AdditionalModelSettingsProps> = ({
|
|||
content={
|
||||
<div style={{ maxWidth: 340 }}>
|
||||
<Typography.Paragraph className="text-sm" style={{ marginBottom: 8 }}>
|
||||
Causes the first request to fail so the router tries fallbacks (if configured). Use this to verify
|
||||
your fallback setup.
|
||||
{t("playground.additionalModelSettings.simulateFailureDesc")}
|
||||
</Typography.Paragraph>
|
||||
<Typography.Paragraph className="text-sm" style={{ marginBottom: 0 }}>
|
||||
Behavior can differ when keys, teams, or router settings are configured.{" "}
|
||||
<a
|
||||
href="https://docs.litellm.ai/docs/proxy/keys_teams_router_settings"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
<Trans
|
||||
i18nKey="playground.additionalModelSettings.simulateFailureNote"
|
||||
components={{
|
||||
learnMoreLink: (
|
||||
<a
|
||||
href="https://docs.litellm.ai/docs/proxy/keys_teams_router_settings"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-600 hover:text-blue-800"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Typography.Paragraph>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<InfoCircleOutlined
|
||||
className="text-xs text-gray-400 cursor-pointer shrink-0 hover:text-gray-600"
|
||||
aria-label="Help: Simulate failure to test fallbacks"
|
||||
aria-label={t("playground.additionalModelSettings.simulateFailureAriaLabel")}
|
||||
/>
|
||||
</Popover>
|
||||
</div>
|
||||
|
|
@ -108,8 +113,10 @@ const AdditionalModelSettings: React.FC<AdditionalModelSettingsProps> = ({
|
|||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-1">
|
||||
<Text className={`text-sm ${disabledTextColor}`}>Temperature</Text>
|
||||
<Tooltip title="Controls randomness. Lower values make output more deterministic, higher values more creative.">
|
||||
<Text className={`text-sm ${disabledTextColor}`}>
|
||||
{t("playground.additionalModelSettings.temperature")}
|
||||
</Text>
|
||||
<Tooltip title={t("playground.additionalModelSettings.temperatureTooltip")}>
|
||||
<InfoCircleOutlined className={`text-xs ${disabledTextColor} cursor-help`} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
|
@ -142,8 +149,10 @@ const AdditionalModelSettings: React.FC<AdditionalModelSettingsProps> = ({
|
|||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-1">
|
||||
<Text className={`text-sm ${disabledTextColor}`}>Max Tokens</Text>
|
||||
<Tooltip title="Maximum number of tokens to generate in the response.">
|
||||
<Text className={`text-sm ${disabledTextColor}`}>
|
||||
{t("playground.additionalModelSettings.maxTokens")}
|
||||
</Text>
|
||||
<Tooltip title={t("playground.additionalModelSettings.maxTokensTooltip")}>
|
||||
<InfoCircleOutlined className={`text-xs ${disabledTextColor} cursor-help`} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
} from "@ant-design/icons";
|
||||
import { Button, Input, Modal, Select, Spin, Tabs } from "antd";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import CodeBlock from "@/app/(dashboard)/api-reference/components/CodeBlock";
|
||||
import NotificationsManager from "../../molecules/notifications_manager";
|
||||
import { keyCreateCall, modelCreateCall, modelDeleteCall, modelPatchUpdateCall, proxyBaseUrl } from "../../networking";
|
||||
|
|
@ -71,6 +72,7 @@ function ConnectTabContent({
|
|||
createdKeyValue,
|
||||
onCreateKey,
|
||||
}: ConnectTabContentProps) {
|
||||
const { t } = useTranslation();
|
||||
const baseUrl = proxyBaseUrl ?? getConnectTabBaseUrl(proxySettings, customProxyBaseUrl);
|
||||
const apiKeyForCurl = createdKeyValue
|
||||
? createdKeyValue.startsWith("Bearer ")
|
||||
|
|
@ -95,31 +97,32 @@ function ConnectTabContent({
|
|||
return (
|
||||
<div className="mx-auto max-w-3xl space-y-6">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-gray-900 mb-1">Proxy base URL</h3>
|
||||
<h3 className="text-sm font-semibold text-gray-900 mb-1">{t("playground.agentBuilderView.proxyBaseUrl")}</h3>
|
||||
<p className="text-sm text-gray-600 font-mono bg-gray-50 px-2 py-1.5 rounded border border-gray-200 break-all">
|
||||
{baseUrl}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-gray-900 mb-2">Call your agent (cURL)</h3>
|
||||
<h3 className="text-sm font-semibold text-gray-900 mb-2">{t("playground.agentBuilderView.callYourAgent")}</h3>
|
||||
<CodeBlock code={curlExample} language="bash" />
|
||||
</div>
|
||||
<div className="rounded-lg border border-gray-200 bg-gray-50 p-4">
|
||||
<h3 className="text-sm font-semibold text-gray-900 mb-2">Create a key for this agent</h3>
|
||||
<h3 className="text-sm font-semibold text-gray-900 mb-2">{t("playground.agentBuilderView.createKeyTitle")}</h3>
|
||||
<p className="text-sm text-gray-600 mb-3">
|
||||
Create a virtual key that can only call this agent. The key will be scoped to you (user_id) and restricted to
|
||||
the model <span className="font-mono text-gray-800">{agentName}</span>.
|
||||
<Trans
|
||||
i18nKey="playground.agentBuilderView.createKeyDesc"
|
||||
values={{ agentName }}
|
||||
components={{ agentCode: <span className="font-mono text-gray-800" /> }}
|
||||
/>
|
||||
</p>
|
||||
<Button type="primary" onClick={onCreateKey} loading={creatingKey} disabled={disabledPersonalKeyCreation}>
|
||||
Create key for this agent
|
||||
{t("playground.agentBuilderView.createKeyButton")}
|
||||
</Button>
|
||||
{disabledPersonalKeyCreation && (
|
||||
<p className="text-xs text-amber-600 mt-2">Key creation is disabled for your account.</p>
|
||||
<p className="text-xs text-amber-600 mt-2">{t("playground.agentBuilderView.keyCreationDisabled")}</p>
|
||||
)}
|
||||
{createdKeyValue && (
|
||||
<p className="text-xs text-green-700 mt-2">
|
||||
Key created. It is shown in the cURL example above — copy the snippet to use it.
|
||||
</p>
|
||||
<p className="text-xs text-green-700 mt-2">{t("playground.agentBuilderView.keyCreated")}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -180,6 +183,7 @@ export default function AgentBuilderView({
|
|||
apiKey,
|
||||
customProxyBaseUrl,
|
||||
}: AgentBuilderViewProps) {
|
||||
const { t } = useTranslation();
|
||||
const [agentModels, setAgentModels] = useState<AgentModel[]>([]);
|
||||
const [modelGroups, setModelGroups] = useState<ModelGroup[]>([]);
|
||||
const [loadingAgents, setLoadingAgents] = useState(true);
|
||||
|
|
@ -220,7 +224,7 @@ export default function AgentBuilderView({
|
|||
return list;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
NotificationsManager.fromBackend("Failed to load agents");
|
||||
NotificationsManager.fromBackend(t("playground.agentBuilderView.failedToLoadAgents"));
|
||||
return [];
|
||||
} finally {
|
||||
setLoadingAgents(false);
|
||||
|
|
@ -303,7 +307,7 @@ export default function AgentBuilderView({
|
|||
const handleAddAgent = () => {
|
||||
setSelectedId(NEW_AGENT_ID);
|
||||
setDraftName("");
|
||||
setDraftSystemPrompt("You are a helpful assistant.");
|
||||
setDraftSystemPrompt(t("playground.agentBuilderView.defaultSystemPrompt"));
|
||||
setDraftUnderlyingModel(modelGroups[0]?.model_group);
|
||||
setDraftTemperature(0.7);
|
||||
setDraftMaxTokens(4096);
|
||||
|
|
@ -313,7 +317,7 @@ export default function AgentBuilderView({
|
|||
|
||||
const handleSaveAgent = async () => {
|
||||
if (!accessToken || !draftName?.trim() || !draftUnderlyingModel) {
|
||||
NotificationsManager.fromBackend("Name and underlying model are required");
|
||||
NotificationsManager.fromBackend(t("playground.agentBuilderView.nameAndModelRequired"));
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
|
|
@ -340,7 +344,7 @@ export default function AgentBuilderView({
|
|||
setSelectedId(created ? getAgentSelectionKey(created) : list[0] ? getAgentSelectionKey(list[0]) : null);
|
||||
setActiveTab("chat");
|
||||
} catch (e) {
|
||||
NotificationsManager.fromBackend("Failed to save agent");
|
||||
NotificationsManager.fromBackend(t("playground.agentBuilderView.failedToSaveAgent"));
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
|
|
@ -348,7 +352,7 @@ export default function AgentBuilderView({
|
|||
|
||||
const handleUpdateAgent = async () => {
|
||||
if (!accessToken || !selectedAgent || !selectedAgentModelId || !draftName?.trim() || !draftUnderlyingModel) {
|
||||
NotificationsManager.fromBackend("Name and underlying model are required");
|
||||
NotificationsManager.fromBackend(t("playground.agentBuilderView.nameAndModelRequired"));
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
|
|
@ -368,13 +372,13 @@ export default function AgentBuilderView({
|
|||
},
|
||||
selectedAgentModelId,
|
||||
);
|
||||
NotificationsManager.success("Agent updated successfully");
|
||||
NotificationsManager.success(t("playground.agentBuilderView.agentUpdated"));
|
||||
const list = await loadAgents();
|
||||
const stillSelected = list.find((a) => getAgentModelId(a) === selectedAgentModelId);
|
||||
const target = stillSelected ?? list[0];
|
||||
setSelectedId(target ? getAgentSelectionKey(target) : null);
|
||||
} catch (e) {
|
||||
NotificationsManager.fromBackend("Failed to update agent");
|
||||
NotificationsManager.fromBackend(t("playground.agentBuilderView.failedToUpdateAgent"));
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
|
|
@ -392,12 +396,12 @@ export default function AgentBuilderView({
|
|||
const keyValue = response?.key ?? null;
|
||||
if (keyValue) {
|
||||
setCreatedKeyValue(keyValue);
|
||||
NotificationsManager.success("Virtual key created. Use it in the curl example below.");
|
||||
NotificationsManager.success(t("playground.agentBuilderView.virtualKeyCreated"));
|
||||
} else {
|
||||
NotificationsManager.fromBackend("Key created but value not returned");
|
||||
NotificationsManager.fromBackend(t("playground.agentBuilderView.keyCreatedNoValue"));
|
||||
}
|
||||
} catch (e) {
|
||||
NotificationsManager.fromBackend("Failed to create key for agent");
|
||||
NotificationsManager.fromBackend(t("playground.agentBuilderView.failedToCreateKey"));
|
||||
} finally {
|
||||
setCreatingKey(false);
|
||||
}
|
||||
|
|
@ -406,21 +410,21 @@ export default function AgentBuilderView({
|
|||
const handleDeleteAgent = () => {
|
||||
if (!selectedAgent || !selectedAgentModelId || !accessToken) return;
|
||||
Modal.confirm({
|
||||
title: "Delete agent",
|
||||
content: `Are you sure you want to delete "${selectedAgent.model_name}"? This cannot be undone.`,
|
||||
okText: "Delete",
|
||||
title: t("playground.agentBuilderView.deleteAgentTitle"),
|
||||
content: t("playground.agentBuilderView.deleteAgentConfirm", { name: selectedAgent.model_name }),
|
||||
okText: t("common.delete"),
|
||||
okType: "danger",
|
||||
cancelText: "Cancel",
|
||||
cancelText: t("common.cancel"),
|
||||
onOk: async () => {
|
||||
setDeleting(true);
|
||||
try {
|
||||
await modelDeleteCall(accessToken, selectedAgentModelId);
|
||||
NotificationsManager.success("Agent deleted");
|
||||
NotificationsManager.success(t("playground.agentBuilderView.agentDeleted"));
|
||||
const list = await loadAgents();
|
||||
const remaining = list.filter((a) => getAgentModelId(a) !== selectedAgentModelId);
|
||||
setSelectedId(remaining.length > 0 ? getAgentSelectionKey(remaining[0]) : null);
|
||||
} catch (e) {
|
||||
NotificationsManager.fromBackend("Failed to delete agent");
|
||||
NotificationsManager.fromBackend(t("playground.agentBuilderView.failedToDeleteAgent"));
|
||||
} finally {
|
||||
setDeleting(false);
|
||||
}
|
||||
|
|
@ -430,7 +434,9 @@ export default function AgentBuilderView({
|
|||
|
||||
if (!accessToken || !userID || !userRole) {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center p-8 text-gray-500">Sign in to use Agent Builder.</div>
|
||||
<div className="flex h-full items-center justify-center p-8 text-gray-500">
|
||||
{t("playground.agentBuilderView.signInRequired")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -438,7 +444,7 @@ export default function AgentBuilderView({
|
|||
<div className="flex h-full flex-col bg-white text-gray-900">
|
||||
<div className="flex flex-shrink-0 flex-col border-b border-gray-200">
|
||||
<div className="flex h-12 items-center justify-between px-4">
|
||||
<span className="text-sm font-medium text-gray-900">Agent Builder</span>
|
||||
<span className="text-sm font-medium text-gray-900">{t("playground.agentBuilderView.title")}</span>
|
||||
{isNewAgent ? (
|
||||
<Button
|
||||
type="primary"
|
||||
|
|
@ -447,21 +453,26 @@ export default function AgentBuilderView({
|
|||
loading={saving}
|
||||
disabled={!draftName?.trim() || !draftUnderlyingModel}
|
||||
>
|
||||
Save Agent
|
||||
{t("playground.agentBuilderView.saveAgent")}
|
||||
</Button>
|
||||
) : (
|
||||
<span className="text-xs text-gray-500">Build Agents that pass your compliance requirements.</span>
|
||||
<span className="text-xs text-gray-500">{t("playground.agentBuilderView.buildAgentsSubtitle")}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 border-t border-amber-200 bg-amber-50 px-4 py-2 text-xs text-amber-800">
|
||||
<ExperimentOutlined className="flex-shrink-0 text-amber-600" />
|
||||
<span>
|
||||
Agent Builder is experimental and may change or be removed without notice. We’d love your feedback—email us
|
||||
at{" "}
|
||||
<a href="mailto:product@berri.ai" className="font-medium text-amber-900 underline hover:text-amber-700">
|
||||
product@berri.ai
|
||||
</a>
|
||||
.
|
||||
<Trans
|
||||
i18nKey="playground.agentBuilderView.experimentalBanner"
|
||||
components={{
|
||||
emailLink: (
|
||||
<a
|
||||
href="mailto:product@berri.ai"
|
||||
className="font-medium text-amber-900 underline hover:text-amber-700"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -470,8 +481,16 @@ export default function AgentBuilderView({
|
|||
{/* Roster */}
|
||||
<div className="w-60 flex-shrink-0 border-r border-gray-200 bg-white flex flex-col">
|
||||
<div className="flex items-center justify-between border-b border-gray-200 p-3">
|
||||
<span className="text-xs font-semibold uppercase tracking-wide text-gray-500">Agents</span>
|
||||
<Button type="text" size="small" icon={<PlusOutlined />} onClick={handleAddAgent} aria-label="Add agent" />
|
||||
<span className="text-xs font-semibold uppercase tracking-wide text-gray-500">
|
||||
{t("playground.agentBuilderView.agentsLabel")}
|
||||
</span>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={handleAddAgent}
|
||||
aria-label={t("playground.agentBuilderView.addAgentAriaLabel")}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto p-2">
|
||||
{loadingAgents ? (
|
||||
|
|
@ -503,7 +522,7 @@ export default function AgentBuilderView({
|
|||
onClick={handleAddAgent}
|
||||
className="mb-1 w-full rounded-md border border-dashed border-gray-300 px-3 py-2 text-left text-sm text-gray-500 hover:border-blue-400 hover:bg-blue-50/50 hover:text-gray-700"
|
||||
>
|
||||
<PlusOutlined className="mr-1" /> New agent
|
||||
<PlusOutlined className="mr-1" /> {t("playground.agentBuilderView.newAgent")}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -514,7 +533,7 @@ export default function AgentBuilderView({
|
|||
<div className="flex flex-1 flex-col overflow-hidden">
|
||||
{selectedId === null && !isNewAgent && agentModels.length === 0 && !loadingAgents && (
|
||||
<div className="flex flex-1 items-center justify-center p-8 text-gray-500">
|
||||
No agents yet. Add an agent to get started.
|
||||
{t("playground.agentBuilderView.noAgentsYet")}
|
||||
</div>
|
||||
)}
|
||||
{(selectedId !== null || isNewAgent) && (
|
||||
|
|
@ -528,7 +547,7 @@ export default function AgentBuilderView({
|
|||
key: "configure",
|
||||
label: (
|
||||
<span>
|
||||
<RobotOutlined className="mr-1" /> Configure
|
||||
<RobotOutlined className="mr-1" /> {t("playground.agentBuilderView.tabConfigure")}
|
||||
</span>
|
||||
),
|
||||
children: (
|
||||
|
|
@ -537,40 +556,47 @@ export default function AgentBuilderView({
|
|||
<div className="mx-auto max-w-xl space-y-4">
|
||||
{!selectedAgentModelId && selectedAgent && (
|
||||
<div className="rounded border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800">
|
||||
This agent cannot be updated or deleted here (missing model id). Manage it from Models
|
||||
& Endpoints.
|
||||
{t("playground.agentBuilderView.missingModelIdWarning")}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700">Agent name</label>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700">
|
||||
{t("playground.agentBuilderView.agentNameLabel")}
|
||||
</label>
|
||||
<Input
|
||||
value={draftName}
|
||||
onChange={(e) => setDraftName(e.target.value)}
|
||||
placeholder="My Agent"
|
||||
placeholder={t("playground.agentBuilderView.agentNamePlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700">System prompt</label>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700">
|
||||
{t("playground.agentBuilderView.systemPromptLabel")}
|
||||
</label>
|
||||
<TextArea
|
||||
value={draftSystemPrompt}
|
||||
onChange={(e) => setDraftSystemPrompt(e.target.value)}
|
||||
placeholder="You are a helpful assistant..."
|
||||
placeholder={t("playground.agentBuilderView.systemPromptPlaceholder")}
|
||||
rows={6}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700">Underlying LLM</label>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700">
|
||||
{t("playground.agentBuilderView.underlyingLlmLabel")}
|
||||
</label>
|
||||
<Select
|
||||
value={draftUnderlyingModel}
|
||||
onChange={setDraftUnderlyingModel}
|
||||
className="w-full"
|
||||
options={modelGroups.map((m) => ({ value: m.model_group, label: m.model_group }))}
|
||||
placeholder="Select model"
|
||||
placeholder={t("common.select")}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700">Temperature</label>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700">
|
||||
{t("playground.agentBuilderView.temperatureLabel")}
|
||||
</label>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
|
|
@ -581,7 +607,9 @@ export default function AgentBuilderView({
|
|||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700">Max tokens</label>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700">
|
||||
{t("playground.agentBuilderView.maxTokensLabel")}
|
||||
</label>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
|
|
@ -591,10 +619,12 @@ export default function AgentBuilderView({
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700">MCP servers</label>
|
||||
<label className="mb-1 block text-sm font-medium text-gray-700">
|
||||
{t("playground.agentBuilderView.mcpServersLabel")}
|
||||
</label>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder="Select MCP servers to attach (same format as chat completions API)"
|
||||
placeholder={t("playground.agentBuilderView.mcpServersPlaceholder")}
|
||||
value={selectedMCPServerIds}
|
||||
onChange={handleMCPServerChange}
|
||||
loading={loadingMCPServers}
|
||||
|
|
@ -609,9 +639,11 @@ export default function AgentBuilderView({
|
|||
/>
|
||||
{selectedAgent && draftTools.length > 0 && (
|
||||
<p className="mt-1 text-xs text-gray-500">
|
||||
{draftTools.length} MCP server{draftTools.length !== 1 ? "s" : ""} saved. Use the same{" "}
|
||||
<code className="rounded bg-gray-100 px-1">tools</code> array in chat completions when
|
||||
calling this agent.
|
||||
<Trans
|
||||
i18nKey="playground.agentBuilderView.mcpServersSaved"
|
||||
count={draftTools.length}
|
||||
components={{ toolsCode: <code className="rounded bg-gray-100 px-1" /> }}
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -626,7 +658,7 @@ export default function AgentBuilderView({
|
|||
loading={saving}
|
||||
disabled={!draftName?.trim() || !draftUnderlyingModel}
|
||||
>
|
||||
Update Agent
|
||||
{t("playground.agentBuilderView.updateAgent")}
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
|
|
@ -635,12 +667,12 @@ export default function AgentBuilderView({
|
|||
onClick={handleDeleteAgent}
|
||||
loading={deleting}
|
||||
>
|
||||
Delete
|
||||
{t("common.delete")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<Button type="primary" icon={<CommentOutlined />} onClick={() => setActiveTab("chat")}>
|
||||
Test in Chat
|
||||
{t("playground.agentBuilderView.testInChat")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -653,7 +685,7 @@ export default function AgentBuilderView({
|
|||
key: "chat",
|
||||
label: (
|
||||
<span>
|
||||
<CommentOutlined className="mr-1" /> Chat
|
||||
<CommentOutlined className="mr-1" /> {t("playground.agentBuilderView.tabChat")}
|
||||
</span>
|
||||
),
|
||||
disabled: isNewAgent,
|
||||
|
|
@ -673,7 +705,7 @@ export default function AgentBuilderView({
|
|||
/>
|
||||
) : (
|
||||
<div className="flex flex-1 items-center justify-center text-gray-500">
|
||||
Save an agent first to test in Chat.
|
||||
{t("playground.agentBuilderView.saveAgentFirst")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -683,7 +715,7 @@ export default function AgentBuilderView({
|
|||
key: "test",
|
||||
label: (
|
||||
<span>
|
||||
<ExperimentOutlined className="mr-1" /> Batch Test
|
||||
<ExperimentOutlined className="mr-1" /> {t("playground.agentBuilderView.tabBatchTest")}
|
||||
</span>
|
||||
),
|
||||
disabled: isNewAgent,
|
||||
|
|
@ -699,7 +731,7 @@ export default function AgentBuilderView({
|
|||
/>
|
||||
) : (
|
||||
<div className="flex flex-1 items-center justify-center text-gray-500">
|
||||
Select an agent to run batch tests.
|
||||
{t("playground.agentBuilderView.selectAgentForBatchTest")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -709,7 +741,7 @@ export default function AgentBuilderView({
|
|||
key: "connect",
|
||||
label: (
|
||||
<span>
|
||||
<LinkOutlined className="mr-1" /> Connect
|
||||
<LinkOutlined className="mr-1" /> {t("playground.agentBuilderView.tabConnect")}
|
||||
</span>
|
||||
),
|
||||
disabled: isNewAgent,
|
||||
|
|
@ -729,7 +761,7 @@ export default function AgentBuilderView({
|
|||
/>
|
||||
) : (
|
||||
<div className="flex flex-1 items-center justify-center text-gray-500">
|
||||
Select an agent to see how to connect.
|
||||
{t("playground.agentBuilderView.selectAgentToConnect")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MessageType } from "./types";
|
||||
|
||||
interface AudioRendererProps {
|
||||
|
|
@ -6,6 +7,7 @@ interface AudioRendererProps {
|
|||
}
|
||||
|
||||
const AudioRenderer: React.FC<AudioRendererProps> = ({ message }) => {
|
||||
const { t } = useTranslation();
|
||||
// Check if this message contains audio
|
||||
if (!message.isAudio || typeof message.content !== "string") {
|
||||
return null;
|
||||
|
|
@ -14,7 +16,7 @@ const AudioRenderer: React.FC<AudioRendererProps> = ({ message }) => {
|
|||
return (
|
||||
<div className="mb-2">
|
||||
<audio controls src={message.content} className="max-w-full" style={{ maxWidth: "500px" }}>
|
||||
Your browser does not support the audio element.
|
||||
{t("playground.audioRenderer.browserNotSupported")}
|
||||
</audio>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MessageType } from "./types";
|
||||
import { shouldShowChatAttachedImage } from "./ChatImageUtils";
|
||||
import { FilePdfOutlined } from "@ant-design/icons";
|
||||
|
|
@ -9,6 +10,7 @@ interface ChatImageRendererProps {
|
|||
}
|
||||
|
||||
const ChatImageRenderer: React.FC<ChatImageRendererProps> = ({ message }) => {
|
||||
const { t } = useTranslation();
|
||||
if (!shouldShowChatAttachedImage(message)) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -24,7 +26,7 @@ const ChatImageRenderer: React.FC<ChatImageRendererProps> = ({ message }) => {
|
|||
) : (
|
||||
<Image
|
||||
src={message.imagePreviewUrl || ""}
|
||||
alt="User uploaded image"
|
||||
alt={t("playground.chatImageRenderer.uploadedImageAlt")}
|
||||
width={256}
|
||||
height={200}
|
||||
className="max-w-64 rounded-md border border-gray-200 shadow-sm"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React from "react";
|
||||
import { Upload, Tooltip } from "antd";
|
||||
import { PaperClipOutlined } from "@ant-design/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const { Dragger } = Upload;
|
||||
|
||||
|
|
@ -17,6 +18,7 @@ const ChatImageUpload: React.FC<ChatImageUploadProps> = ({
|
|||
onImageUpload,
|
||||
onRemoveImage,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
{/* Subtle upload button - only show when no image */}
|
||||
|
|
@ -28,7 +30,7 @@ const ChatImageUpload: React.FC<ChatImageUploadProps> = ({
|
|||
className="inline-block"
|
||||
style={{ padding: 0, border: "none", background: "none" }}
|
||||
>
|
||||
<Tooltip title="Attach image or PDF">
|
||||
<Tooltip title={t("playground.chatImageUpload.attachTooltip")}>
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center justify-center w-8 h-8 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-md transition-colors"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { RobotOutlined, UserOutlined } from "@ant-design/icons";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { coy } from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||
|
|
@ -38,6 +39,7 @@ function ChatMessageBubble({
|
|||
codeInterpreterResult,
|
||||
accessToken,
|
||||
}: ChatMessageBubbleProps) {
|
||||
const { t } = useTranslation();
|
||||
const isUser = message.role === "user";
|
||||
|
||||
return (
|
||||
|
|
@ -114,7 +116,7 @@ function ChatMessageBubble({
|
|||
{message.isImage ? (
|
||||
<img
|
||||
src={typeof message.content === "string" ? message.content : ""}
|
||||
alt="Generated image"
|
||||
alt={t("playground.chatMessageBubble.generatedImageAlt")}
|
||||
className="max-w-full rounded-md border border-gray-200 shadow-sm"
|
||||
style={{ maxHeight: "500px" }}
|
||||
/>
|
||||
|
|
@ -172,7 +174,7 @@ function ChatMessageBubble({
|
|||
<div className="mt-3">
|
||||
<img
|
||||
src={message.image.url}
|
||||
alt="Generated image"
|
||||
alt={t("playground.chatMessageBubble.generatedImageAlt")}
|
||||
className="max-w-full rounded-md border border-gray-200 shadow-sm"
|
||||
style={{ maxHeight: "500px" }}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -3768,5 +3768,95 @@
|
|||
"emptyNoMatch": "No servers match the current filters or search.",
|
||||
"deleteSuccess": "Deleted MCP Server successfully"
|
||||
}
|
||||
},
|
||||
"playground": {
|
||||
"a2AMetrics": {
|
||||
"header": "A2A Metadata",
|
||||
"totalLatency": "Total latency",
|
||||
"timeToFirstToken": "Time to first token",
|
||||
"clickToCopy": "Click to copy: {{id}}",
|
||||
"taskPrefix": "Task: ",
|
||||
"sessionPrefix": "Session: ",
|
||||
"details": "Details",
|
||||
"statusMessage": "Status Message:",
|
||||
"taskId": "Task ID:",
|
||||
"sessionId": "Session ID:",
|
||||
"customMetadata": "Custom Metadata:"
|
||||
},
|
||||
"additionalModelSettings": {
|
||||
"useAdvancedParams": "Use Advanced Parameters",
|
||||
"simulateFailure": "Simulate failure to test fallbacks",
|
||||
"simulateFailureDesc": "Causes the first request to fail so the router tries fallbacks (if configured). Use this to verify your fallback setup.",
|
||||
"simulateFailureNote": "Behavior can differ when keys, teams, or router settings are configured. <learnMoreLink>Learn more</learnMoreLink>",
|
||||
"simulateFailureAriaLabel": "Help: Simulate failure to test fallbacks",
|
||||
"temperature": "Temperature",
|
||||
"temperatureTooltip": "Controls randomness. Lower values make output more deterministic, higher values more creative.",
|
||||
"maxTokens": "Max Tokens",
|
||||
"maxTokensTooltip": "Maximum number of tokens to generate in the response."
|
||||
},
|
||||
"agentBuilderView": {
|
||||
"title": "Agent Builder",
|
||||
"saveAgent": "Save Agent",
|
||||
"buildAgentsSubtitle": "Build Agents that pass your compliance requirements.",
|
||||
"experimentalBanner": "Agent Builder is experimental and may change or be removed without notice. We'd love your feedback—email us at <emailLink>product@berri.ai</emailLink>.",
|
||||
"agentsLabel": "Agents",
|
||||
"addAgentAriaLabel": "Add agent",
|
||||
"newAgent": "New agent",
|
||||
"noAgentsYet": "No agents yet. Add an agent to get started.",
|
||||
"signInRequired": "Sign in to use Agent Builder.",
|
||||
"tabConfigure": "Configure",
|
||||
"tabChat": "Chat",
|
||||
"tabBatchTest": "Batch Test",
|
||||
"tabConnect": "Connect",
|
||||
"missingModelIdWarning": "This agent cannot be updated or deleted here (missing model id). Manage it from Models & Endpoints.",
|
||||
"agentNameLabel": "Agent name",
|
||||
"agentNamePlaceholder": "My Agent",
|
||||
"systemPromptLabel": "System prompt",
|
||||
"systemPromptPlaceholder": "You are a helpful assistant...",
|
||||
"defaultSystemPrompt": "You are a helpful assistant.",
|
||||
"underlyingLlmLabel": "Underlying LLM",
|
||||
"temperatureLabel": "Temperature",
|
||||
"maxTokensLabel": "Max tokens",
|
||||
"mcpServersLabel": "MCP servers",
|
||||
"mcpServersPlaceholder": "Select MCP servers to attach (same format as chat completions API)",
|
||||
"mcpServersSaved_one": "{{count}} MCP server saved. Use the same <toolsCode>tools</toolsCode> array in chat completions when calling this agent.",
|
||||
"mcpServersSaved_other": "{{count}} MCP servers saved. Use the same <toolsCode>tools</toolsCode> array in chat completions when calling this agent.",
|
||||
"updateAgent": "Update Agent",
|
||||
"testInChat": "Test in Chat",
|
||||
"saveAgentFirst": "Save an agent first to test in Chat.",
|
||||
"selectAgentForBatchTest": "Select an agent to run batch tests.",
|
||||
"selectAgentToConnect": "Select an agent to see how to connect.",
|
||||
"proxyBaseUrl": "Proxy base URL",
|
||||
"callYourAgent": "Call your agent (cURL)",
|
||||
"createKeyTitle": "Create a key for this agent",
|
||||
"createKeyDesc": "Create a virtual key that can only call this agent. The key will be scoped to you (user_id) and restricted to the model <agentCode>{{agentName}}</agentCode>.",
|
||||
"createKeyButton": "Create key for this agent",
|
||||
"keyCreationDisabled": "Key creation is disabled for your account.",
|
||||
"keyCreated": "Key created. It is shown in the cURL example above — copy the snippet to use it.",
|
||||
"failedToLoadAgents": "Failed to load agents",
|
||||
"nameAndModelRequired": "Name and underlying model are required",
|
||||
"failedToSaveAgent": "Failed to save agent",
|
||||
"agentUpdated": "Agent updated successfully",
|
||||
"failedToUpdateAgent": "Failed to update agent",
|
||||
"virtualKeyCreated": "Virtual key created. Use it in the curl example below.",
|
||||
"keyCreatedNoValue": "Key created but value not returned",
|
||||
"failedToCreateKey": "Failed to create key for agent",
|
||||
"deleteAgentTitle": "Delete agent",
|
||||
"deleteAgentConfirm": "Are you sure you want to delete \"{{name}}\"? This cannot be undone.",
|
||||
"agentDeleted": "Agent deleted",
|
||||
"failedToDeleteAgent": "Failed to delete agent"
|
||||
},
|
||||
"audioRenderer": {
|
||||
"browserNotSupported": "Your browser does not support the audio element."
|
||||
},
|
||||
"chatImageRenderer": {
|
||||
"uploadedImageAlt": "User uploaded image"
|
||||
},
|
||||
"chatImageUpload": {
|
||||
"attachTooltip": "Attach image or PDF"
|
||||
},
|
||||
"chatMessageBubble": {
|
||||
"generatedImageAlt": "Generated image"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3768,5 +3768,95 @@
|
|||
"emptyNoMatch": "没有服务器匹配当前过滤条件或搜索。",
|
||||
"deleteSuccess": "MCP 服务器已成功删除"
|
||||
}
|
||||
},
|
||||
"playground": {
|
||||
"a2AMetrics": {
|
||||
"header": "A2A 元数据",
|
||||
"totalLatency": "总延迟",
|
||||
"timeToFirstToken": "首 Token 时间",
|
||||
"clickToCopy": "点击复制:{{id}}",
|
||||
"taskPrefix": "任务:",
|
||||
"sessionPrefix": "会话:",
|
||||
"details": "详情",
|
||||
"statusMessage": "状态消息:",
|
||||
"taskId": "任务 ID:",
|
||||
"sessionId": "会话 ID:",
|
||||
"customMetadata": "自定义元数据:"
|
||||
},
|
||||
"additionalModelSettings": {
|
||||
"useAdvancedParams": "使用高级参数",
|
||||
"simulateFailure": "模拟失败以测试回退",
|
||||
"simulateFailureDesc": "使第一个请求失败,以便路由器尝试回退(如已配置)。用于验证您的回退设置。",
|
||||
"simulateFailureNote": "当配置了密钥、团队或路由器设置时,行为可能有所不同。<learnMoreLink>了解更多</learnMoreLink>",
|
||||
"simulateFailureAriaLabel": "帮助:模拟失败以测试回退",
|
||||
"temperature": "Temperature",
|
||||
"temperatureTooltip": "控制随机性。值越低输出越确定,值越高输出越有创意。",
|
||||
"maxTokens": "最大 Token 数",
|
||||
"maxTokensTooltip": "响应中生成的最大 Token 数。"
|
||||
},
|
||||
"agentBuilderView": {
|
||||
"title": "Agent Builder",
|
||||
"saveAgent": "保存 Agent",
|
||||
"buildAgentsSubtitle": "构建符合合规要求的 Agent。",
|
||||
"experimentalBanner": "Agent Builder 为实验性功能,可能随时更改或移除。欢迎反馈——请发送邮件至 <emailLink>product@berri.ai</emailLink>。",
|
||||
"agentsLabel": "Agents",
|
||||
"addAgentAriaLabel": "添加 Agent",
|
||||
"newAgent": "新建 Agent",
|
||||
"noAgentsYet": "暂无 Agent。添加一个 Agent 以开始使用。",
|
||||
"signInRequired": "请登录以使用 Agent Builder。",
|
||||
"tabConfigure": "配置",
|
||||
"tabChat": "聊天",
|
||||
"tabBatchTest": "批量测试",
|
||||
"tabConnect": "连接",
|
||||
"missingModelIdWarning": "此 Agent 无法在此处更新或删除(缺少模型 ID)。请从模型与端点页面管理。",
|
||||
"agentNameLabel": "Agent 名称",
|
||||
"agentNamePlaceholder": "我的 Agent",
|
||||
"systemPromptLabel": "系统提示词",
|
||||
"systemPromptPlaceholder": "你是一个有帮助的助手...",
|
||||
"defaultSystemPrompt": "You are a helpful assistant.",
|
||||
"underlyingLlmLabel": "底层 LLM",
|
||||
"temperatureLabel": "Temperature",
|
||||
"maxTokensLabel": "最大 Token 数",
|
||||
"mcpServersLabel": "MCP 服务器",
|
||||
"mcpServersPlaceholder": "选择要附加的 MCP 服务器(格式与 chat completions API 相同)",
|
||||
"mcpServersSaved_one": "已保存 {{count}} 个 MCP 服务器。调用此 Agent 时,请在 chat completions 中使用相同的 <toolsCode>tools</toolsCode> 数组。",
|
||||
"mcpServersSaved_other": "已保存 {{count}} 个 MCP 服务器。调用此 Agent 时,请在 chat completions 中使用相同的 <toolsCode>tools</toolsCode> 数组。",
|
||||
"updateAgent": "更新 Agent",
|
||||
"testInChat": "在聊天中测试",
|
||||
"saveAgentFirst": "请先保存 Agent 以在聊天中测试。",
|
||||
"selectAgentForBatchTest": "请选择一个 Agent 以运行批量测试。",
|
||||
"selectAgentToConnect": "请选择一个 Agent 以查看连接方式。",
|
||||
"proxyBaseUrl": "代理基础 URL",
|
||||
"callYourAgent": "调用您的 Agent(cURL)",
|
||||
"createKeyTitle": "为此 Agent 创建密钥",
|
||||
"createKeyDesc": "创建一个只能调用此 Agent 的虚拟密钥。该密钥将绑定到您(user_id),并限制为模型 <agentCode>{{agentName}}</agentCode>。",
|
||||
"createKeyButton": "为此 Agent 创建密钥",
|
||||
"keyCreationDisabled": "您的账户已禁用密钥创建。",
|
||||
"keyCreated": "密钥已创建。它显示在上方的 cURL 示例中——复制代码片段以使用。",
|
||||
"failedToLoadAgents": "加载 Agent 失败",
|
||||
"nameAndModelRequired": "名称和底层模型为必填项",
|
||||
"failedToSaveAgent": "保存 Agent 失败",
|
||||
"agentUpdated": "Agent 已成功更新",
|
||||
"failedToUpdateAgent": "更新 Agent 失败",
|
||||
"virtualKeyCreated": "虚拟密钥已创建。请在下方的 curl 示例中使用。",
|
||||
"keyCreatedNoValue": "密钥已创建但未返回值",
|
||||
"failedToCreateKey": "为 Agent 创建密钥失败",
|
||||
"deleteAgentTitle": "删除 Agent",
|
||||
"deleteAgentConfirm": "确定要删除 \"{{name}}\"?此操作无法撤销。",
|
||||
"agentDeleted": "Agent 已删除",
|
||||
"failedToDeleteAgent": "删除 Agent 失败"
|
||||
},
|
||||
"audioRenderer": {
|
||||
"browserNotSupported": "您的浏览器不支持音频元素。"
|
||||
},
|
||||
"chatImageRenderer": {
|
||||
"uploadedImageAlt": "用户上传的图片"
|
||||
},
|
||||
"chatImageUpload": {
|
||||
"attachTooltip": "附加图片或 PDF"
|
||||
},
|
||||
"chatMessageBubble": {
|
||||
"generatedImageAlt": "生成的图片"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue