feat(ui): i18n for playground chat UI

This commit is contained in:
吹雪 2026-06-10 21:43:53 +09:00
parent da509c009a
commit c48e2ad23f
3 changed files with 396 additions and 140 deletions

View file

@ -24,6 +24,7 @@ import {
import { Card, Text, TextInput, Title, Button as TremorButton } from "@tremor/react";
import { Button, Input, Modal, Popover, Select, Spin, Tooltip, Typography, Upload } from "antd";
import React, { useEffect, useRef, useState } from "react";
import { Trans, 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";
@ -109,6 +110,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
simplified = false,
fixedModel,
}) => {
const { t } = useTranslation();
const [mcpServers, setMCPServers] = useState<MCPServer[]>([]);
const [mcpToolsets, setMCPToolsets] = useState<MCPToolset[]>([]);
const [isToolsetsInfoModalVisible, setIsToolsetsInfoModalVisible] = useState(false);
@ -491,7 +493,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
abortControllerRef.current.abort();
abortControllerRef.current = null;
setIsLoading(false);
NotificationsManager.info("Request cancelled");
NotificationsManager.info(t("playground.chatUi.requestCancelled"));
}
};
@ -565,19 +567,19 @@ const ChatUI: React.FC<ChatUIProps> = ({
// For image edits, require both image and prompt
if (endpointType === EndpointType.IMAGE_EDITS && uploadedImages.length === 0) {
NotificationsManager.fromBackend("Please upload at least one image for editing");
NotificationsManager.fromBackend(t("playground.chatUi.pleaseUploadImage"));
return;
}
// For audio transcriptions, require audio file
if (endpointType === EndpointType.TRANSCRIPTION && !uploadedAudio) {
NotificationsManager.fromBackend("Please upload an audio file for transcription");
NotificationsManager.fromBackend(t("playground.chatUi.pleaseUploadAudio"));
return;
}
// For A2A agents, require agent selection
if (endpointType === EndpointType.A2A_AGENTS && !selectedAgent) {
NotificationsManager.fromBackend("Please select an agent to send a message");
NotificationsManager.fromBackend(t("playground.chatUi.pleaseSelectAgent"));
return;
}
@ -587,13 +589,13 @@ const ChatUI: React.FC<ChatUIProps> = ({
const rawSelected =
selectedMCPServers.length === 1 && selectedMCPServers[0] !== "__all__" ? selectedMCPServers[0] : null;
if (!rawSelected) {
NotificationsManager.fromBackend("Please select an MCP server to test");
NotificationsManager.fromBackend(t("playground.chatUi.pleaseSelectMCPServer"));
return;
}
// Resolve the real server ID (toolsets use toolset: prefix)
const mcpServerId = rawSelected.startsWith("toolset:") ? rawSelected : rawSelected;
if (!selectedMCPDirectTool) {
NotificationsManager.fromBackend("Please select an MCP tool to call");
NotificationsManager.fromBackend(t("playground.chatUi.pleaseSelectMCPTool"));
return;
}
// For toolsets, find the tool in the servers that back this toolset
@ -611,13 +613,15 @@ const ChatUI: React.FC<ChatUIProps> = ({
}
const mcpTool = searchPool.find((t: any) => t.name === selectedMCPDirectTool);
if (!mcpTool) {
NotificationsManager.fromBackend("Please wait for tool schema to load");
NotificationsManager.fromBackend(t("playground.chatUi.waitForToolSchema"));
return;
}
try {
mcpToolArguments = (await mcpToolArgsFormRef.current?.getSubmitValues()) ?? {};
} catch (err) {
NotificationsManager.fromBackend(err instanceof Error ? err.message : "Please fill in all required parameters");
NotificationsManager.fromBackend(
err instanceof Error ? err.message : t("playground.chatUi.fillRequiredParameters"),
);
return;
}
}
@ -636,7 +640,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
];
if (modelRequiredEndpoints.includes(endpointType as EndpointType) && !selectedModel) {
NotificationsManager.fromBackend("Please select a model before sending a request");
NotificationsManager.fromBackend(t("playground.chatUi.pleaseSelectModel"));
return;
}
@ -647,7 +651,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
const effectiveApiKey = simplified ? accessToken : apiKeySource === "session" ? accessToken : apiKey;
if (!effectiveApiKey) {
NotificationsManager.fromBackend("Please provide a Virtual Key or select Current UI Session");
NotificationsManager.fromBackend(t("playground.chatUi.pleaseProvideVirtualKey"));
return;
}
@ -663,7 +667,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
try {
newUserMessage = await createMultimodalMessage(inputMessage, responsesUploadedImage);
} catch (error) {
NotificationsManager.fromBackend("Failed to process image. Please try again.");
NotificationsManager.fromBackend(t("playground.chatUi.failedToProcessImage"));
return;
}
}
@ -672,7 +676,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
try {
newUserMessage = await createChatMultimodalMessage(inputMessage, chatUploadedImage);
} catch (error) {
NotificationsManager.fromBackend("Failed to process image. Please try again.");
NotificationsManager.fromBackend(t("playground.chatUi.failedToProcessImage"));
return;
}
} else {
@ -939,7 +943,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
2,
)
: JSON.stringify(result, null, 2);
updateTextUI("assistant", resultText || "Tool executed successfully.");
updateTextUI("assistant", resultText || t("playground.chatUi.toolExecutedSuccessfully"));
}
}
@ -963,7 +967,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
console.log("Request was cancelled");
} else {
console.error("Error fetching response", error);
updateTextUI("assistant", "Error fetching response:" + error);
updateTextUI("assistant", t("playground.chatUi.errorFetchingResponse", { error }));
}
} finally {
setIsLoading(false);
@ -995,15 +999,15 @@ const ChatUI: React.FC<ChatUIProps> = ({
handleRemoveResponsesImage();
handleRemoveChatImage();
handleRemoveAudio();
NotificationsManager.success("Chat history cleared.");
NotificationsManager.success(t("playground.chatUi.chatHistoryCleared"));
};
if (userRole && userRole === "Admin Viewer") {
const { Title, Paragraph } = Typography;
return (
<div>
<Title level={1}>Access Denied</Title>
<Paragraph>Ask your proxy admin for access to test models</Paragraph>
<Title level={1}>{t("playground.chatUi.accessDenied")}</Title>
<Paragraph>{t("playground.chatUi.askProxyAdminForAccess")}</Paragraph>
</div>
);
}
@ -1037,11 +1041,11 @@ const ChatUI: React.FC<ChatUIProps> = ({
{/* Left Sidebar with Controls - hidden in simplified mode */}
{!simplified && (
<div className="w-1/4 p-4 bg-gray-50 overflow-y-auto">
<Title className="text-xl font-semibold mb-6 mt-2">Configurations</Title>
<Title className="text-xl font-semibold mb-6 mt-2">{t("playground.chatUi.configurations")}</Title>
<div className="space-y-4">
<div>
<Text className="font-medium block mb-2 text-gray-700 flex items-center">
<KeyOutlined className="mr-2" /> Virtual Key Source
<KeyOutlined className="mr-2" /> {t("playground.chatUi.virtualKeySource")}
</Text>
<Select
disabled={disabledPersonalKeyCreation}
@ -1051,15 +1055,15 @@ const ChatUI: React.FC<ChatUIProps> = ({
setApiKeySource(value as "session" | "custom");
}}
options={[
{ value: "session", label: "Current UI Session" },
{ value: "custom", label: "Virtual Key" },
{ value: "session", label: t("playground.chatUi.currentUiSession") },
{ value: "custom", label: t("playground.chatUi.virtualKeyLabel") },
]}
className="rounded-md"
/>
{apiKeySource === "custom" && (
<TextInput
className="mt-2"
placeholder="Enter custom Virtual Key"
placeholder={t("playground.chatUi.enterCustomVirtualKey")}
type="password"
onValueChange={setApiKey}
value={apiKey}
@ -1071,7 +1075,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
<div>
<div className="flex items-center justify-between mb-2">
<Text className="font-medium block text-gray-700 flex items-center">
<SettingOutlined className="mr-2" /> Custom Proxy Base URL
<SettingOutlined className="mr-2" /> {t("playground.chatUi.customProxyBaseUrl")}
</Text>
{proxySettings?.LITELLM_UI_API_DOC_BASE_URL && !customProxyBaseUrl && (
<Button
@ -1084,7 +1088,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
}}
className="text-gray-500 hover:text-gray-700"
>
Fill
{t("playground.chatUi.fill")}
</Button>
)}
{customProxyBaseUrl && (
@ -1098,12 +1102,12 @@ const ChatUI: React.FC<ChatUIProps> = ({
}}
className="text-gray-500 hover:text-gray-700"
>
Clear
{t("common.clear")}
</Button>
)}
</div>
<TextInput
placeholder="Optional: Enter custom proxy URL (e.g., http://localhost:5000)"
placeholder={t("playground.chatUi.customProxyUrlPlaceholder")}
onValueChange={(value) => {
setCustomProxyBaseUrl(value);
sessionStorage.setItem("customProxyBaseUrl", value);
@ -1112,13 +1116,15 @@ const ChatUI: React.FC<ChatUIProps> = ({
icon={ApiOutlined}
/>
{customProxyBaseUrl && (
<Text className="text-xs text-gray-500 mt-1">API calls will be sent to: {customProxyBaseUrl}</Text>
<Text className="text-xs text-gray-500 mt-1">
{t("playground.chatUi.apiCallsSentTo", { url: customProxyBaseUrl })}
</Text>
)}
</div>
<div>
<Text className="font-medium block mb-2 text-gray-700 flex items-center">
<ApiOutlined className="mr-2" /> Endpoint Type
<ApiOutlined className="mr-2" /> {t("playground.chatUi.endpointTypeLabel")}
</Text>
<EndpointSelector
endpointType={endpointType}
@ -1146,7 +1152,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
<div className="mb-4">
<Text className="font-medium block mb-2 text-gray-700 flex items-center">
<SoundOutlined className="mr-2" />
Voice
{t("playground.chatUi.voiceLabel")}
</Text>
<Select
value={selectedVoice}
@ -1175,7 +1181,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
<div>
<Text className="font-medium block mb-2 text-gray-700 flex items-center justify-between">
<span className="flex items-center">
<RobotOutlined className="mr-2" /> Select Model
<RobotOutlined className="mr-2" /> {t("playground.chatUi.selectModelLabel")}
</span>
{isChatModel() ? (
<Popover
@ -1191,7 +1197,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
onMockTestFallbacksChange={setMockTestFallbacks}
/>
}
title="Model Settings"
title={t("playground.chatUi.modelSettings")}
trigger="click"
placement="right"
>
@ -1200,12 +1206,12 @@ const ChatUI: React.FC<ChatUIProps> = ({
size="small"
icon={<SettingOutlined />}
className="text-gray-500 hover:text-gray-700"
aria-label="Model Settings"
aria-label={t("playground.chatUi.modelSettings")}
data-testid="model-settings-button"
/>
</Popover>
) : (
<Tooltip title="Advanced parameters are only supported for chat models currently">
<Tooltip title={t("playground.chatUi.advancedParamsTooltip")}>
<Button
type="text"
size="small"
@ -1218,10 +1224,10 @@ const ChatUI: React.FC<ChatUIProps> = ({
</Text>
<Select
value={selectedModel}
placeholder="Select a Model"
placeholder={t("playground.chatUi.selectModelPlaceholder")}
onChange={onModelChange}
options={[
{ value: "custom", label: "Enter custom model", key: "custom" },
{ value: "custom", label: t("playground.chatUi.enterCustomModel"), key: "custom" },
...Array.from(
new Set(
modelInfo
@ -1260,7 +1266,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
{showCustomModelInput && (
<TextInput
className="mt-2"
placeholder="Enter custom model name"
placeholder={t("playground.chatUi.enterCustomModelName")}
onValueChange={(value) => {
// Using setTimeout to create a simple debounce effect
if (customModelTimeout.current) {
@ -1280,11 +1286,11 @@ const ChatUI: React.FC<ChatUIProps> = ({
{endpointType === EndpointType.A2A_AGENTS && (
<div>
<Text className="font-medium block mb-2 text-gray-700 flex items-center">
<RobotOutlined className="mr-2" /> Select Agent
<RobotOutlined className="mr-2" /> {t("playground.chatUi.selectAgentLabel")}
</Text>
<Select
value={selectedAgent}
placeholder="Select an Agent"
placeholder={t("playground.chatUi.selectAgentPlaceholder")}
onChange={(value) => setSelectedAgent(value)}
options={agentInfo.map((agent) => ({
value: agent.agent_name,
@ -1312,16 +1318,14 @@ const ChatUI: React.FC<ChatUIProps> = ({
))}
</Select>
{agentInfo.length === 0 && (
<Text className="text-xs text-gray-500 mt-2 block">
No agents found. Create agents via /v1/agents endpoint.
</Text>
<Text className="text-xs text-gray-500 mt-2 block">{t("playground.chatUi.noAgentsFound")}</Text>
)}
</div>
)}
<div>
<Text className="font-medium block mb-2 text-gray-700 flex items-center">
<TagsOutlined className="mr-2" /> Tags
<TagsOutlined className="mr-2" /> {t("playground.chatUi.tagsLabel")}
</Text>
<TagSelector
value={selectedTags}
@ -1335,13 +1339,15 @@ const ChatUI: React.FC<ChatUIProps> = ({
<div>
<Text className="font-medium block mb-2 text-gray-700 flex items-center">
<ToolOutlined className="mr-2" />
{endpointType === EndpointType.MCP ? "MCP Server" : "MCP Servers"}
{endpointType === EndpointType.MCP
? t("playground.chatUi.mcpServerLabel")
: t("playground.chatUi.mcpServersLabel")}
<Tooltip
className="ml-1"
title={
endpointType === EndpointType.MCP
? "Select an MCP server or toolset to test tools directly."
: "Select MCP servers or toolsets to use in your conversation."
? t("playground.chatUi.mcpServerTooltipDirect")
: t("playground.chatUi.mcpServerTooltip")
}
>
<InfoCircleOutlined
@ -1353,7 +1359,11 @@ const ChatUI: React.FC<ChatUIProps> = ({
<Select
mode={endpointType === EndpointType.MCP ? undefined : "multiple"}
style={{ width: "100%" }}
placeholder={endpointType === EndpointType.MCP ? "Select MCP server" : "Select MCP servers"}
placeholder={
endpointType === EndpointType.MCP
? t("playground.chatUi.selectMCPServerPlaceholder")
: t("playground.chatUi.selectMCPServersPlaceholder")
}
value={
endpointType === EndpointType.MCP
? selectedMCPServers[0] !== "__all__" && selectedMCPServers.length === 1
@ -1399,7 +1409,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
maxTagCount={endpointType === EndpointType.MCP ? 1 : "responsive"}
filterOption={(input, option) => {
if (option?.value === "__all__") {
return "All MCP Servers".toLowerCase().includes(input.toLowerCase());
return t("playground.chatUi.allMCPServers").toLowerCase().includes(input.toLowerCase());
}
const val = option?.value as string | undefined;
if (val?.startsWith("toolset:")) {
@ -1423,17 +1433,17 @@ const ChatUI: React.FC<ChatUIProps> = ({
>
{/* All MCP Servers option - hidden for MCP direct mode */}
{endpointType !== EndpointType.MCP && (
<Select.Option key="__all__" value="__all__" label="All MCP Servers">
<Select.Option key="__all__" value="__all__" label={t("playground.chatUi.allMCPServers")}>
<div className="flex flex-col py-1">
<span className="font-medium">All MCP Servers</span>
<span className="text-xs text-gray-500 mt-1">Use all available MCP servers</span>
<span className="font-medium">{t("playground.chatUi.allMCPServers")}</span>
<span className="text-xs text-gray-500 mt-1">{t("playground.chatUi.useAllMCPServers")}</span>
</div>
</Select.Option>
)}
{/* Toolsets (purple badge) */}
{mcpToolsets.length > 0 && (
<Select.OptGroup label="Toolsets">
<Select.OptGroup label={t("playground.chatUi.toolsetsGroup")}>
{mcpToolsets.map((toolset) => (
<Select.Option
key={`toolset:${toolset.toolset_id}`}
@ -1450,9 +1460,11 @@ const ChatUI: React.FC<ChatUIProps> = ({
className="text-xs px-1 rounded"
style={{ background: "#ede9fe", color: "#7c3aed" }}
>
Toolset
{t("playground.chatUi.toolsetBadge")}
</span>
<span className="text-xs text-gray-500">
{t("playground.chatUi.toolCount", { count: toolset.tools.length })}
</span>
<span className="text-xs text-gray-500">({toolset.tools.length} tools)</span>
</div>
{toolset.description && (
<span className="text-xs text-gray-500 mt-1">{toolset.description}</span>
@ -1465,7 +1477,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
{/* Individual servers */}
{mcpServers.length > 0 && (
<Select.OptGroup label="Servers">
<Select.OptGroup label={t("playground.chatUi.serversGroup")}>
{mcpServers.map((server) => (
<Select.Option
key={server.server_id}
@ -1514,10 +1526,12 @@ const ChatUI: React.FC<ChatUIProps> = ({
}
return (
<div className="mt-3">
<Text className="text-xs text-gray-600 mb-1 block">Select Tool</Text>
<Text className="text-xs text-gray-600 mb-1 block">
{t("playground.chatUi.selectToolLabel")}
</Text>
<Select
style={{ width: "100%" }}
placeholder="Select a tool to call"
placeholder={t("playground.chatUi.selectToolPlaceholder")}
value={selectedMCPDirectTool}
onChange={(value) => setSelectedMCPDirectTool(value)}
options={toolOptions}
@ -1542,13 +1556,15 @@ const ChatUI: React.FC<ChatUIProps> = ({
return (
<div key={serverId} className="border rounded p-2">
<Text className="text-xs text-gray-600 mb-1">
Limit tools for {server?.alias || server?.server_name || serverId}:
{t("playground.chatUi.limitToolsFor", {
serverName: server?.alias || server?.server_name || serverId,
})}
</Text>
<Select
mode="multiple"
size="small"
style={{ width: "100%" }}
placeholder="All tools (default)"
placeholder={t("playground.chatUi.allToolsDefault")}
value={mcpServerToolRestrictions[serverId] || []}
onChange={(selectedTools) => {
setMCPServerToolRestrictions((prev) => ({
@ -1585,17 +1601,19 @@ const ChatUI: React.FC<ChatUIProps> = ({
key={serverId}
className="border border-blue-100 rounded p-2 bg-blue-50 flex items-center justify-between"
>
<Text className="text-xs text-blue-700">{serverName} requires your API key</Text>
<Text className="text-xs text-blue-700">
{t("playground.chatUi.serverRequiresApiKey", { serverName })}
</Text>
{server.has_user_credential ? (
<div className="flex items-center gap-2">
<span className="text-green-600 text-xs font-medium flex items-center gap-1">
<KeyOutlined /> Connected
<KeyOutlined /> {t("playground.chatUi.connected")}
</span>
<button
className="text-xs text-gray-400 hover:text-blue-500 underline"
onClick={() => setByokModalServer(server)}
>
Reconnect
{t("playground.chatUi.reconnect")}
</button>
</div>
) : (
@ -1603,7 +1621,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
className="text-xs bg-blue-500 hover:bg-blue-600 text-white px-3 py-1 rounded-lg font-medium"
onClick={() => setByokModalServer(server)}
>
Connect
{t("playground.chatUi.connectButton")}
</button>
)}
</div>
@ -1615,17 +1633,16 @@ const ChatUI: React.FC<ChatUIProps> = ({
<div>
<Text className="font-medium block mb-2 text-gray-700 flex items-center">
<DatabaseOutlined className="mr-2" /> Vector Store
<DatabaseOutlined className="mr-2" /> {t("playground.chatUi.vectorStoreLabel")}
<Tooltip
className="ml-1"
title={
<span>
Select vector store(s) to use for this LLM API call. You can set up your vector store{" "}
<a href="?page=vector-stores" style={{ color: "#1890ff" }}>
here
</a>
.
</span>
<Trans
i18nKey="playground.chatUi.vectorStoreTooltip"
components={{
setupLink: <a href="?page=vector-stores" style={{ color: "#1890ff" }} />,
}}
/>
}
>
<InfoCircleOutlined />
@ -1641,17 +1658,16 @@ const ChatUI: React.FC<ChatUIProps> = ({
<div>
<Text className="font-medium block mb-2 text-gray-700 flex items-center">
<SafetyOutlined className="mr-2" /> Guardrails
<SafetyOutlined className="mr-2" /> {t("playground.chatUi.guardrailsLabel")}
<Tooltip
className="ml-1"
title={
<span>
Select guardrail(s) to use for this LLM API call. You can set up your guardrails{" "}
<a href="?page=guardrails" style={{ color: "#1890ff" }}>
here
</a>
.
</span>
<Trans
i18nKey="playground.chatUi.guardrailsTooltip"
components={{
setupLink: <a href="?page=guardrails" style={{ color: "#1890ff" }} />,
}}
/>
}
>
<InfoCircleOutlined />
@ -1667,18 +1683,16 @@ const ChatUI: React.FC<ChatUIProps> = ({
<div>
<Text className="font-medium block mb-2 text-gray-700 flex items-center">
<SafetyOutlined className="mr-2" /> Policies
<SafetyOutlined className="mr-2" /> {t("playground.chatUi.policiesLabel")}
<Tooltip
className="ml-1"
title={
<span>
Select policy/policies to apply to this LLM API call. Policies define which guardrails are
applied based on conditions. You can set up your policies{" "}
<a href="?page=policies" style={{ color: "#1890ff" }}>
here
</a>
.
</span>
<Trans
i18nKey="playground.chatUi.policiesTooltip"
components={{
setupLink: <a href="?page=policies" style={{ color: "#1890ff" }} />,
}}
/>
}
>
<InfoCircleOutlined />
@ -1721,14 +1735,16 @@ const ChatUI: React.FC<ChatUIProps> = ({
) : (
<>
<div className="p-4 border-b border-gray-200 flex justify-between items-center">
<Title className="text-xl font-semibold mb-0">{simplified ? "Chat" : "Test Key"}</Title>
<Title className="text-xl font-semibold mb-0">
{simplified ? t("playground.chatUi.chatTitle") : t("playground.chatUi.testKeyTitle")}
</Title>
<div className="flex gap-2">
<TremorButton
onClick={clearChatHistory}
className="bg-gray-100 hover:bg-gray-200 text-gray-700 border-gray-300"
icon={ClearOutlined}
>
Clear Chat
{t("playground.chatUi.clearChat")}
</TremorButton>
{!simplified && (
<TremorButton
@ -1736,7 +1752,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
className="bg-gray-100 hover:bg-gray-200 text-gray-700 border-gray-300"
icon={CodeOutlined}
>
Get Code
{t("playground.chatUi.getCode")}
</TremorButton>
)}
</div>
@ -1745,7 +1761,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
{chatHistory.length === 0 && (
<div className="h-full flex flex-col items-center justify-center text-gray-400">
<RobotOutlined style={{ fontSize: "48px", marginBottom: "16px" }} />
<Text>Start a conversation, generate an image, or handle audio</Text>
<Text>{t("playground.chatUi.emptyStateHint")}</Text>
</div>
)}
@ -1786,7 +1802,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
>
<RobotOutlined style={{ fontSize: "12px", color: "#4b5563" }} />
</div>
<strong className="text-sm capitalize">Assistant</strong>
<strong className="text-sm capitalize">{t("playground.chatUi.assistant")}</strong>
</div>
<MCPEventsDisplay events={mcpEvents} />
</div>
@ -1810,9 +1826,9 @@ const ChatUI: React.FC<ChatUIProps> = ({
<p className="ant-upload-drag-icon">
<PictureOutlined style={{ fontSize: "24px", color: "#666" }} />
</p>
<p className="ant-upload-text text-sm">Click or drag images to upload</p>
<p className="ant-upload-text text-sm">{t("playground.chatUi.dragImagesToUpload")}</p>
<p className="ant-upload-hint text-xs text-gray-500">
Support for PNG, JPG, JPEG formats. Multiple images supported.
{t("playground.chatUi.imageUploadHint")}
</p>
</Dragger>
) : (
@ -1830,7 +1846,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
return "";
}
})()}
alt={`Upload preview ${index + 1}`}
alt={t("playground.chatUi.uploadPreviewAlt", { index: index + 1 })}
className="max-w-32 max-h-32 rounded-md border border-gray-200 object-cover"
/>
<button
@ -1848,7 +1864,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
>
<div className="text-center">
<PictureOutlined style={{ fontSize: "24px", color: "#666" }} />
<p className="text-xs text-gray-500 mt-1">Add more</p>
<p className="text-xs text-gray-500 mt-1">{t("playground.chatUi.addMore")}</p>
</div>
<input
id="additional-image-upload"
@ -1879,9 +1895,9 @@ const ChatUI: React.FC<ChatUIProps> = ({
<p className="ant-upload-drag-icon">
<SoundOutlined style={{ fontSize: "24px", color: "#666" }} />
</p>
<p className="ant-upload-text text-sm">Click or drag audio file to upload</p>
<p className="ant-upload-text text-sm">{t("playground.chatUi.dragAudioToUpload")}</p>
<p className="ant-upload-hint text-xs text-gray-500">
Support for MP3, MP4, MPEG, MPGA, M4A, WAV, WEBM formats. Max file size: 25 MB.
{t("playground.chatUi.audioUploadHint")}
</p>
</Dragger>
) : (
@ -1897,7 +1913,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
className="bg-white shadow-sm border border-gray-200 rounded px-2 py-1 text-red-500 hover:bg-red-50 text-xs"
onClick={handleRemoveAudio}
>
<DeleteOutlined /> Remove
<DeleteOutlined /> {t("playground.chatUi.removeButton")}
</button>
</div>
)}
@ -1929,12 +1945,16 @@ const ChatUI: React.FC<ChatUIProps> = ({
{isLoading ? (
<>
<LoadingOutlined className="text-blue-500" spin />
<span className="text-sm text-blue-700 font-medium">Running Python code...</span>
<span className="text-sm text-blue-700 font-medium">
{t("playground.chatUi.runningPythonCode")}
</span>
</>
) : (
<>
<CodeOutlined className="text-blue-500" />
<span className="text-sm text-blue-700 font-medium">Code Interpreter Active</span>
<span className="text-sm text-blue-700 font-medium">
{t("playground.chatUi.codeInterpreterActive")}
</span>
</>
)}
</div>
@ -1942,16 +1962,16 @@ const ChatUI: React.FC<ChatUIProps> = ({
className="text-xs text-blue-500 hover:text-blue-700"
onClick={() => codeInterpreter.setEnabled(false)}
>
Disable
{t("playground.chatUi.disableButton")}
</button>
</div>
{/* Sample prompts - only show when not loading */}
{!isLoading && (
<div className="flex flex-wrap gap-2">
{[
"Generate sample sales data CSV and create a chart",
"Create a PNG bar chart comparing AI gateway providers including LiteLLM",
"Generate a CSV of LLM pricing data and visualize it as a line chart",
t("playground.chatUi.samplePrompt1"),
t("playground.chatUi.samplePrompt2"),
t("playground.chatUi.samplePrompt3"),
].map((prompt, idx) => (
<button
key={idx}
@ -1970,8 +1990,16 @@ const ChatUI: React.FC<ChatUIProps> = ({
{chatHistory.length === 0 && !isLoading && endpointType !== EndpointType.MCP && (
<div className="flex items-center gap-2 mb-3 overflow-x-auto">
{(endpointType === EndpointType.A2A_AGENTS
? ["What can you help me with?", "Tell me about yourself", "What tasks can you perform?"]
: ["Write me a poem", "Explain quantum computing", "Draft a polite email requesting a meeting"]
? [
t("playground.chatUi.suggestPromptA2A1"),
t("playground.chatUi.suggestPromptA2A2"),
t("playground.chatUi.suggestPromptA2A3"),
]
: [
t("playground.chatUi.suggestPrompt1"),
t("playground.chatUi.suggestPrompt2"),
t("playground.chatUi.suggestPrompt3"),
]
).map((prompt) => (
<button
key={prompt}
@ -2010,8 +2038,8 @@ const ChatUI: React.FC<ChatUIProps> = ({
<Tooltip
title={
codeInterpreter.enabled
? "Code Interpreter enabled (click to disable)"
: "Enable Code Interpreter"
? t("playground.chatUi.codeInterpreterEnabledTooltip")
: t("playground.chatUi.enableCodeInterpreterTooltip")
}
>
<button
@ -2023,7 +2051,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
onClick={() => {
codeInterpreter.toggle();
if (!codeInterpreter.enabled) {
NotificationsManager.success("Code Interpreter enabled!");
NotificationsManager.success(t("playground.chatUi.codeInterpreterEnabledNotice"));
}
}}
>
@ -2059,7 +2087,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
<MCPToolArgumentsForm ref={mcpToolArgsFormRef} tool={mcpTool} className="space-y-2" />
) : (
<div className="flex items-center justify-center h-10 text-sm text-gray-500">
Loading tool schema...
{t("playground.chatUi.loadingToolSchema")}
</div>
);
})()}
@ -2075,16 +2103,16 @@ const ChatUI: React.FC<ChatUIProps> = ({
endpointType === EndpointType.RESPONSES ||
endpointType === EndpointType.ANTHROPIC_MESSAGES ||
endpointType === EndpointType.INTERACTIONS
? "Type your message... (Shift+Enter for new line)"
? t("playground.chatUi.placeholderChat")
: endpointType === EndpointType.A2A_AGENTS
? "Send a message to the A2A agent..."
? t("playground.chatUi.placeholderA2A")
: endpointType === EndpointType.IMAGE_EDITS
? "Describe how you want to edit the image..."
? t("playground.chatUi.placeholderImageEdits")
: endpointType === EndpointType.SPEECH
? "Enter text to convert to speech..."
? t("playground.chatUi.placeholderSpeech")
: endpointType === EndpointType.TRANSCRIPTION
? "Optional: Add context or prompt for transcription..."
: "Describe the image you want to generate..."
? t("playground.chatUi.placeholderTranscription")
: t("playground.chatUi.placeholderImage")
}
disabled={isLoading}
className="flex-1"
@ -2128,7 +2156,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
className="bg-red-50 hover:bg-red-100 text-red-600 border-red-200"
icon={DeleteOutlined}
>
Cancel
{t("common.cancel")}
</TremorButton>
)}
</div>
@ -2139,7 +2167,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
</div>
</Card>
<Modal
title="Generated Code"
title={t("playground.chatUi.generatedCodeTitle")}
open={isGetCodeModalVisible}
onCancel={() => setIsGetCodeModalVisible(false)}
footer={null}
@ -2147,24 +2175,24 @@ const ChatUI: React.FC<ChatUIProps> = ({
>
<div className="flex justify-between items-end my-4">
<div>
<Text className="font-medium block mb-1 text-gray-700">SDK Type</Text>
<Text className="font-medium block mb-1 text-gray-700">{t("playground.chatUi.sdkTypeLabel")}</Text>
<Select
value={selectedSdk}
onChange={(value) => setSelectedSdk(value as "openai" | "azure")}
style={{ width: 150 }}
options={[
{ value: "openai", label: "OpenAI SDK" },
{ value: "azure", label: "Azure SDK" },
{ value: "openai", label: t("playground.chatUi.openAiSdk") },
{ value: "azure", label: t("playground.chatUi.azureSdk") },
]}
/>
</div>
<Button
onClick={() => {
navigator.clipboard.writeText(generatedCode);
NotificationsManager.success("Copied to clipboard!");
NotificationsManager.success(t("common.copied"));
}}
>
Copy to Clipboard
{t("playground.chatUi.copyToClipboard")}
</Button>
</div>
<SyntaxHighlighter
@ -2198,45 +2226,43 @@ const ChatUI: React.FC<ChatUIProps> = ({
{/* Toolsets info modal */}
<Modal
title="How Toolsets Work"
title={t("playground.chatUi.toolsetsInfoTitle")}
open={isToolsetsInfoModalVisible}
onCancel={() => setIsToolsetsInfoModalVisible(false)}
footer={[
<Button key="close" onClick={() => setIsToolsetsInfoModalVisible(false)}>
Close
{t("common.close")}
</Button>,
]}
width={600}
>
<div className="space-y-4 py-2">
<p className="text-gray-700">
<strong>Toolsets</strong> are named collections of specific tools from one or more MCP servers. Instead of
exposing all tools from a server, a toolset gives an agent exactly the tools it needs.
<Trans i18nKey="playground.chatUi.toolsetsInfoIntro" components={{ strong: <strong /> }} />
</p>
<div>
<h4 className="font-semibold text-gray-800 mb-2">How to use a toolset:</h4>
<h4 className="font-semibold text-gray-800 mb-2">{t("playground.chatUi.toolsetsHowToUseTitle")}</h4>
<ol className="list-decimal list-inside space-y-2 text-gray-700">
<li>
Select a <span style={{ color: "#7c3aed", fontWeight: 600 }}>Toolset</span> (purple badge) from the MCP
Servers dropdown.
<Trans
i18nKey="playground.chatUi.toolsetsStep1"
components={{ purple: <span style={{ color: "#7c3aed", fontWeight: 600 }} /> }}
/>
</li>
<li>The tool picker will show only the tools included in that toolset.</li>
<li>Select a tool and fill in its parameters, then send.</li>
<li>The tool call is routed to the correct underlying MCP server automatically.</li>
<li>{t("playground.chatUi.toolsetsStep2")}</li>
<li>{t("playground.chatUi.toolsetsStep3")}</li>
<li>{t("playground.chatUi.toolsetsStep4")}</li>
</ol>
</div>
<div className="bg-purple-50 border border-purple-200 rounded p-3">
<p className="text-sm text-purple-800">
<strong>Example:</strong> A &quot;GitHub Read-only&quot; toolset might include only{" "}
<code>list_repos</code> and <code>get_file</code> from a GitHub MCP server — preventing agents from making
writes.
<Trans i18nKey="playground.chatUi.toolsetsExample" components={{ strong: <strong />, code: <code /> }} />
</p>
</div>
<div>
<h4 className="font-semibold text-gray-800 mb-1">Creating toolsets:</h4>
<h4 className="font-semibold text-gray-800 mb-1">{t("playground.chatUi.toolsetsCreatingTitle")}</h4>
<p className="text-sm text-gray-600">
Admins can create and manage toolsets from the <strong>MCP</strong> page → <strong>Toolsets</strong> tab.
Toolsets can then be assigned to keys and teams to scope their tool access.
<Trans i18nKey="playground.chatUi.toolsetsCreatingDesc" components={{ strong: <strong /> }} />
</p>
</div>
</div>

View file

@ -3857,6 +3857,121 @@
},
"chatMessageBubble": {
"generatedImageAlt": "Generated image"
},
"chatUi": {
"requestCancelled": "Request cancelled",
"pleaseUploadImage": "Please upload at least one image for editing",
"pleaseUploadAudio": "Please upload an audio file for transcription",
"pleaseSelectAgent": "Please select an agent to send a message",
"pleaseSelectMCPServer": "Please select an MCP server to test",
"pleaseSelectMCPTool": "Please select an MCP tool to call",
"waitForToolSchema": "Please wait for tool schema to load",
"fillRequiredParameters": "Please fill in all required parameters",
"pleaseSelectModel": "Please select a model before sending a request",
"pleaseProvideVirtualKey": "Please provide a Virtual Key or select Current UI Session",
"failedToProcessImage": "Failed to process image. Please try again.",
"toolExecutedSuccessfully": "Tool executed successfully.",
"errorFetchingResponse": "Error fetching response: {{error}}",
"chatHistoryCleared": "Chat history cleared.",
"accessDenied": "Access Denied",
"askProxyAdminForAccess": "Ask your proxy admin for access to test models",
"configurations": "Configurations",
"virtualKeySource": "Virtual Key Source",
"currentUiSession": "Current UI Session",
"virtualKeyLabel": "Virtual Key",
"enterCustomVirtualKey": "Enter custom Virtual Key",
"customProxyBaseUrl": "Custom Proxy Base URL",
"fill": "Fill",
"customProxyUrlPlaceholder": "Optional: Enter custom proxy URL (e.g., http://localhost:5000)",
"apiCallsSentTo": "API calls will be sent to: {{url}}",
"endpointTypeLabel": "Endpoint Type",
"voiceLabel": "Voice",
"selectModelLabel": "Select Model",
"advancedParamsTooltip": "Advanced parameters are only supported for chat models currently",
"modelSettings": "Model Settings",
"selectModelPlaceholder": "Select a Model",
"enterCustomModel": "Enter custom model",
"enterCustomModelName": "Enter custom model name",
"selectAgentLabel": "Select Agent",
"selectAgentPlaceholder": "Select an Agent",
"noAgentsFound": "No agents found. Create agents via /v1/agents endpoint.",
"tagsLabel": "Tags",
"mcpServerLabel": "MCP Server",
"mcpServersLabel": "MCP Servers",
"mcpServerTooltipDirect": "Select an MCP server or toolset to test tools directly.",
"mcpServerTooltip": "Select MCP servers or toolsets to use in your conversation.",
"selectMCPServerPlaceholder": "Select MCP server",
"selectMCPServersPlaceholder": "Select MCP servers",
"allMCPServers": "All MCP Servers",
"useAllMCPServers": "Use all available MCP servers",
"toolsetsGroup": "Toolsets",
"toolsetBadge": "Toolset",
"toolCount": "({{count}} tools)",
"serversGroup": "Servers",
"selectToolLabel": "Select Tool",
"selectToolPlaceholder": "Select a tool to call",
"limitToolsFor": "Limit tools for {{serverName}}:",
"allToolsDefault": "All tools (default)",
"serverRequiresApiKey": "{{serverName}} requires your API key",
"connected": "Connected",
"reconnect": "Reconnect",
"connectButton": "Connect",
"vectorStoreLabel": "Vector Store",
"vectorStoreTooltip": "Select vector store(s) to use for this LLM API call. You can set up your vector store <setupLink>here</setupLink>.",
"guardrailsLabel": "Guardrails",
"guardrailsTooltip": "Select guardrail(s) to use for this LLM API call. You can set up your guardrails <setupLink>here</setupLink>.",
"policiesLabel": "Policies",
"policiesTooltip": "Select policy/policies to apply to this LLM API call. Policies define which guardrails are applied based on conditions. You can set up your policies <setupLink>here</setupLink>.",
"testKeyTitle": "Test Key",
"chatTitle": "Chat",
"clearChat": "Clear Chat",
"getCode": "Get Code",
"emptyStateHint": "Start a conversation, generate an image, or handle audio",
"assistant": "Assistant",
"dragImagesToUpload": "Click or drag images to upload",
"imageUploadHint": "Support for PNG, JPG, JPEG formats. Multiple images supported.",
"uploadPreviewAlt": "Upload preview {{index}}",
"addMore": "Add more",
"dragAudioToUpload": "Click or drag audio file to upload",
"audioUploadHint": "Support for MP3, MP4, MPEG, MPGA, M4A, WAV, WEBM formats. Max file size: 25 MB.",
"removeButton": "Remove",
"runningPythonCode": "Running Python code...",
"codeInterpreterActive": "Code Interpreter Active",
"disableButton": "Disable",
"samplePrompt1": "Generate sample sales data CSV and create a chart",
"samplePrompt2": "Create a PNG bar chart comparing AI gateway providers including LiteLLM",
"samplePrompt3": "Generate a CSV of LLM pricing data and visualize it as a line chart",
"suggestPromptA2A1": "What can you help me with?",
"suggestPromptA2A2": "Tell me about yourself",
"suggestPromptA2A3": "What tasks can you perform?",
"suggestPrompt1": "Write me a poem",
"suggestPrompt2": "Explain quantum computing",
"suggestPrompt3": "Draft a polite email requesting a meeting",
"codeInterpreterEnabledTooltip": "Code Interpreter enabled (click to disable)",
"enableCodeInterpreterTooltip": "Enable Code Interpreter",
"codeInterpreterEnabledNotice": "Code Interpreter enabled!",
"placeholderChat": "Type your message... (Shift+Enter for new line)",
"placeholderA2A": "Send a message to the A2A agent...",
"placeholderImageEdits": "Describe how you want to edit the image...",
"placeholderSpeech": "Enter text to convert to speech...",
"placeholderTranscription": "Optional: Add context or prompt for transcription...",
"placeholderImage": "Describe the image you want to generate...",
"loadingToolSchema": "Loading tool schema...",
"generatedCodeTitle": "Generated Code",
"sdkTypeLabel": "SDK Type",
"openAiSdk": "OpenAI SDK",
"azureSdk": "Azure SDK",
"copyToClipboard": "Copy to Clipboard",
"toolsetsInfoTitle": "How Toolsets Work",
"toolsetsInfoIntro": "<strong>Toolsets</strong> are named collections of specific tools from one or more MCP servers. Instead of exposing all tools from a server, a toolset gives an agent exactly the tools it needs.",
"toolsetsHowToUseTitle": "How to use a toolset:",
"toolsetsStep1": "Select a <purple>Toolset</purple> (purple badge) from the MCP Servers dropdown.",
"toolsetsStep2": "The tool picker will show only the tools included in that toolset.",
"toolsetsStep3": "Select a tool and fill in its parameters, then send.",
"toolsetsStep4": "The tool call is routed to the correct underlying MCP server automatically.",
"toolsetsExample": "<strong>Example:</strong> A \"GitHub Read-only\" toolset might include only <code>list_repos</code> and <code>get_file</code> from a GitHub MCP server — preventing agents from making writes.",
"toolsetsCreatingTitle": "Creating toolsets:",
"toolsetsCreatingDesc": "Admins can create and manage toolsets from the <strong>MCP</strong> page → <strong>Toolsets</strong> tab. Toolsets can then be assigned to keys and teams to scope their tool access."
}
}
}

View file

@ -3857,6 +3857,121 @@
},
"chatMessageBubble": {
"generatedImageAlt": "生成的图片"
},
"chatUi": {
"requestCancelled": "请求已取消",
"pleaseUploadImage": "请至少上传一张图片用于编辑",
"pleaseUploadAudio": "请上传一个音频文件用于转录",
"pleaseSelectAgent": "请选择一个 Agent 以发送消息",
"pleaseSelectMCPServer": "请选择一个 MCP 服务器进行测试",
"pleaseSelectMCPTool": "请选择一个 MCP 工具进行调用",
"waitForToolSchema": "请等待工具 schema 加载完成",
"fillRequiredParameters": "请填写所有必填参数",
"pleaseSelectModel": "请在发送请求前选择一个模型",
"pleaseProvideVirtualKey": "请提供虚拟密钥或选择当前 UI 会话",
"failedToProcessImage": "处理图片失败,请重试。",
"toolExecutedSuccessfully": "工具执行成功。",
"errorFetchingResponse": "获取响应时出错:{{error}}",
"chatHistoryCleared": "聊天记录已清除。",
"accessDenied": "拒绝访问",
"askProxyAdminForAccess": "请联系代理管理员以获取测试模型的权限",
"configurations": "配置",
"virtualKeySource": "虚拟密钥来源",
"currentUiSession": "当前 UI 会话",
"virtualKeyLabel": "虚拟密钥",
"enterCustomVirtualKey": "输入自定义虚拟密钥",
"customProxyBaseUrl": "自定义代理基础 URL",
"fill": "填入",
"customProxyUrlPlaceholder": "可选:输入自定义代理 URL(例如:http://localhost:5000)",
"apiCallsSentTo": "API 请求将发送至:{{url}}",
"endpointTypeLabel": "端点类型",
"voiceLabel": "语音",
"selectModelLabel": "选择模型",
"advancedParamsTooltip": "高级参数目前仅支持聊天模型",
"modelSettings": "模型设置",
"selectModelPlaceholder": "选择模型",
"enterCustomModel": "输入自定义模型",
"enterCustomModelName": "输入自定义模型名称",
"selectAgentLabel": "选择 Agent",
"selectAgentPlaceholder": "选择一个 Agent",
"noAgentsFound": "未找到 Agent。请通过 /v1/agents 端点创建 Agent。",
"tagsLabel": "标签",
"mcpServerLabel": "MCP 服务器",
"mcpServersLabel": "MCP 服务器",
"mcpServerTooltipDirect": "选择一个 MCP 服务器或工具集以直接测试工具。",
"mcpServerTooltip": "选择 MCP 服务器或工具集以在对话中使用。",
"selectMCPServerPlaceholder": "选择 MCP 服务器",
"selectMCPServersPlaceholder": "选择 MCP 服务器",
"allMCPServers": "所有 MCP 服务器",
"useAllMCPServers": "使用所有可用的 MCP 服务器",
"toolsetsGroup": "工具集",
"toolsetBadge": "工具集",
"toolCount": "({{count}} 个工具)",
"serversGroup": "服务器",
"selectToolLabel": "选择工具",
"selectToolPlaceholder": "选择要调用的工具",
"limitToolsFor": "限制 {{serverName}} 的工具:",
"allToolsDefault": "所有工具(默认)",
"serverRequiresApiKey": "{{serverName}} 需要您的 API 密钥",
"connected": "已连接",
"reconnect": "重新连接",
"connectButton": "连接",
"vectorStoreLabel": "向量存储",
"vectorStoreTooltip": "选择此次 LLM API 调用使用的向量存储。您可以在<setupLink>此处</setupLink>设置您的向量存储。",
"guardrailsLabel": "护栏",
"guardrailsTooltip": "选择此次 LLM API 调用使用的护栏。您可以在<setupLink>此处</setupLink>设置您的护栏。",
"policiesLabel": "策略",
"policiesTooltip": "选择应用于此次 LLM API 调用的策略。策略根据条件定义要应用的护栏。您可以在<setupLink>此处</setupLink>设置您的策略。",
"testKeyTitle": "测试密钥",
"chatTitle": "聊天",
"clearChat": "清除聊天",
"getCode": "获取代码",
"emptyStateHint": "开始对话、生成图片或处理音频",
"assistant": "助手",
"dragImagesToUpload": "点击或拖拽图片到此处上传",
"imageUploadHint": "支持 PNG、JPG、JPEG 格式,可上传多张图片。",
"uploadPreviewAlt": "上传预览 {{index}}",
"addMore": "添加更多",
"dragAudioToUpload": "点击或拖拽音频文件到此处上传",
"audioUploadHint": "支持 MP3、MP4、MPEG、MPGA、M4A、WAV、WEBM 格式,最大文件大小:25 MB。",
"removeButton": "移除",
"runningPythonCode": "正在运行 Python 代码...",
"codeInterpreterActive": "代码解释器已激活",
"disableButton": "禁用",
"samplePrompt1": "生成示例销售数据 CSV 并创建图表",
"samplePrompt2": "创建一个对比 AI 网关提供商(包括 LiteLLM)的 PNG 柱状图",
"samplePrompt3": "生成 LLM 定价数据的 CSV 并将其可视化为折线图",
"suggestPromptA2A1": "你能帮我做什么?",
"suggestPromptA2A2": "介绍一下你自己",
"suggestPromptA2A3": "你能执行哪些任务?",
"suggestPrompt1": "给我写一首诗",
"suggestPrompt2": "解释量子计算",
"suggestPrompt3": "起草一封礼貌地请求会议的邮件",
"codeInterpreterEnabledTooltip": "代码解释器已启用(点击可禁用)",
"enableCodeInterpreterTooltip": "启用代码解释器",
"codeInterpreterEnabledNotice": "代码解释器已启用!",
"placeholderChat": "输入消息...(Shift+Enter 换行)",
"placeholderA2A": "向 A2A Agent 发送消息...",
"placeholderImageEdits": "描述您希望如何编辑图片...",
"placeholderSpeech": "输入要转换为语音的文本...",
"placeholderTranscription": "可选:为转录添加上下文或提示词...",
"placeholderImage": "描述您想要生成的图片...",
"loadingToolSchema": "正在加载工具 schema...",
"generatedCodeTitle": "生成的代码",
"sdkTypeLabel": "SDK 类型",
"openAiSdk": "OpenAI SDK",
"azureSdk": "Azure SDK",
"copyToClipboard": "复制到剪贴板",
"toolsetsInfoTitle": "工具集的工作原理",
"toolsetsInfoIntro": "<strong>工具集</strong>是来自一个或多个 MCP 服务器的特定工具的命名集合。工具集不会暴露服务器的所有工具,而是为 Agent 提供恰好所需的工具。",
"toolsetsHowToUseTitle": "如何使用工具集:",
"toolsetsStep1": "从 MCP 服务器下拉列表中选择一个<purple>工具集</purple>(紫色标签)。",
"toolsetsStep2": "工具选择器将只显示该工具集包含的工具。",
"toolsetsStep3": "选择工具并填写其参数,然后发送。",
"toolsetsStep4": "工具调用会自动路由到正确的底层 MCP 服务器。",
"toolsetsExample": "<strong>示例:</strong>一个「GitHub 只读」工具集可能只包含来自 GitHub MCP 服务器的 <code>list_repos</code> 和 <code>get_file</code>——防止 Agent 进行写操作。",
"toolsetsCreatingTitle": "创建工具集:",
"toolsetsCreatingDesc": "管理员可以从 <strong>MCP</strong> 页面 → <strong>工具集</strong>标签页创建和管理工具集。工具集随后可分配给密钥和团队以限定其工具访问范围。"
}
}
}