diff --git a/ui/litellm-dashboard/src/components/playground/chat_ui/CodeInterpreterOutput.tsx b/ui/litellm-dashboard/src/components/playground/chat_ui/CodeInterpreterOutput.tsx index c27273a116d..bdd62ebf089 100644 --- a/ui/litellm-dashboard/src/components/playground/chat_ui/CodeInterpreterOutput.tsx +++ b/ui/litellm-dashboard/src/components/playground/chat_ui/CodeInterpreterOutput.tsx @@ -10,6 +10,7 @@ import { import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { coy } from "react-syntax-highlighter/dist/esm/styles/prism"; import { getProxyBaseUrl, getGlobalLitellmHeaderName } from "@/components/networking"; +import { useTranslation } from "react-i18next"; interface ContainerFileCitation { type: "container_file_citation"; @@ -33,6 +34,7 @@ const CodeInterpreterOutput: React.FC = ({ annotations = [], accessToken, }) => { + const { t } = useTranslation(); const [imageUrls, setImageUrls] = useState>({}); const [loadingImages, setLoadingImages] = useState>({}); const proxyBaseUrl = getProxyBaseUrl(); @@ -144,7 +146,7 @@ const CodeInterpreterOutput: React.FC = ({ key: "code", label: ( - Python Code Executed + {t("playground.codeInterpreterOutput.pythonCodeExecuted")} ), children: ( @@ -173,13 +175,13 @@ const CodeInterpreterOutput: React.FC = ({ {loadingImages[annotation.file_id] ? (
} /> - Loading image... + {t("playground.codeInterpreterOutput.loadingImage")}
) : imageUrls[annotation.file_id] ? (
{annotation.filename @@ -191,13 +193,13 @@ const CodeInterpreterOutput: React.FC = ({ onClick={() => handleDownload(annotation)} className="text-xs text-blue-500 hover:text-blue-700 flex items-center gap-1" > - Download + {t("common.download")}
) : (
- Image not available + {t("playground.codeInterpreterOutput.imageNotAvailable")}
)} diff --git a/ui/litellm-dashboard/src/components/playground/chat_ui/CodeInterpreterTool.tsx b/ui/litellm-dashboard/src/components/playground/chat_ui/CodeInterpreterTool.tsx index abc21ab5f65..e08afee5fd6 100644 --- a/ui/litellm-dashboard/src/components/playground/chat_ui/CodeInterpreterTool.tsx +++ b/ui/litellm-dashboard/src/components/playground/chat_ui/CodeInterpreterTool.tsx @@ -3,6 +3,7 @@ import { Switch, Tooltip } from "antd"; import MessageManager from "@/components/molecules/message_manager"; import { CodeOutlined, InfoCircleOutlined, ExclamationCircleOutlined } from "@ant-design/icons"; import { Text } from "@tremor/react"; +import { useTranslation } from "react-i18next"; interface CodeInterpreterToolProps { accessToken: string; @@ -34,12 +35,13 @@ const CodeInterpreterTool: React.FC = ({ selectedModel, disabled = false, }) => { + const { t } = useTranslation(); const isOpenAI = isOpenAIModel(selectedModel); const isDisabled = disabled || !isOpenAI; const handleToggle = (checked: boolean) => { if (checked && !isOpenAI) { - MessageManager.warning("Code Interpreter is only available for OpenAI models"); + MessageManager.warning(t("playground.codeInterpreterTool.onlyOpenAIWarning")); return; } onEnabledChange(checked); @@ -50,8 +52,8 @@ const CodeInterpreterTool: React.FC = ({
- Code Interpreter - + {t("playground.codeInterpreterTool.title")} +
@@ -69,14 +71,14 @@ const CodeInterpreterTool: React.FC = ({
- Code Interpreter is currently only supported for OpenAI models. + {t("playground.codeInterpreterTool.onlyOpenAINotice")} - Request support for other providers + {t("playground.codeInterpreterTool.requestSupportLink")}
diff --git a/ui/litellm-dashboard/src/components/playground/chat_ui/FilePreviewCard.tsx b/ui/litellm-dashboard/src/components/playground/chat_ui/FilePreviewCard.tsx index a046657357e..2b7344a055c 100644 --- a/ui/litellm-dashboard/src/components/playground/chat_ui/FilePreviewCard.tsx +++ b/ui/litellm-dashboard/src/components/playground/chat_ui/FilePreviewCard.tsx @@ -1,4 +1,5 @@ import { DeleteOutlined, FilePdfOutlined } from "@ant-design/icons"; +import { useTranslation } from "react-i18next"; interface FilePreviewCardProps { file: File; @@ -7,6 +8,7 @@ interface FilePreviewCardProps { } function FilePreviewCard({ file, previewUrl, onRemove }: FilePreviewCardProps) { + const { t } = useTranslation(); const isPdf = file.name.toLowerCase().endsWith(".pdf"); return ( @@ -20,14 +22,16 @@ function FilePreviewCard({ file, previewUrl, onRemove }: FilePreviewCardProps) { ) : ( Upload preview )}
{file.name}
-
{isPdf ? "PDF" : "Image"}
+
+ {isPdf ? t("playground.filePreviewCard.pdf") : t("playground.filePreviewCard.image")} +
) : ( )} @@ -399,10 +408,9 @@ const RealtimePlayground: React.FC = ({ {messages.length === 0 && !isConnected && (
- Realtime Voice Playground + {t("playground.realtimePlayground.emptyTitle")} - Click Connect to start a realtime session. You can speak using your microphone or type messages. - The AI will respond with voice and text. + {t("playground.realtimePlayground.emptyHint")}
)} @@ -421,7 +429,9 @@ const RealtimePlayground: React.FC = ({ : "bg-gray-100 text-gray-800 rounded-bl-md" }`} > -
{msg.role === "user" ? "You" : "AI"}
+
+ {msg.role === "user" ? t("playground.realtimePlayground.you") : t("playground.realtimePlayground.ai")} +
{msg.content}
)} @@ -441,11 +451,15 @@ const RealtimePlayground: React.FC = ({ danger={isRecording} icon={isRecording ? : } onClick={isRecording ? stopRecording : startRecording} - title={isRecording ? "Stop recording" : "Start recording"} + title={ + isRecording + ? t("playground.realtimePlayground.stopRecording") + : t("playground.realtimePlayground.startRecording") + } className={isRecording ? "animate-pulse" : ""} /> setInputText(e.target.value)} onPressEnter={sendTextMessage} @@ -463,7 +477,7 @@ const RealtimePlayground: React.FC = ({ {isRecording && (
- Listening — speak into your microphone. Server VAD will detect when you stop. + {t("playground.realtimePlayground.listeningHint")}
)} diff --git a/ui/litellm-dashboard/src/components/playground/chat_ui/ResponseMetrics.tsx b/ui/litellm-dashboard/src/components/playground/chat_ui/ResponseMetrics.tsx index 01eafa4d534..60ba8bee25b 100644 --- a/ui/litellm-dashboard/src/components/playground/chat_ui/ResponseMetrics.tsx +++ b/ui/litellm-dashboard/src/components/playground/chat_ui/ResponseMetrics.tsx @@ -9,6 +9,7 @@ import { ToolOutlined, DollarOutlined, } from "@ant-design/icons"; +import { useTranslation } from "react-i18next"; export interface TokenUsage { completionTokens?: number; @@ -26,66 +27,69 @@ interface ResponseMetricsProps { } const ResponseMetrics: React.FC = ({ timeToFirstToken, totalLatency, usage, toolName }) => { + const { t } = useTranslation(); if (!timeToFirstToken && !totalLatency && !usage) return null; return (
{timeToFirstToken !== undefined && ( - +
- TTFT: {(timeToFirstToken / 1000).toFixed(2)}s + {t("playground.responseMetrics.ttft", { value: (timeToFirstToken / 1000).toFixed(2) })}
)} {totalLatency !== undefined && ( - +
- Total Latency: {(totalLatency / 1000).toFixed(2)}s + + {t("playground.responseMetrics.totalLatencyValue", { value: (totalLatency / 1000).toFixed(2) })} +
)} {usage?.promptTokens !== undefined && ( - +
- In: {usage.promptTokens} + {t("playground.responseMetrics.in", { count: usage.promptTokens })}
)} {usage?.completionTokens !== undefined && ( - +
- Out: {usage.completionTokens} + {t("playground.responseMetrics.out", { count: usage.completionTokens })}
)} {usage?.reasoningTokens !== undefined && ( - +
- Reasoning: {usage.reasoningTokens} + {t("playground.responseMetrics.reasoning", { count: usage.reasoningTokens })}
)} {usage?.totalTokens !== undefined && ( - +
- Total: {usage.totalTokens} + {t("playground.responseMetrics.total", { count: usage.totalTokens })}
)} {usage?.cost !== undefined && ( - +
${usage.cost.toFixed(6)} @@ -94,10 +98,10 @@ const ResponseMetrics: React.FC = ({ timeToFirstToken, tot )} {toolName && ( - +
- Tool: {toolName} + {t("playground.responseMetrics.toolName", { name: toolName })}
)} diff --git a/ui/litellm-dashboard/src/components/playground/chat_ui/ResponsesImageRenderer.tsx b/ui/litellm-dashboard/src/components/playground/chat_ui/ResponsesImageRenderer.tsx index a8707ebd21b..40c5084ab91 100644 --- a/ui/litellm-dashboard/src/components/playground/chat_ui/ResponsesImageRenderer.tsx +++ b/ui/litellm-dashboard/src/components/playground/chat_ui/ResponsesImageRenderer.tsx @@ -2,12 +2,14 @@ import React from "react"; import { MessageType } from "./types"; import { shouldShowAttachedImage } from "./ResponsesImageUtils"; import { FilePdfOutlined } from "@ant-design/icons"; +import { useTranslation } from "react-i18next"; interface ResponsesImageRendererProps { message: MessageType; } const ResponsesImageRenderer: React.FC = ({ message }) => { + const { t } = useTranslation(); if (!shouldShowAttachedImage(message)) { return null; } @@ -23,7 +25,7 @@ const ResponsesImageRenderer: React.FC = ({ message ) : ( User uploaded image diff --git a/ui/litellm-dashboard/src/components/playground/chat_ui/ResponsesImageUpload.tsx b/ui/litellm-dashboard/src/components/playground/chat_ui/ResponsesImageUpload.tsx index d5abf13a7d3..91f4565a806 100644 --- a/ui/litellm-dashboard/src/components/playground/chat_ui/ResponsesImageUpload.tsx +++ b/ui/litellm-dashboard/src/components/playground/chat_ui/ResponsesImageUpload.tsx @@ -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 ResponsesImageUpload: React.FC = ({ onImageUpload, onRemoveImage, }) => { + const { t } = useTranslation(); return ( <> {/* Subtle upload button - only show when no image */} @@ -28,7 +30,7 @@ const ResponsesImageUpload: React.FC = ({ className="inline-block" style={{ padding: 0, border: "none", background: "none" }} > - + @@ -43,11 +47,11 @@ export function SearchResultsDisplay({ searchResults }: SearchResultsDisplayProp {searchResults.map((resultPage, pageIndex) => (
- Query: + {t("playground.searchResultsDisplay.query")} "{resultPage.search_query}" • - {resultPage.data.length} result{resultPage.data.length !== 1 ? "s" : ""} + {t("playground.searchResultsDisplay.resultCount", { count: resultPage.data.length })}
@@ -93,7 +97,9 @@ export function SearchResultsDisplay({ searchResults }: SearchResultsDisplayProp {result.attributes && Object.keys(result.attributes).length > 0 && (
-
Metadata:
+
+ {t("playground.searchResultsDisplay.metadata")} +
{Object.entries(result.attributes).map(([key, value]) => (
diff --git a/ui/litellm-dashboard/src/components/playground/chat_ui/SessionManagement.tsx b/ui/litellm-dashboard/src/components/playground/chat_ui/SessionManagement.tsx index e782845bfc3..7d75a821889 100644 --- a/ui/litellm-dashboard/src/components/playground/chat_ui/SessionManagement.tsx +++ b/ui/litellm-dashboard/src/components/playground/chat_ui/SessionManagement.tsx @@ -3,6 +3,7 @@ import { Switch, Tooltip } from "antd"; import { InfoCircleOutlined, CopyOutlined } from "@ant-design/icons"; import { EndpointType } from "./mode_endpoint_mapping"; import NotificationsManager from "../../molecules/notifications_manager"; +import { useTranslation } from "react-i18next"; interface SessionManagementProps { endpointType: string; @@ -17,6 +18,7 @@ const SessionManagement: React.FC = ({ useApiSessionManagement, onToggleSessionManagement, }) => { + const { t } = useTranslation(); if (endpointType !== EndpointType.RESPONSES) { return null; } @@ -24,16 +26,20 @@ const SessionManagement: React.FC = ({ const handleCopySessionId = () => { if (responsesSessionId) { navigator.clipboard.writeText(responsesSessionId); - NotificationsManager.success("Response ID copied to clipboard!"); + NotificationsManager.success(t("playground.sessionManagement.copiedToClipboard")); } }; const getSessionDisplay = () => { if (!responsesSessionId) { - return useApiSessionManagement ? "API Session: Ready" : "UI Session: Ready"; + return useApiSessionManagement + ? t("playground.sessionManagement.apiSessionReady") + : t("playground.sessionManagement.uiSessionReady"); } - const sessionPrefix = useApiSessionManagement ? "Response ID" : "UI Session"; + const sessionPrefix = useApiSessionManagement + ? t("playground.sessionManagement.responseIdPrefix") + : t("playground.sessionManagement.uiSessionPrefix"); const truncatedId = responsesSessionId.slice(0, 10); return `${sessionPrefix}: ${truncatedId}...`; }; @@ -41,13 +47,13 @@ const SessionManagement: React.FC = ({ const getSessionDescription = () => { if (!responsesSessionId) { return useApiSessionManagement - ? "LiteLLM will manage session using previous_response_id" - : "UI will manage session using chat history"; + ? t("playground.sessionManagement.apiSessionReadyDesc") + : t("playground.sessionManagement.uiSessionReadyDesc"); } return useApiSessionManagement - ? "LiteLLM API session active - context maintained server-side" - : "UI session active - context maintained client-side"; + ? t("playground.sessionManagement.apiSessionActiveDesc") + : t("playground.sessionManagement.uiSessionActiveDesc"); }; return ( @@ -55,8 +61,8 @@ const SessionManagement: React.FC = ({ {/* Session Management Toggle */}
- Session Management - + {t("playground.sessionManagement.title")} +
@@ -86,7 +92,7 @@ const SessionManagement: React.FC = ({ -
Copy response ID to continue session:
+
{t("playground.sessionManagement.copyTooltipTitle")}
{`curl -X POST "your-proxy-url/v1/responses" \\ -H "Authorization: Bearer your-api-key" \\ diff --git a/ui/litellm-dashboard/src/locales/en.json b/ui/litellm-dashboard/src/locales/en.json index c3ed860697a..c00b53351db 100644 --- a/ui/litellm-dashboard/src/locales/en.json +++ b/ui/litellm-dashboard/src/locales/en.json @@ -3972,6 +3972,94 @@ "toolsetsExample": "Example: A \"GitHub Read-only\" toolset might include only list_repos and get_file from a GitHub MCP server — preventing agents from making writes.", "toolsetsCreatingTitle": "Creating toolsets:", "toolsetsCreatingDesc": "Admins can create and manage toolsets from the MCP page → Toolsets tab. Toolsets can then be assigned to keys and teams to scope their tool access." + }, + "codeInterpreterOutput": { + "pythonCodeExecuted": "Python Code Executed", + "loadingImage": "Loading image...", + "generatedChart": "Generated chart", + "imageNotAvailable": "Image not available" + }, + "codeInterpreterTool": { + "title": "Code Interpreter", + "tooltipDesc": "Run Python code to generate files, charts, and analyze data. Container is created automatically.", + "onlyOpenAIWarning": "Code Interpreter is only available for OpenAI models", + "onlyOpenAINotice": "Code Interpreter is currently only supported for OpenAI models.", + "requestSupportLink": "Request support for other providers" + }, + "filePreviewCard": { + "uploadPreview": "Upload preview", + "pdf": "PDF", + "image": "Image" + }, + "mcpEventsDisplay": { + "listTools": "List tools", + "toolCall": "Tool call", + "request": "Request", + "approved": "Approved", + "response": "Response" + }, + "realtimePlayground": { + "title": "Realtime Voice Chat", + "selectModelFirst": "Please select a model first", + "connectedToApi": "Connected to realtime API", + "wsError": "Error: {{error}}", + "websocketError": "WebSocket error", + "disconnected": "Disconnected", + "connectionFailed": "Connection failed: {{error}}", + "listening": "🎙️ Listening...", + "microphoneError": "Microphone error: {{error}}", + "connected": "Connected", + "connecting": "Connecting...", + "disconnectedStatus": "Disconnected", + "connect": "Connect", + "disconnect": "Disconnect", + "emptyTitle": "Realtime Voice Playground", + "emptyHint": "Click Connect to start a realtime session. You can speak using your microphone or type messages. The AI will respond with voice and text.", + "you": "You", + "ai": "AI", + "stopRecording": "Stop recording", + "startRecording": "Start recording", + "inputPlaceholder": "Type a message or use the mic...", + "listeningHint": "Listening — speak into your microphone. Server VAD will detect when you stop." + }, + "responseMetrics": { + "timeToFirstToken": "Time to first token", + "totalLatency": "Total latency", + "promptTokens": "Prompt tokens", + "completionTokens": "Completion tokens", + "reasoningTokens": "Reasoning tokens", + "totalTokens": "Total tokens", + "cost": "Cost", + "toolUsed": "Tool used", + "ttft": "TTFT: {{value}}s", + "totalLatencyValue": "Total Latency: {{value}}s", + "in": "In: {{count}}", + "out": "Out: {{count}}", + "reasoning": "Reasoning: {{count}}", + "total": "Total: {{count}}", + "toolName": "Tool: {{name}}" + }, + "searchResultsDisplay": { + "hideSources": "Hide sources", + "showSources": "Show sources ({{count}})", + "query": "Query:", + "resultCount_one": "{{count}} result", + "resultCount_other": "{{count}} results", + "metadata": "Metadata:" + }, + "sessionManagement": { + "title": "Session Management", + "toggleTooltip": "Choose between LiteLLM API session management (using previous_response_id) or UI-based session management (using chat history)", + "copiedToClipboard": "Response ID copied to clipboard!", + "apiSessionReady": "API Session: Ready", + "uiSessionReady": "UI Session: Ready", + "responseIdPrefix": "Response ID", + "uiSessionPrefix": "UI Session", + "apiSessionReadyDesc": "LiteLLM will manage session using previous_response_id", + "uiSessionReadyDesc": "UI will manage session using chat history", + "apiSessionActiveDesc": "LiteLLM API session active - context maintained server-side", + "uiSessionActiveDesc": "UI session active - context maintained client-side", + "copyTooltipTitle": "Copy response ID to continue session:" } } } diff --git a/ui/litellm-dashboard/src/locales/zh-CN.json b/ui/litellm-dashboard/src/locales/zh-CN.json index e003a4b4658..40ae7c1a843 100644 --- a/ui/litellm-dashboard/src/locales/zh-CN.json +++ b/ui/litellm-dashboard/src/locales/zh-CN.json @@ -3972,6 +3972,94 @@ "toolsetsExample": "示例:一个「GitHub 只读」工具集可能只包含来自 GitHub MCP 服务器的 list_repos 和 get_file——防止 Agent 进行写操作。", "toolsetsCreatingTitle": "创建工具集:", "toolsetsCreatingDesc": "管理员可以从 MCP 页面 → 工具集标签页创建和管理工具集。工具集随后可分配给密钥和团队以限定其工具访问范围。" + }, + "codeInterpreterOutput": { + "pythonCodeExecuted": "已执行的 Python 代码", + "loadingImage": "图片加载中...", + "generatedChart": "生成的图表", + "imageNotAvailable": "图片不可用" + }, + "codeInterpreterTool": { + "title": "代码解释器", + "tooltipDesc": "运行 Python 代码以生成文件、图表并分析数据。容器将自动创建。", + "onlyOpenAIWarning": "代码解释器仅适用于 OpenAI 模型", + "onlyOpenAINotice": "代码解释器目前仅支持 OpenAI 模型。", + "requestSupportLink": "申请支持其他提供商" + }, + "filePreviewCard": { + "uploadPreview": "上传预览", + "pdf": "PDF", + "image": "图片" + }, + "mcpEventsDisplay": { + "listTools": "列出工具", + "toolCall": "工具调用", + "request": "请求", + "approved": "已批准", + "response": "响应" + }, + "realtimePlayground": { + "title": "实时语音对话", + "selectModelFirst": "请先选择一个模型", + "connectedToApi": "已连接到实时 API", + "wsError": "错误:{{error}}", + "websocketError": "WebSocket 错误", + "disconnected": "已断开", + "connectionFailed": "连接失败:{{error}}", + "listening": "🎙️ 正在监听...", + "microphoneError": "麦克风错误:{{error}}", + "connected": "已连接", + "connecting": "连接中...", + "disconnectedStatus": "已断开", + "connect": "连接", + "disconnect": "断开", + "emptyTitle": "实时语音 Playground", + "emptyHint": "点击「连接」开始实时会话。您可以使用麦克风说话或输入文字,AI 将以语音和文字回复。", + "you": "您", + "ai": "AI", + "stopRecording": "停止录音", + "startRecording": "开始录音", + "inputPlaceholder": "输入消息或使用麦克风...", + "listeningHint": "正在监听 — 请对麦克风说话,服务器 VAD 将检测您何时停止说话。" + }, + "responseMetrics": { + "timeToFirstToken": "首 Token 时间", + "totalLatency": "总延迟", + "promptTokens": "提示词 Token", + "completionTokens": "补全 Token", + "reasoningTokens": "推理 Token", + "totalTokens": "总 Token", + "cost": "费用", + "toolUsed": "使用的工具", + "ttft": "TTFT:{{value}}s", + "totalLatencyValue": "总延迟:{{value}}s", + "in": "输入:{{count}}", + "out": "输出:{{count}}", + "reasoning": "推理:{{count}}", + "total": "总计:{{count}}", + "toolName": "工具:{{name}}" + }, + "searchResultsDisplay": { + "hideSources": "隐藏来源", + "showSources": "显示来源({{count}})", + "query": "查询:", + "resultCount_one": "{{count}} 条结果", + "resultCount_other": "{{count}} 条结果", + "metadata": "元数据:" + }, + "sessionManagement": { + "title": "会话管理", + "toggleTooltip": "在 LiteLLM API 会话管理(使用 previous_response_id)和基于 UI 的会话管理(使用聊天历史)之间选择", + "copiedToClipboard": "响应 ID 已复制到剪贴板!", + "apiSessionReady": "API 会话:就绪", + "uiSessionReady": "UI 会话:就绪", + "responseIdPrefix": "响应 ID", + "uiSessionPrefix": "UI 会话", + "apiSessionReadyDesc": "LiteLLM 将使用 previous_response_id 管理会话", + "uiSessionReadyDesc": "UI 将使用聊天历史管理会话", + "apiSessionActiveDesc": "LiteLLM API 会话活跃 - 上下文在服务端维护", + "uiSessionActiveDesc": "UI 会话活跃 - 上下文在客户端维护", + "copyTooltipTitle": "复制响应 ID 以继续会话:" } } }