mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for guardrail test playground
This commit is contained in:
parent
95af75657c
commit
fbb8477d23
5 changed files with 149 additions and 34 deletions
|
|
@ -2,6 +2,7 @@ import React, { useState } from "react";
|
|||
import { Button } from "@tremor/react";
|
||||
import { Input, Typography, Tooltip } from "antd";
|
||||
import { CopyOutlined, InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import NotificationsManager from "../molecules/notifications_manager";
|
||||
import GuardrailTestResults from "./GuardrailTestResults";
|
||||
|
||||
|
|
@ -25,11 +26,12 @@ export function GuardrailTestPanel({
|
|||
errors,
|
||||
onClose,
|
||||
}: GuardrailTestPanelProps) {
|
||||
const { t } = useTranslation();
|
||||
const [inputText, setInputText] = useState("");
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!inputText.trim()) {
|
||||
NotificationsManager.fromBackend("Please enter text to test");
|
||||
NotificationsManager.fromBackend(t("guardrails.guardrailTestPanel.pleaseEnterText"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -74,9 +76,9 @@ export function GuardrailTestPanel({
|
|||
const handleCopyInput = async () => {
|
||||
const success = await copyToClipboard(inputText);
|
||||
if (success) {
|
||||
NotificationsManager.success("Input copied to clipboard");
|
||||
NotificationsManager.success(t("guardrails.guardrailTestPanel.inputCopied"));
|
||||
} else {
|
||||
NotificationsManager.fromBackend("Failed to copy input");
|
||||
NotificationsManager.fromBackend(t("guardrails.guardrailTestPanel.failedToCopyInput"));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -87,7 +89,7 @@ export function GuardrailTestPanel({
|
|||
<div className="flex items-center space-x-3">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center space-x-2 mb-1">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Test Guardrails:</h2>
|
||||
<h2 className="text-lg font-semibold text-gray-900">{t("guardrails.guardrailTestPanel.title")}</h2>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{guardrailNames.map((name) => (
|
||||
<div
|
||||
|
|
@ -100,7 +102,7 @@ export function GuardrailTestPanel({
|
|||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500">
|
||||
Test {guardrailNames.length > 1 ? "guardrails" : "guardrail"} and compare results
|
||||
{t("guardrails.guardrailTestPanel.subtitle", { count: guardrailNames.length })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -112,14 +114,16 @@ export function GuardrailTestPanel({
|
|||
<div>
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<label className="text-sm font-medium text-gray-700">Input Text</label>
|
||||
<Tooltip title="Press Enter to submit. Use Shift+Enter for new line.">
|
||||
<label className="text-sm font-medium text-gray-700">
|
||||
{t("guardrails.guardrailTestPanel.inputText")}
|
||||
</label>
|
||||
<Tooltip title={t("guardrails.guardrailTestPanel.inputTooltip")}>
|
||||
<InfoCircleOutlined className="text-gray-400 cursor-help" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
{inputText && (
|
||||
<Button size="xs" variant="secondary" icon={CopyOutlined} onClick={handleCopyInput}>
|
||||
Copy Input
|
||||
{t("guardrails.guardrailTestPanel.copyInput")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -127,26 +131,31 @@ export function GuardrailTestPanel({
|
|||
value={inputText}
|
||||
onChange={(e) => setInputText(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Enter text to test with guardrails..."
|
||||
placeholder={t("guardrails.guardrailTestPanel.inputPlaceholder")}
|
||||
rows={8}
|
||||
className="font-mono text-sm"
|
||||
/>
|
||||
<div className="flex justify-between items-center mt-1">
|
||||
<Text className="text-xs text-gray-500">
|
||||
Press <kbd className="px-1 py-0.5 bg-gray-100 border border-gray-300 rounded text-xs">Enter</kbd> to
|
||||
submit •{" "}
|
||||
<kbd className="px-1 py-0.5 bg-gray-100 border border-gray-300 rounded text-xs">Shift+Enter</kbd> for
|
||||
new line
|
||||
<Trans
|
||||
i18nKey="guardrails.guardrailTestPanel.keyboardHint"
|
||||
components={{
|
||||
enter: <kbd className="px-1 py-0.5 bg-gray-100 border border-gray-300 rounded text-xs" />,
|
||||
shiftEnter: <kbd className="px-1 py-0.5 bg-gray-100 border border-gray-300 rounded text-xs" />,
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
<Text className="text-xs text-gray-500">
|
||||
{t("guardrails.guardrailTestPanel.characters", { count: inputText.length })}
|
||||
</Text>
|
||||
<Text className="text-xs text-gray-500">Characters: {inputText.length}</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-2">
|
||||
<Button onClick={handleSubmit} loading={isLoading} disabled={!inputText.trim()} className="w-full">
|
||||
{isLoading
|
||||
? `Testing ${guardrailNames.length} guardrail${guardrailNames.length > 1 ? "s" : ""}...`
|
||||
: `Test ${guardrailNames.length} guardrail${guardrailNames.length > 1 ? "s" : ""}`}
|
||||
? t("guardrails.guardrailTestPanel.testingButton", { count: guardrailNames.length })
|
||||
: t("guardrails.guardrailTestPanel.testButton", { count: guardrailNames.length })}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useState } from "react";
|
||||
import { Card, List, Empty, Spin, Input, Typography } from "antd";
|
||||
import { ExperimentOutlined, SearchOutlined } from "@ant-design/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import GuardrailTestPanel from "./GuardrailTestPanel";
|
||||
import { applyGuardrail } from "../networking";
|
||||
import NotificationsManager from "../molecules/notifications_manager";
|
||||
|
|
@ -43,6 +44,7 @@ const GuardrailTestPlayground: React.FC<GuardrailTestPlaygroundProps> = ({
|
|||
accessToken,
|
||||
onClose,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [selectedGuardrails, setSelectedGuardrails] = useState<Set<string>>(new Set());
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [testResults, setTestResults] = useState<TestResult[]>([]);
|
||||
|
|
@ -103,10 +105,10 @@ const GuardrailTestPlayground: React.FC<GuardrailTestPlaygroundProps> = ({
|
|||
setIsTesting(false);
|
||||
|
||||
if (results.length > 0) {
|
||||
NotificationsManager.success(`${results.length} guardrail${results.length > 1 ? "s" : ""} applied successfully`);
|
||||
NotificationsManager.success(t("guardrails.guardrailTestPlayground.appliedSuccess", { count: results.length }));
|
||||
}
|
||||
if (errors.length > 0) {
|
||||
NotificationsManager.fromBackend(`${errors.length} guardrail${errors.length > 1 ? "s" : ""} failed`);
|
||||
NotificationsManager.fromBackend(t("guardrails.guardrailTestPlayground.applyFailed", { count: errors.length }));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -118,10 +120,12 @@ const GuardrailTestPlayground: React.FC<GuardrailTestPlaygroundProps> = ({
|
|||
<div className="w-1/4 border-r border-gray-200 flex flex-col overflow-hidden">
|
||||
<div className="p-4 border-b border-gray-200">
|
||||
<div className="mb-3">
|
||||
<h3 className="text-lg font-semibold mb-3">Guardrails</h3>
|
||||
<h3 className="text-lg font-semibold mb-3">
|
||||
{t("guardrails.guardrailTestPlayground.guardrailsHeading")}
|
||||
</h3>
|
||||
<Input
|
||||
prefix={<SearchOutlined />}
|
||||
placeholder="Search guardrails..."
|
||||
placeholder={t("guardrails.guardrailTestPlayground.searchPlaceholder")}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
|
|
@ -135,7 +139,13 @@ const GuardrailTestPlayground: React.FC<GuardrailTestPlaygroundProps> = ({
|
|||
</div>
|
||||
) : filteredGuardrails.length === 0 ? (
|
||||
<div className="p-4">
|
||||
<Empty description={searchQuery ? "No guardrails match your search" : "No guardrails available"} />
|
||||
<Empty
|
||||
description={
|
||||
searchQuery
|
||||
? t("guardrails.guardrailTestPlayground.noGuardrailsMatch")
|
||||
: t("guardrails.guardrailTestPlayground.noGuardrailsAvailable")
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<List
|
||||
|
|
@ -164,11 +174,11 @@ const GuardrailTestPlayground: React.FC<GuardrailTestPlaygroundProps> = ({
|
|||
description={
|
||||
<div className="text-xs space-y-1 mt-1">
|
||||
<div>
|
||||
<span className="font-medium">Type: </span>
|
||||
<span className="font-medium">{t("guardrails.guardrailTestPlayground.typeLabel")} </span>
|
||||
<span className="text-gray-600">{guardrail.litellm_params.guardrail}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium">Mode: </span>
|
||||
<span className="font-medium">{t("guardrails.guardrailTestPlayground.modeLabel")} </span>
|
||||
<span className="text-gray-600">{guardrail.litellm_params.mode}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -182,7 +192,10 @@ const GuardrailTestPlayground: React.FC<GuardrailTestPlaygroundProps> = ({
|
|||
|
||||
<div className="p-3 border-t border-gray-200 bg-gray-50">
|
||||
<Typography.Text className="text-xs text-gray-600">
|
||||
{selectedGuardrails.size} of {filteredGuardrails.length} selected
|
||||
{t("guardrails.guardrailTestPlayground.selectedCount", {
|
||||
selected: selectedGuardrails.size,
|
||||
total: filteredGuardrails.length,
|
||||
})}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -191,7 +204,7 @@ const GuardrailTestPlayground: React.FC<GuardrailTestPlaygroundProps> = ({
|
|||
<div className="w-3/4 flex flex-col bg-white">
|
||||
<div className="p-4 border-b border-gray-200 flex justify-between items-center">
|
||||
<Typography.Title level={2} className="text-xl font-semibold mb-0">
|
||||
Guardrail Testing Playground
|
||||
{t("guardrails.guardrailTestPlayground.pageTitle")}
|
||||
</Typography.Title>
|
||||
</div>
|
||||
|
||||
|
|
@ -200,10 +213,10 @@ const GuardrailTestPlayground: React.FC<GuardrailTestPlaygroundProps> = ({
|
|||
<div className="h-full flex flex-col items-center justify-center text-gray-400">
|
||||
<ExperimentOutlined style={{ fontSize: "48px", marginBottom: "16px" }} />
|
||||
<Typography.Paragraph className="text-lg font-medium text-gray-600 mb-2">
|
||||
Select Guardrails to Test
|
||||
{t("guardrails.guardrailTestPlayground.emptyStateTitle")}
|
||||
</Typography.Paragraph>
|
||||
<Typography.Paragraph className="text-center text-gray-500 max-w-md">
|
||||
Choose one or more guardrails from the left sidebar to start testing and comparing results.
|
||||
{t("guardrails.guardrailTestPlayground.emptyStateDesc")}
|
||||
</Typography.Paragraph>
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React, { useState } from "react";
|
|||
import { Button, Card } from "@tremor/react";
|
||||
import { Typography } from "antd";
|
||||
import { CopyOutlined, CheckCircleOutlined, ClockCircleOutlined, DownOutlined, RightOutlined } from "@ant-design/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import NotificationsManager from "../molecules/notifications_manager";
|
||||
|
||||
const { Text } = Typography;
|
||||
|
|
@ -24,6 +25,7 @@ interface GuardrailTestResultsProps {
|
|||
}
|
||||
|
||||
export function GuardrailTestResults({ results, errors }: GuardrailTestResultsProps) {
|
||||
const { t } = useTranslation();
|
||||
const [collapsedResults, setCollapsedResults] = useState<Set<string>>(new Set());
|
||||
|
||||
const toggleResultCollapse = (guardrailName: string) => {
|
||||
|
|
@ -70,7 +72,7 @@ export function GuardrailTestResults({ results, errors }: GuardrailTestResultsPr
|
|||
|
||||
return (
|
||||
<div className="space-y-3 pt-4 border-t border-gray-200">
|
||||
<h3 className="text-sm font-semibold text-gray-900">Results</h3>
|
||||
<h3 className="text-sm font-semibold text-gray-900">{t("guardrails.guardrailTestResults.resultsHeading")}</h3>
|
||||
|
||||
{/* Success Results */}
|
||||
{results &&
|
||||
|
|
@ -105,13 +107,13 @@ export function GuardrailTestResults({ results, errors }: GuardrailTestResultsPr
|
|||
onClick={async () => {
|
||||
const success = await copyToClipboard(result.response_text);
|
||||
if (success) {
|
||||
NotificationsManager.success("Result copied to clipboard");
|
||||
NotificationsManager.success(t("guardrails.guardrailTestResults.resultCopied"));
|
||||
} else {
|
||||
NotificationsManager.fromBackend("Failed to copy result");
|
||||
NotificationsManager.fromBackend(t("guardrails.guardrailTestResults.failedToCopyResult"));
|
||||
}
|
||||
}}
|
||||
>
|
||||
Copy
|
||||
{t("common.copy")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -119,13 +121,16 @@ export function GuardrailTestResults({ results, errors }: GuardrailTestResultsPr
|
|||
{!isCollapsed && (
|
||||
<>
|
||||
<div className="bg-white border border-green-200 rounded p-3">
|
||||
<label className="text-xs font-medium text-gray-600 mb-2 block">Output Text</label>
|
||||
<label className="text-xs font-medium text-gray-600 mb-2 block">
|
||||
{t("guardrails.guardrailTestResults.outputText")}
|
||||
</label>
|
||||
<div className="font-mono text-sm text-gray-900 whitespace-pre-wrap break-words">
|
||||
{result.response_text}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs text-gray-600">
|
||||
<span className="font-medium">Characters:</span> {result.response_text.length}
|
||||
<span className="font-medium">{t("guardrails.guardrailTestResults.characters")}</span>{" "}
|
||||
{result.response_text.length}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -163,7 +168,7 @@ export function GuardrailTestResults({ results, errors }: GuardrailTestResultsPr
|
|||
className="text-sm font-medium text-red-800 cursor-pointer"
|
||||
onClick={() => toggleResultCollapse(errorItem.guardrailName)}
|
||||
>
|
||||
{errorItem.guardrailName} - Error
|
||||
{t("guardrails.guardrailTestResults.errorTitle", { name: errorItem.guardrailName })}
|
||||
</p>
|
||||
<div className="flex items-center space-x-1 text-xs text-gray-600">
|
||||
<ClockCircleOutlined />
|
||||
|
|
|
|||
|
|
@ -2421,5 +2421,49 @@
|
|||
"showJson": "Show JSON",
|
||||
"copied": "Copied!"
|
||||
}
|
||||
},
|
||||
"guardrails": {
|
||||
"guardrailTestPanel": {
|
||||
"title": "Test Guardrails:",
|
||||
"subtitle_one": "Test guardrail and compare results",
|
||||
"subtitle_other": "Test guardrails and compare results",
|
||||
"inputText": "Input Text",
|
||||
"inputTooltip": "Press Enter to submit. Use Shift+Enter for new line.",
|
||||
"copyInput": "Copy Input",
|
||||
"inputPlaceholder": "Enter text to test with guardrails...",
|
||||
"keyboardHint": "Press <enter>Enter</enter> to submit • <shiftEnter>Shift+Enter</shiftEnter> for new line",
|
||||
"characters": "Characters: {{count}}",
|
||||
"testButton_one": "Test {{count}} guardrail",
|
||||
"testButton_other": "Test {{count}} guardrails",
|
||||
"testingButton_one": "Testing {{count}} guardrail...",
|
||||
"testingButton_other": "Testing {{count}} guardrails...",
|
||||
"pleaseEnterText": "Please enter text to test",
|
||||
"inputCopied": "Input copied to clipboard",
|
||||
"failedToCopyInput": "Failed to copy input"
|
||||
},
|
||||
"guardrailTestPlayground": {
|
||||
"pageTitle": "Guardrail Testing Playground",
|
||||
"guardrailsHeading": "Guardrails",
|
||||
"searchPlaceholder": "Search guardrails...",
|
||||
"noGuardrailsMatch": "No guardrails match your search",
|
||||
"noGuardrailsAvailable": "No guardrails available",
|
||||
"typeLabel": "Type:",
|
||||
"modeLabel": "Mode:",
|
||||
"selectedCount": "{{selected}} of {{total}} selected",
|
||||
"emptyStateTitle": "Select Guardrails to Test",
|
||||
"emptyStateDesc": "Choose one or more guardrails from the left sidebar to start testing and comparing results.",
|
||||
"appliedSuccess_one": "{{count}} guardrail applied successfully",
|
||||
"appliedSuccess_other": "{{count}} guardrails applied successfully",
|
||||
"applyFailed_one": "{{count}} guardrail failed",
|
||||
"applyFailed_other": "{{count}} guardrails failed"
|
||||
},
|
||||
"guardrailTestResults": {
|
||||
"resultsHeading": "Results",
|
||||
"outputText": "Output Text",
|
||||
"characters": "Characters:",
|
||||
"errorTitle": "{{name}} - Error",
|
||||
"resultCopied": "Result copied to clipboard",
|
||||
"failedToCopyResult": "Failed to copy result"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2421,5 +2421,49 @@
|
|||
"showJson": "显示 JSON",
|
||||
"copied": "已复制!"
|
||||
}
|
||||
},
|
||||
"guardrails": {
|
||||
"guardrailTestPanel": {
|
||||
"title": "测试护栏:",
|
||||
"subtitle_one": "测试护栏并比较结果",
|
||||
"subtitle_other": "测试护栏并比较结果",
|
||||
"inputText": "输入文本",
|
||||
"inputTooltip": "按 Enter 提交,按 Shift+Enter 换行。",
|
||||
"copyInput": "复制输入",
|
||||
"inputPlaceholder": "输入要用护栏测试的文本...",
|
||||
"keyboardHint": "按 <enter>Enter</enter> 提交 • <shiftEnter>Shift+Enter</shiftEnter> 换行",
|
||||
"characters": "字符数:{{count}}",
|
||||
"testButton_one": "测试 {{count}} 个护栏",
|
||||
"testButton_other": "测试 {{count}} 个护栏",
|
||||
"testingButton_one": "正在测试 {{count}} 个护栏...",
|
||||
"testingButton_other": "正在测试 {{count}} 个护栏...",
|
||||
"pleaseEnterText": "请输入要测试的文本",
|
||||
"inputCopied": "输入已复制到剪贴板",
|
||||
"failedToCopyInput": "复制输入失败"
|
||||
},
|
||||
"guardrailTestPlayground": {
|
||||
"pageTitle": "护栏测试 Playground",
|
||||
"guardrailsHeading": "护栏",
|
||||
"searchPlaceholder": "搜索护栏...",
|
||||
"noGuardrailsMatch": "没有护栏匹配您的搜索",
|
||||
"noGuardrailsAvailable": "暂无护栏可用",
|
||||
"typeLabel": "类型:",
|
||||
"modeLabel": "模式:",
|
||||
"selectedCount": "已选 {{selected}} / {{total}}",
|
||||
"emptyStateTitle": "选择要测试的护栏",
|
||||
"emptyStateDesc": "从左侧边栏选择一个或多个护栏,开始测试并比较结果。",
|
||||
"appliedSuccess_one": "{{count}} 个护栏已成功应用",
|
||||
"appliedSuccess_other": "{{count}} 个护栏已成功应用",
|
||||
"applyFailed_one": "{{count}} 个护栏执行失败",
|
||||
"applyFailed_other": "{{count}} 个护栏执行失败"
|
||||
},
|
||||
"guardrailTestResults": {
|
||||
"resultsHeading": "结果",
|
||||
"outputText": "输出文本",
|
||||
"characters": "字符数:",
|
||||
"errorTitle": "{{name}} - 错误",
|
||||
"resultCopied": "结果已复制到剪贴板",
|
||||
"failedToCopyResult": "复制结果失败"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue