mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-05 02:41:56 +00:00
feat(ui): i18n for compliance UI
This commit is contained in:
parent
e1d3ea9aa5
commit
2932904741
4 changed files with 319 additions and 99 deletions
|
|
@ -1602,11 +1602,6 @@
|
|||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/playground/complianceUI/ComplianceUI.tsx": {
|
||||
"react-hooks/preserve-manual-memoization": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"src/components/playground/llm_calls/a2a_send_message.tsx": {
|
||||
"max-params": {
|
||||
"count": 2
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { getGuardrailsList, testPoliciesAndGuardrails } from "@/components/netwo
|
|||
import PolicySelector, { getPolicyOptionEntries } from "@/components/policies/PolicySelector";
|
||||
import { Policy } from "@/components/policies/types";
|
||||
import { makeOpenAIChatCompletionRequest } from "../llm_calls/chat_completion";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
AlertTriangle,
|
||||
BarChart3,
|
||||
|
|
@ -123,6 +124,7 @@ export default function ComplianceUI({
|
|||
fixedModel,
|
||||
proxySettings,
|
||||
}: ComplianceUIProps) {
|
||||
const { t } = useTranslation();
|
||||
const frameworks = getFrameworks();
|
||||
|
||||
const [policyValueToLabel, setPolicyValueToLabel] = useState<Map<string, string>>(new Map());
|
||||
|
|
@ -194,7 +196,7 @@ export default function ComplianceUI({
|
|||
const customFrameworks: ComplianceFramework[] = Array.from(fwMap.entries()).map(([fwName, catMap]) => ({
|
||||
name: fwName,
|
||||
icon: customPrompts.find((p) => p.framework === fwName)?.categoryIcon ?? "file-text",
|
||||
description: `Custom prompts — ${fwName}.`,
|
||||
description: t("playground.complianceUi.customPromptsDesc", { name: fwName }),
|
||||
categories: Array.from(catMap.entries()).map(([catName, prompts]) => ({
|
||||
name: catName,
|
||||
icon: prompts[0]?.categoryIcon ?? "file-text",
|
||||
|
|
@ -275,7 +277,7 @@ export default function ComplianceUI({
|
|||
framework: "Custom",
|
||||
category: "Custom Prompts",
|
||||
categoryIcon: "pencil",
|
||||
categoryDescription: "Custom prompts added this session.",
|
||||
categoryDescription: t("playground.complianceUi.customPromptsSessionDesc"),
|
||||
prompt: newPromptText.trim(),
|
||||
expectedResult: newPromptExpected,
|
||||
};
|
||||
|
|
@ -330,11 +332,11 @@ export default function ComplianceUI({
|
|||
setCsvError(null);
|
||||
|
||||
if (!file.name.endsWith(".csv") && file.type !== "text/csv") {
|
||||
setCsvError("Please upload a .csv file.");
|
||||
setCsvError(t("playground.complianceUi.csvErrorNotCsv"));
|
||||
return;
|
||||
}
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
setCsvError("File too large (max 5 MB).");
|
||||
setCsvError(t("playground.complianceUi.csvErrorTooLarge"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -343,16 +345,14 @@ export default function ComplianceUI({
|
|||
skipEmptyLines: true,
|
||||
complete: (results) => {
|
||||
if (!results.data || results.data.length === 0) {
|
||||
setCsvError("CSV file is empty.");
|
||||
setCsvError(t("playground.complianceUi.csvErrorEmpty"));
|
||||
return;
|
||||
}
|
||||
|
||||
const headers = results.meta.fields ?? [];
|
||||
const missing = EXPECTED_CSV_COLUMNS.filter((col) => !headers.includes(col));
|
||||
if (missing.length > 0) {
|
||||
setCsvError(
|
||||
`Missing required columns: ${missing.join(", ")}. Expected: prompt, expected_result. Optional: framework, category.`,
|
||||
);
|
||||
setCsvError(t("playground.complianceUi.csvErrorMissingColumns", { columns: missing.join(", ") }));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -365,11 +365,13 @@ export default function ComplianceUI({
|
|||
const expected = row.expected_result?.trim().toLowerCase();
|
||||
|
||||
if (!prompt) {
|
||||
errors.push(`Row ${rowNum}: missing prompt text`);
|
||||
errors.push(t("playground.complianceUi.csvRowMissingPrompt", { row: rowNum }));
|
||||
return;
|
||||
}
|
||||
if (expected !== "fail" && expected !== "pass") {
|
||||
errors.push(`Row ${rowNum}: expected_result must be "fail" or "pass", got "${row.expected_result ?? ""}"`);
|
||||
errors.push(
|
||||
t("playground.complianceUi.csvRowInvalidExpected", { row: rowNum, value: row.expected_result ?? "" }),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -381,7 +383,7 @@ export default function ComplianceUI({
|
|||
framework,
|
||||
category,
|
||||
categoryIcon: "file-text",
|
||||
categoryDescription: `Prompts uploaded from CSV — ${category}.`,
|
||||
categoryDescription: t("playground.complianceUi.csvPromptsDesc", { category }),
|
||||
prompt,
|
||||
expectedResult: expected as "fail" | "pass",
|
||||
});
|
||||
|
|
@ -389,13 +391,16 @@ export default function ComplianceUI({
|
|||
|
||||
if (errors.length > 0) {
|
||||
setCsvError(
|
||||
errors.slice(0, 5).join("\n") + (errors.length > 5 ? `\n...and ${errors.length - 5} more errors` : ""),
|
||||
errors.slice(0, 5).join("\n") +
|
||||
(errors.length > 5
|
||||
? "\n" + t("playground.complianceUi.csvMoreErrors", { count: errors.length - 5 })
|
||||
: ""),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPrompts.length === 0) {
|
||||
setCsvError("No valid prompts found in CSV.");
|
||||
setCsvError(t("playground.complianceUi.csvErrorNoValidPrompts"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -418,7 +423,7 @@ export default function ComplianceUI({
|
|||
setCsvError(null);
|
||||
},
|
||||
error: () => {
|
||||
setCsvError("Failed to parse CSV file.");
|
||||
setCsvError(t("playground.complianceUi.csvErrorParseFailed"));
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -470,7 +475,7 @@ export default function ComplianceUI({
|
|||
const sysMsg: QuickTestMessage = {
|
||||
id: `msg-${Date.now()}-sys`,
|
||||
type: "system",
|
||||
text: "Allowed — model response received.",
|
||||
text: t("playground.complianceUi.quickTestAllowedModel"),
|
||||
result: "allowed",
|
||||
returnedText: fullResponse,
|
||||
timestamp: new Date(),
|
||||
|
|
@ -492,8 +497,10 @@ export default function ComplianceUI({
|
|||
const returnedText = Array.isArray(inputs?.texts) && inputs.texts.length > 0 ? inputs.texts[0] : undefined;
|
||||
const displayText =
|
||||
result === "blocked"
|
||||
? `Blocked — ${triggeredBy ?? "content filter"}`
|
||||
: "Allowed — no policy or guardrail violations detected.";
|
||||
? t("playground.complianceUi.quickTestBlocked", {
|
||||
triggeredBy: triggeredBy ?? t("playground.complianceUi.contentFilter"),
|
||||
})
|
||||
: t("playground.complianceUi.quickTestAllowedClean");
|
||||
const sysMsg: QuickTestMessage = {
|
||||
id: `msg-${Date.now()}-sys`,
|
||||
type: "system",
|
||||
|
|
@ -510,7 +517,7 @@ export default function ComplianceUI({
|
|||
const sysMsg: QuickTestMessage = {
|
||||
id: `msg-${Date.now()}-sys`,
|
||||
type: "system",
|
||||
text: `Error: ${errorMessage}`,
|
||||
text: t("playground.complianceUi.quickTestError", { errorMessage }),
|
||||
result: "blocked",
|
||||
triggeredBy: errorMessage,
|
||||
timestamp: new Date(),
|
||||
|
|
@ -608,7 +615,7 @@ export default function ComplianceUI({
|
|||
...row,
|
||||
actualResult: "blocked" as const,
|
||||
isMatch: false,
|
||||
triggeredBy: `Error: ${errorMessage}`,
|
||||
triggeredBy: t("playground.complianceUi.quickTestError", { errorMessage }),
|
||||
status: "complete" as const,
|
||||
})),
|
||||
);
|
||||
|
|
@ -687,11 +694,11 @@ export default function ComplianceUI({
|
|||
const testButtonLabel = (() => {
|
||||
const parts: string[] = [];
|
||||
if (selectedPolicies.length > 0)
|
||||
parts.push(`${selectedPolicies.length} ${selectedPolicies.length === 1 ? "policy" : "policies"}`);
|
||||
parts.push(t("playground.complianceUi.policyCount", { count: selectedPolicies.length }));
|
||||
if (selectedGuardrails.length > 0)
|
||||
parts.push(`${selectedGuardrails.length} ${selectedGuardrails.length === 1 ? "guardrail" : "guardrails"}`);
|
||||
if (parts.length === 0) return "Test";
|
||||
return `Test ${parts.join(" & ")}`;
|
||||
parts.push(t("playground.complianceUi.guardrailCount", { count: selectedGuardrails.length }));
|
||||
if (parts.length === 0) return t("playground.complianceUi.testButton");
|
||||
return t("playground.complianceUi.testButtonWithConfig", { config: parts.join(" & ") });
|
||||
})();
|
||||
|
||||
return (
|
||||
|
|
@ -700,14 +707,14 @@ export default function ComplianceUI({
|
|||
{/* Top config */}
|
||||
<div className="flex-shrink-0 border-b border-gray-200 px-6 py-4">
|
||||
<div className="mb-3">
|
||||
<h3 className="text-sm font-semibold text-gray-900">Test Configuration</h3>
|
||||
<p className="text-xs text-gray-500 mt-0.5">Select policies, guardrails, or both to test against.</p>
|
||||
<h3 className="text-sm font-semibold text-gray-900">{t("playground.complianceUi.testConfiguration")}</h3>
|
||||
<p className="text-xs text-gray-500 mt-0.5">{t("playground.complianceUi.testConfigurationDesc")}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-3 flex-wrap">
|
||||
<div className="flex-1 min-w-[200px]">
|
||||
<label className="text-[11px] font-medium text-gray-500 uppercase tracking-wide mb-1.5 block">
|
||||
Policies
|
||||
{t("playground.complianceUi.policiesLabel")}
|
||||
</label>
|
||||
{accessToken && (
|
||||
<PolicySelector
|
||||
|
|
@ -721,13 +728,15 @@ export default function ComplianceUI({
|
|||
|
||||
<div className="flex flex-col items-center pt-6 flex-shrink-0">
|
||||
<div className="w-px h-4 bg-gray-200" />
|
||||
<span className="text-[10px] font-medium text-gray-400 my-1">or</span>
|
||||
<span className="text-[10px] font-medium text-gray-400 my-1">
|
||||
{t("playground.complianceUi.orSeparator")}
|
||||
</span>
|
||||
<div className="w-px h-4 bg-gray-200" />
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-w-[200px]">
|
||||
<label className="text-[11px] font-medium text-gray-500 uppercase tracking-wide mb-1.5 block">
|
||||
Guardrails
|
||||
{t("playground.complianceUi.guardrailsLabel")}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<button
|
||||
|
|
@ -736,7 +745,9 @@ export default function ComplianceUI({
|
|||
className="w-full flex items-center justify-between border border-gray-200 rounded-lg px-3 py-2 text-sm text-left hover:border-gray-300 transition-colors"
|
||||
>
|
||||
<span className={selectedGuardrails.length > 0 ? "text-gray-700" : "text-gray-400"}>
|
||||
{selectedGuardrails.length > 0 ? `${selectedGuardrails.length} selected` : "None selected"}
|
||||
{selectedGuardrails.length > 0
|
||||
? t("playground.complianceUi.guardrailsSelected", { count: selectedGuardrails.length })
|
||||
: t("playground.complianceUi.guardrailsNoneSelected")}
|
||||
</span>
|
||||
<ChevronDown className="w-4 h-4 text-gray-400" />
|
||||
</button>
|
||||
|
|
@ -744,7 +755,7 @@ export default function ComplianceUI({
|
|||
<div className="absolute z-30 top-full left-0 right-0 mt-1 bg-white border border-gray-200 rounded-lg shadow-lg py-1 max-h-52 overflow-y-auto">
|
||||
{guardrailOptions.length === 0 ? (
|
||||
<div className="px-3 py-2 text-xs text-gray-500">
|
||||
No guardrails available. Create guardrails in the Guardrails page.
|
||||
{t("playground.complianceUi.noGuardrailsAvailable")}
|
||||
</div>
|
||||
) : (
|
||||
guardrailOptions.map((g) => (
|
||||
|
|
@ -783,7 +794,7 @@ export default function ComplianceUI({
|
|||
type="button"
|
||||
onClick={() => toggleGuardrail(id)}
|
||||
className="hover:text-indigo-900"
|
||||
aria-label="Remove"
|
||||
aria-label={t("common.remove")}
|
||||
>
|
||||
<X className="w-2.5 h-2.5" />
|
||||
</button>
|
||||
|
|
@ -801,7 +812,7 @@ export default function ComplianceUI({
|
|||
onClick={() => batchAbortControllerRef.current?.abort()}
|
||||
className="flex items-center gap-1.5 px-4 py-2 rounded-lg text-sm font-medium transition-colors whitespace-nowrap bg-red-600 text-white hover:bg-red-700"
|
||||
>
|
||||
<Square className="w-3.5 h-3.5" /> Stop
|
||||
<Square className="w-3.5 h-3.5" /> {t("playground.complianceUi.stopButton")}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
|
|
@ -810,12 +821,13 @@ export default function ComplianceUI({
|
|||
disabled={selectedPromptIds.size === 0 || disabledPersonalKeyCreation}
|
||||
className={`flex items-center gap-1.5 px-4 py-2 rounded-lg text-sm font-medium transition-colors whitespace-nowrap ${selectedPromptIds.size === 0 || disabledPersonalKeyCreation ? "bg-gray-100 text-gray-400 cursor-not-allowed" : "bg-blue-600 text-white hover:bg-blue-700"}`}
|
||||
>
|
||||
<Play className="w-3.5 h-3.5" /> Simulate ({selectedPromptIds.size})
|
||||
<Play className="w-3.5 h-3.5" />{" "}
|
||||
{t("playground.complianceUi.simulateButton", { count: selectedPromptIds.size })}
|
||||
</button>
|
||||
)}
|
||||
{isRunning && (
|
||||
<span className="text-[11px] text-gray-500 flex items-center gap-1">
|
||||
<Loader2 className="w-3 h-3 animate-spin" /> Running...
|
||||
<Loader2 className="w-3 h-3 animate-spin" /> {t("playground.complianceUi.runningStatus")}
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
|
|
@ -828,7 +840,7 @@ export default function ComplianceUI({
|
|||
}}
|
||||
className="flex items-center justify-center gap-1.5 px-4 py-1.5 rounded-lg text-xs font-medium text-gray-500 hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
<RotateCcw className="w-3 h-3" /> Reset
|
||||
<RotateCcw className="w-3 h-3" /> {t("common.reset")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -841,7 +853,7 @@ export default function ComplianceUI({
|
|||
<div className="flex-1 overflow-y-auto min-h-0">
|
||||
<div className="px-4 pt-4 pb-2">
|
||||
<div className="flex items-center justify-between mb-2.5">
|
||||
<h3 className="text-sm font-semibold text-gray-900">Test Prompts</h3>
|
||||
<h3 className="text-sm font-semibold text-gray-900">{t("playground.complianceUi.testPrompts")}</h3>
|
||||
<span className="text-[11px] text-gray-400 tabular-nums">
|
||||
{selectedPromptIds.size}/{totalPromptCount}
|
||||
</span>
|
||||
|
|
@ -853,7 +865,7 @@ export default function ComplianceUI({
|
|||
type="text"
|
||||
value={searchPrompt}
|
||||
onChange={(e) => setSearchPrompt(e.target.value)}
|
||||
placeholder="Search prompts..."
|
||||
placeholder={t("playground.complianceUi.searchPromptsPlaceholder")}
|
||||
className="w-full border border-gray-200 rounded-lg pl-8 pr-3 py-1.5 text-xs placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-400"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -865,7 +877,7 @@ export default function ComplianceUI({
|
|||
onClick={selectAll}
|
||||
className="text-[11px] font-medium text-blue-600 hover:text-blue-700"
|
||||
>
|
||||
Select All
|
||||
{t("playground.complianceUi.selectAll")}
|
||||
</button>
|
||||
<span className="text-gray-300 text-[10px]">·</span>
|
||||
<button
|
||||
|
|
@ -873,7 +885,7 @@ export default function ComplianceUI({
|
|||
onClick={deselectAll}
|
||||
className="text-[11px] font-medium text-gray-500 hover:text-gray-700"
|
||||
>
|
||||
Clear
|
||||
{t("common.clear")}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
|
|
@ -885,7 +897,7 @@ export default function ComplianceUI({
|
|||
}}
|
||||
className={`flex items-center gap-1 text-[11px] font-medium px-2 py-0.5 rounded transition-colors ${showAddPrompt ? "bg-blue-50 text-blue-600" : "text-gray-500 hover:bg-gray-100"}`}
|
||||
>
|
||||
<Plus className="w-3 h-3" /> Add
|
||||
<Plus className="w-3 h-3" /> {t("common.add")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -895,7 +907,7 @@ export default function ComplianceUI({
|
|||
}}
|
||||
className={`flex items-center gap-1 text-[11px] font-medium px-2 py-0.5 rounded transition-colors ${showCsvUpload ? "bg-blue-50 text-blue-600" : "text-gray-500 hover:bg-gray-100"}`}
|
||||
>
|
||||
<Upload className="w-3 h-3" /> CSV
|
||||
<Upload className="w-3 h-3" /> {t("playground.complianceUi.csvButton")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -906,7 +918,7 @@ export default function ComplianceUI({
|
|||
<textarea
|
||||
value={newPromptText}
|
||||
onChange={(e) => setNewPromptText(e.target.value)}
|
||||
placeholder="Enter your test prompt..."
|
||||
placeholder={t("playground.complianceUi.enterPromptPlaceholder")}
|
||||
rows={2}
|
||||
className="w-full border border-gray-200 rounded px-2.5 py-1.5 text-xs text-gray-700 placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-400 resize-none bg-white"
|
||||
/>
|
||||
|
|
@ -917,14 +929,14 @@ export default function ComplianceUI({
|
|||
onClick={() => setNewPromptExpected("fail")}
|
||||
className={`text-[10px] font-semibold px-2 py-0.5 rounded ${newPromptExpected === "fail" ? "bg-red-100 text-red-700" : "bg-gray-100 text-gray-500"}`}
|
||||
>
|
||||
Should Fail
|
||||
{t("playground.complianceUi.shouldFail")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setNewPromptExpected("pass")}
|
||||
className={`text-[10px] font-semibold px-2 py-0.5 rounded ${newPromptExpected === "pass" ? "bg-green-100 text-green-700" : "bg-gray-100 text-gray-500"}`}
|
||||
>
|
||||
Should Pass
|
||||
{t("playground.complianceUi.shouldPass")}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
|
|
@ -936,7 +948,7 @@ export default function ComplianceUI({
|
|||
}}
|
||||
className="text-[11px] text-gray-500 px-2 py-1"
|
||||
>
|
||||
Cancel
|
||||
{t("common.cancel")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -944,7 +956,7 @@ export default function ComplianceUI({
|
|||
disabled={!newPromptText.trim()}
|
||||
className={`text-[11px] font-medium px-2.5 py-1 rounded ${newPromptText.trim() ? "bg-blue-600 text-white" : "bg-gray-100 text-gray-400"}`}
|
||||
>
|
||||
Add
|
||||
{t("common.add")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -954,25 +966,31 @@ export default function ComplianceUI({
|
|||
{showCsvUpload && (
|
||||
<div className="mx-4 mb-2 border border-blue-200 bg-blue-50/30 rounded-lg p-3">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-[11px] font-semibold text-gray-700">Upload CSV Dataset</span>
|
||||
<span className="text-[11px] font-semibold text-gray-700">
|
||||
{t("playground.complianceUi.uploadCsvDataset")}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={downloadCsvTemplate}
|
||||
className="flex items-center gap-1 text-[10px] font-medium text-blue-600 hover:text-blue-700"
|
||||
>
|
||||
<Download className="w-3 h-3" /> Download Template
|
||||
<Download className="w-3 h-3" /> {t("playground.complianceUi.downloadTemplate")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mb-2 p-2 bg-white rounded border border-gray-200">
|
||||
<p className="text-[10px] text-gray-500 leading-relaxed">
|
||||
<span className="font-semibold text-gray-600">Required columns:</span>{" "}
|
||||
<span className="font-semibold text-gray-600">
|
||||
{t("playground.complianceUi.csvRequiredColumns")}
|
||||
</span>{" "}
|
||||
<code className="bg-gray-100 px-1 rounded text-[10px]">prompt</code>,{" "}
|
||||
<code className="bg-gray-100 px-1 rounded text-[10px]">expected_result</code>{" "}
|
||||
<span className="text-gray-400">(fail or pass)</span>
|
||||
<span className="text-gray-400">{t("playground.complianceUi.csvFailOrPass")}</span>
|
||||
</p>
|
||||
<p className="text-[10px] text-gray-500 leading-relaxed mt-0.5">
|
||||
<span className="font-semibold text-gray-600">Optional columns:</span>{" "}
|
||||
<span className="font-semibold text-gray-600">
|
||||
{t("playground.complianceUi.csvOptionalColumns")}
|
||||
</span>{" "}
|
||||
<code className="bg-gray-100 px-1 rounded text-[10px]">framework</code>,{" "}
|
||||
<code className="bg-gray-100 px-1 rounded text-[10px]">category</code>
|
||||
</p>
|
||||
|
|
@ -993,7 +1011,7 @@ export default function ComplianceUI({
|
|||
onClick={() => csvInputRef.current?.click()}
|
||||
className="w-full flex items-center justify-center gap-1.5 py-2 border-2 border-dashed border-gray-300 rounded-lg text-xs text-gray-500 hover:border-blue-400 hover:text-blue-600 transition-colors"
|
||||
>
|
||||
<Upload className="w-3.5 h-3.5" /> Choose CSV file
|
||||
<Upload className="w-3.5 h-3.5" /> {t("playground.complianceUi.chooseCsvFile")}
|
||||
</button>
|
||||
|
||||
{csvError && (
|
||||
|
|
@ -1011,7 +1029,7 @@ export default function ComplianceUI({
|
|||
}}
|
||||
className="text-[11px] text-gray-500 px-2 py-1"
|
||||
>
|
||||
Cancel
|
||||
{t("common.cancel")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1040,7 +1058,9 @@ export default function ComplianceUI({
|
|||
<CategoryIcon iconKey={fw.icon} className="w-4 h-4 text-gray-500 flex-shrink-0" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<span className="text-xs font-semibold text-gray-900">{fw.name}</span>
|
||||
<span className="text-[10px] text-gray-400 ml-1.5">{fwPromptCount} prompts</span>
|
||||
<span className="text-[10px] text-gray-400 ml-1.5">
|
||||
{t("playground.complianceUi.promptCount", { count: fwPromptCount })}
|
||||
</span>
|
||||
</div>
|
||||
{fwSelectedCount > 0 && (
|
||||
<span className="text-[10px] font-medium bg-blue-100 text-blue-700 px-1.5 py-0.5 rounded-full">
|
||||
|
|
@ -1055,7 +1075,9 @@ export default function ComplianceUI({
|
|||
}}
|
||||
className="text-[10px] font-medium text-blue-600 hover:text-blue-700 px-1.5 py-0.5 rounded hover:bg-blue-50 flex-shrink-0"
|
||||
>
|
||||
{fwSelectedCount === fwPromptCount ? "Clear" : "All"}
|
||||
{fwSelectedCount === fwPromptCount
|
||||
? t("common.clear")
|
||||
: t("playground.complianceUi.allButton")}
|
||||
</button>
|
||||
</button>
|
||||
|
||||
|
|
@ -1107,7 +1129,9 @@ export default function ComplianceUI({
|
|||
onClick={() => toggleCategoryPrompts(category)}
|
||||
className="text-[10px] font-medium text-blue-600 hover:text-blue-700 flex-shrink-0 whitespace-nowrap"
|
||||
>
|
||||
{allCatSelected ? "Clear" : "Select all"}
|
||||
{allCatSelected
|
||||
? t("common.clear")
|
||||
: t("playground.complianceUi.selectAllCategory")}
|
||||
</button>
|
||||
</div>
|
||||
{category.prompts.map((prompt) => (
|
||||
|
|
@ -1126,7 +1150,9 @@ export default function ComplianceUI({
|
|||
<span
|
||||
className={`inline-block mt-0.5 text-[9px] font-semibold px-1 py-0.5 rounded ${prompt.expectedResult === "fail" ? "bg-red-50 text-red-600" : "bg-green-50 text-green-600"}`}
|
||||
>
|
||||
{prompt.expectedResult === "fail" ? "Should Fail" : "Should Pass"}
|
||||
{prompt.expectedResult === "fail"
|
||||
? t("playground.complianceUi.shouldFail")
|
||||
: t("playground.complianceUi.shouldPass")}
|
||||
</span>
|
||||
</div>
|
||||
{isCustom && (
|
||||
|
|
@ -1138,7 +1164,7 @@ export default function ComplianceUI({
|
|||
deleteCustomPrompt(prompt.id);
|
||||
}}
|
||||
className="opacity-0 group-hover:opacity-100 p-0.5 text-gray-400 hover:text-red-500 transition-all flex-shrink-0"
|
||||
aria-label="Delete"
|
||||
aria-label={t("common.delete")}
|
||||
>
|
||||
<Trash2 className="w-3 h-3" />
|
||||
</button>
|
||||
|
|
@ -1168,7 +1194,7 @@ export default function ComplianceUI({
|
|||
onClick={() => setRightTab("quick-test")}
|
||||
className={`relative flex items-center gap-1.5 px-3 py-2.5 text-xs font-medium transition-colors ${rightTab === "quick-test" ? "text-blue-600" : "text-gray-500 hover:text-gray-700"}`}
|
||||
>
|
||||
<MessageSquare className="w-3.5 h-3.5" /> Quick Test
|
||||
<MessageSquare className="w-3.5 h-3.5" /> {t("playground.complianceUi.quickTestTab")}
|
||||
{rightTab === "quick-test" && (
|
||||
<span className="absolute bottom-0 left-0 right-0 h-0.5 bg-blue-600 rounded-t" />
|
||||
)}
|
||||
|
|
@ -1178,7 +1204,7 @@ export default function ComplianceUI({
|
|||
onClick={() => setRightTab("batch-results")}
|
||||
className={`relative flex items-center gap-1.5 px-3 py-2.5 text-xs font-medium transition-colors ${rightTab === "batch-results" ? "text-blue-600" : "text-gray-500 hover:text-gray-700"}`}
|
||||
>
|
||||
<ListChecks className="w-3.5 h-3.5" /> Batch Results
|
||||
<ListChecks className="w-3.5 h-3.5" /> {t("playground.complianceUi.batchResultsTab")}
|
||||
{testResults.length > 0 && (
|
||||
<span className="text-[10px] bg-gray-100 text-gray-600 px-1.5 py-0.5 rounded-full">
|
||||
{testResults.length}
|
||||
|
|
@ -1196,7 +1222,9 @@ export default function ComplianceUI({
|
|||
<div className="px-5 pt-4 pb-2 flex-shrink-0">
|
||||
{hasAnyConfig ? (
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="text-[11px] font-medium text-gray-500">Testing against:</span>
|
||||
<span className="text-[11px] font-medium text-gray-500">
|
||||
{t("playground.complianceUi.testingAgainst")}
|
||||
</span>
|
||||
{selectedPolicies.map((id) => (
|
||||
<span key={id} className="text-[11px] bg-blue-50 text-blue-700 px-2 py-0.5 rounded font-medium">
|
||||
{policyValueToLabel.get(id) ?? id}
|
||||
|
|
@ -1215,9 +1243,7 @@ export default function ComplianceUI({
|
|||
})}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-[11px] text-gray-400">
|
||||
No policies or guardrails selected — select above to test against specific rules.
|
||||
</p>
|
||||
<p className="text-[11px] text-gray-400">{t("playground.complianceUi.noConfigSelected")}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
|
@ -1228,7 +1254,7 @@ export default function ComplianceUI({
|
|||
<div className="w-10 h-10 bg-gray-100 rounded-xl flex items-center justify-center mx-auto mb-3">
|
||||
<MessageSquare className="w-5 h-5 text-gray-400" />
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">Type a prompt below to quickly test it.</p>
|
||||
<p className="text-xs text-gray-500">{t("playground.complianceUi.quickTestEmptyHint")}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -1247,14 +1273,16 @@ export default function ComplianceUI({
|
|||
) : (
|
||||
<CheckCircle2 className="w-3 h-3 inline" />
|
||||
)}
|
||||
{msg.result === "blocked" ? "Blocked" : "Allowed"}
|
||||
{msg.result === "blocked"
|
||||
? t("playground.complianceUi.blockedLabel")
|
||||
: t("playground.complianceUi.allowedLabel")}
|
||||
<span className="font-normal mx-0.5">—</span>
|
||||
</span>
|
||||
)}
|
||||
{msg.text}
|
||||
{msg.type === "system" && msg.returnedText != null && (
|
||||
<span className="block mt-1.5 pt-1.5 border-t border-gray-200/60">
|
||||
<span className="text-gray-500">Returned: </span>
|
||||
<span className="text-gray-500">{t("playground.complianceUi.returnedLabel")} </span>
|
||||
<span className="font-medium text-gray-700 break-all">{msg.returnedText}</span>
|
||||
</span>
|
||||
)}
|
||||
|
|
@ -1279,16 +1307,17 @@ export default function ComplianceUI({
|
|||
value={quickTestInput}
|
||||
onChange={(e) => setQuickTestInput(e.target.value)}
|
||||
onKeyDown={handleQuickTestKeyDown}
|
||||
placeholder="Enter text to test..."
|
||||
placeholder={t("playground.complianceUi.enterTextPlaceholder")}
|
||||
rows={3}
|
||||
className="w-full px-3 pt-3 pb-1 text-sm text-gray-700 placeholder:text-gray-400 focus:outline-none resize-none"
|
||||
/>
|
||||
<div className="flex items-center justify-between px-3 pb-2">
|
||||
<span className="text-[10px] text-gray-400">
|
||||
Press <kbd className="px-1 py-0.5 bg-gray-100 rounded text-[10px] font-mono">Enter</kbd> to
|
||||
submit ·{" "}
|
||||
<kbd className="px-1 py-0.5 bg-gray-100 rounded text-[10px] font-mono">Shift+Enter</kbd> for new
|
||||
line
|
||||
{t("playground.complianceUi.keyboardHintEnter")}{" "}
|
||||
<kbd className="px-1 py-0.5 bg-gray-100 rounded text-[10px] font-mono">Enter</kbd>{" "}
|
||||
{t("playground.complianceUi.keyboardHintSubmit")} ·{" "}
|
||||
<kbd className="px-1 py-0.5 bg-gray-100 rounded text-[10px] font-mono">Shift+Enter</kbd>{" "}
|
||||
{t("playground.complianceUi.keyboardHintNewLine")}
|
||||
</span>
|
||||
<span className="text-[10px] text-gray-400 tabular-nums">{quickTestInput.length}</span>
|
||||
</div>
|
||||
|
|
@ -1310,7 +1339,7 @@ export default function ComplianceUI({
|
|||
<div className="flex-1 flex flex-col overflow-hidden bg-white min-h-0">
|
||||
<div className="px-5 py-3 border-b border-gray-200 flex-shrink-0">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h2 className="text-sm font-semibold text-gray-900">Results</h2>
|
||||
<h2 className="text-sm font-semibold text-gray-900">{t("playground.complianceUi.resultsTitle")}</h2>
|
||||
{testResults.length > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
|
|
@ -1319,7 +1348,7 @@ export default function ComplianceUI({
|
|||
disabled={filteredResults.length === 0}
|
||||
className="flex items-center gap-1 text-[11px] font-medium text-gray-600 hover:text-gray-900 hover:bg-gray-100 px-2 py-1 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent"
|
||||
>
|
||||
<Download className="w-3 h-3" /> Export CSV
|
||||
<Download className="w-3 h-3" /> {t("playground.complianceUi.exportCsv")}
|
||||
</button>
|
||||
<div className="flex items-center gap-2.5 text-[11px]">
|
||||
<span className="flex items-center gap-1 text-green-600">
|
||||
|
|
@ -1328,14 +1357,14 @@ export default function ComplianceUI({
|
|||
</span>
|
||||
<span
|
||||
className="flex items-center gap-1 text-amber-600"
|
||||
title="Allowed content that should have been blocked"
|
||||
title={t("playground.complianceUi.falseNegativeTitle")}
|
||||
>
|
||||
<AlertTriangle className="w-3 h-3" />
|
||||
{falseNegativeCount} FN
|
||||
</span>
|
||||
<span
|
||||
className="flex items-center gap-1 text-red-600"
|
||||
title="Blocked content that should have been allowed"
|
||||
title={t("playground.complianceUi.falsePositiveTitle")}
|
||||
>
|
||||
<X className="w-3 h-3" />
|
||||
{falsePositiveCount} FP
|
||||
|
|
@ -1361,6 +1390,12 @@ export default function ComplianceUI({
|
|||
: filter === "mismatches"
|
||||
? mismatchCount
|
||||
: pendingCount;
|
||||
const filterLabels: Record<ResultFilter, string> = {
|
||||
all: t("common.all"),
|
||||
matches: t("playground.complianceUi.filterMatches"),
|
||||
mismatches: t("playground.complianceUi.filterMismatches"),
|
||||
pending: t("playground.complianceUi.filterPending"),
|
||||
};
|
||||
return (
|
||||
<button
|
||||
key={filter}
|
||||
|
|
@ -1368,7 +1403,7 @@ export default function ComplianceUI({
|
|||
onClick={() => setResultFilter(filter)}
|
||||
className={`text-[11px] font-medium px-2.5 py-1 rounded-md transition-colors capitalize ${resultFilter === filter ? "bg-gray-900 text-white" : "text-gray-500 hover:bg-gray-100"}`}
|
||||
>
|
||||
{filter} ({count})
|
||||
{filterLabels[filter]} ({count})
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
|
@ -1384,7 +1419,7 @@ export default function ComplianceUI({
|
|||
<FlaskConical className="w-6 h-6 text-gray-400" />
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 max-w-[240px]">
|
||||
Select prompts and click Simulate to run batch compliance tests.
|
||||
{t("playground.complianceUi.batchEmptyHint")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1395,22 +1430,22 @@ export default function ComplianceUI({
|
|||
<div className="flex items-center gap-3 text-sm flex-1">
|
||||
<span>
|
||||
<span className="font-semibold text-gray-700">{testResults.length}</span>{" "}
|
||||
<span className="text-gray-500">total</span>
|
||||
<span className="text-gray-500">{t("common.total")}</span>
|
||||
</span>
|
||||
<div className="w-px h-4 bg-gray-200" />
|
||||
<span>
|
||||
<span className="font-semibold text-green-700">{matchCount}</span>{" "}
|
||||
<span className="text-gray-500">correct</span>
|
||||
<span className="text-gray-500">{t("playground.complianceUi.statCorrect")}</span>
|
||||
</span>
|
||||
<div className="w-px h-4 bg-gray-200" />
|
||||
<span title="Allowed content that should have been blocked">
|
||||
<span title={t("playground.complianceUi.falseNegativeTitle")}>
|
||||
<span className="font-semibold text-amber-700">{falseNegativeCount}</span>{" "}
|
||||
<span className="text-gray-500">false negative</span>
|
||||
<span className="text-gray-500">{t("playground.complianceUi.statFalseNegative")}</span>
|
||||
</span>
|
||||
<div className="w-px h-4 bg-gray-200" />
|
||||
<span title="Blocked content that should have been allowed">
|
||||
<span title={t("playground.complianceUi.falsePositiveTitle")}>
|
||||
<span className="font-semibold text-red-700">{falsePositiveCount}</span>{" "}
|
||||
<span className="text-gray-500">false positive</span>
|
||||
<span className="text-gray-500">{t("playground.complianceUi.statFalsePositive")}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
|
|
@ -1422,7 +1457,9 @@ export default function ComplianceUI({
|
|||
: "bg-red-50 border-red-200 text-red-700"
|
||||
}`}
|
||||
>
|
||||
<span className="text-[10px] font-semibold uppercase tracking-wider opacity-90">Score</span>
|
||||
<span className="text-[10px] font-semibold uppercase tracking-wider opacity-90">
|
||||
{t("playground.complianceUi.scoreLabel")}
|
||||
</span>
|
||||
<span>{Math.round((matchCount / completedResults.length) * 100)}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1456,13 +1493,17 @@ export default function ComplianceUI({
|
|||
<span
|
||||
className={`text-[9px] font-semibold px-1 py-0.5 rounded ${result.expectedResult === "fail" ? "bg-red-50 text-red-600" : "bg-green-50 text-green-600"}`}
|
||||
>
|
||||
{result.expectedResult === "fail" ? "Expect Block" : "Expect Allow"}
|
||||
{result.expectedResult === "fail"
|
||||
? t("playground.complianceUi.expectBlock")
|
||||
: t("playground.complianceUi.expectAllow")}
|
||||
</span>
|
||||
{result.status === "complete" && (
|
||||
<span
|
||||
className={`text-[9px] font-bold px-1 py-0.5 rounded ${result.isMatch ? "bg-green-100 text-green-700" : "bg-red-100 text-red-700"}`}
|
||||
>
|
||||
{result.isMatch ? "✓ Match" : "✗ Gap"}
|
||||
{result.isMatch
|
||||
? t("playground.complianceUi.matchLabel")
|
||||
: t("playground.complianceUi.gapLabel")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -1479,7 +1520,11 @@ export default function ComplianceUI({
|
|||
});
|
||||
}}
|
||||
className="flex-shrink-0 p-0.5 text-gray-400 hover:text-gray-600"
|
||||
aria-label={isExpanded ? "Collapse" : "Expand"}
|
||||
aria-label={
|
||||
isExpanded
|
||||
? t("playground.complianceUi.collapseAriaLabel")
|
||||
: t("playground.complianceUi.expandAriaLabel")
|
||||
}
|
||||
>
|
||||
{isExpanded ? (
|
||||
<ChevronDown className="w-3.5 h-3.5" />
|
||||
|
|
@ -1493,25 +1538,27 @@ export default function ComplianceUI({
|
|||
<div className="mt-2 pt-2 border-t border-gray-100 text-[11px] space-y-1">
|
||||
{result.triggeredBy && (
|
||||
<div>
|
||||
<span className="text-gray-400">Triggered by:</span>{" "}
|
||||
<span className="text-gray-400">{t("playground.complianceUi.triggeredBy")}</span>{" "}
|
||||
<span className="font-medium text-gray-700 bg-gray-100 px-1.5 py-0.5 rounded">
|
||||
{result.triggeredBy}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<span className="text-gray-400">Verdict:</span>{" "}
|
||||
<span className="text-gray-400">{t("playground.complianceUi.verdictLabel")}</span>{" "}
|
||||
<span className={result.isMatch ? "text-green-600" : "text-red-600"}>
|
||||
{result.isMatch
|
||||
? "Correctly handled"
|
||||
? t("playground.complianceUi.verdictCorrect")
|
||||
: result.expectedResult === "fail"
|
||||
? "Gap — should have been blocked"
|
||||
: "False positive — incorrectly blocked"}
|
||||
? t("playground.complianceUi.verdictGap")
|
||||
: t("playground.complianceUi.verdictFalsePositive")}
|
||||
</span>
|
||||
</div>
|
||||
{result.returnedText != null && result.returnedText !== "" && (
|
||||
<div className="mt-1.5">
|
||||
<span className="text-gray-400 block mb-0.5">LLM response:</span>
|
||||
<span className="text-gray-400 block mb-0.5">
|
||||
{t("playground.complianceUi.llmResponse")}
|
||||
</span>
|
||||
<div className="text-gray-700 bg-gray-50 rounded px-2 py-1.5 border border-gray-100 max-h-32 overflow-y-auto whitespace-pre-wrap break-words">
|
||||
{result.returnedText}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4105,6 +4105,95 @@
|
|||
"selectModel": "Select a model",
|
||||
"addCustomModel": "+ Add custom model",
|
||||
"customModelPlaceholder": "Custom Model Name (Enter to add)"
|
||||
},
|
||||
"complianceUi": {
|
||||
"testConfiguration": "Test Configuration",
|
||||
"testConfigurationDesc": "Select policies, guardrails, or both to test against.",
|
||||
"policiesLabel": "Policies",
|
||||
"guardrailsLabel": "Guardrails",
|
||||
"orSeparator": "or",
|
||||
"guardrailsSelected": "{{count}} selected",
|
||||
"guardrailsNoneSelected": "None selected",
|
||||
"noGuardrailsAvailable": "No guardrails available. Create guardrails in the Guardrails page.",
|
||||
"stopButton": "Stop",
|
||||
"simulateButton": "Simulate ({{count}})",
|
||||
"runningStatus": "Running...",
|
||||
"testPrompts": "Test Prompts",
|
||||
"searchPromptsPlaceholder": "Search prompts...",
|
||||
"selectAll": "Select All",
|
||||
"csvButton": "CSV",
|
||||
"enterPromptPlaceholder": "Enter your test prompt...",
|
||||
"shouldFail": "Should Fail",
|
||||
"shouldPass": "Should Pass",
|
||||
"uploadCsvDataset": "Upload CSV Dataset",
|
||||
"downloadTemplate": "Download Template",
|
||||
"csvRequiredColumns": "Required columns:",
|
||||
"csvOptionalColumns": "Optional columns:",
|
||||
"csvFailOrPass": "(fail or pass)",
|
||||
"chooseCsvFile": "Choose CSV file",
|
||||
"csvErrorNotCsv": "Please upload a .csv file.",
|
||||
"csvErrorTooLarge": "File too large (max 5 MB).",
|
||||
"csvErrorEmpty": "CSV file is empty.",
|
||||
"csvErrorMissingColumns": "Missing required columns: {{columns}}. Expected: prompt, expected_result. Optional: framework, category.",
|
||||
"csvRowMissingPrompt": "Row {{row}}: missing prompt text",
|
||||
"csvRowInvalidExpected": "Row {{row}}: expected_result must be \"fail\" or \"pass\", got \"{{value}}\"",
|
||||
"csvMoreErrors": "...and {{count}} more errors",
|
||||
"csvErrorNoValidPrompts": "No valid prompts found in CSV.",
|
||||
"csvErrorParseFailed": "Failed to parse CSV file.",
|
||||
"csvPromptsDesc": "Prompts uploaded from CSV — {{category}}.",
|
||||
"customPromptsDesc": "Custom prompts — {{name}}.",
|
||||
"customPromptsSessionDesc": "Custom prompts added this session.",
|
||||
"policyCount_one": "{{count}} policy",
|
||||
"policyCount_other": "{{count}} policies",
|
||||
"guardrailCount_one": "{{count}} guardrail",
|
||||
"guardrailCount_other": "{{count}} guardrails",
|
||||
"testButton": "Test",
|
||||
"testButtonWithConfig": "Test {{config}}",
|
||||
"quickTestTab": "Quick Test",
|
||||
"batchResultsTab": "Batch Results",
|
||||
"testingAgainst": "Testing against:",
|
||||
"noConfigSelected": "No policies or guardrails selected — select above to test against specific rules.",
|
||||
"quickTestEmptyHint": "Type a prompt below to quickly test it.",
|
||||
"blockedLabel": "Blocked",
|
||||
"allowedLabel": "Allowed",
|
||||
"returnedLabel": "Returned:",
|
||||
"enterTextPlaceholder": "Enter text to test...",
|
||||
"keyboardHintEnter": "Press",
|
||||
"keyboardHintSubmit": "to submit",
|
||||
"keyboardHintNewLine": "for new line",
|
||||
"quickTestAllowedModel": "Allowed — model response received.",
|
||||
"quickTestBlocked": "Blocked — {{triggeredBy}}",
|
||||
"quickTestAllowedClean": "Allowed — no policy or guardrail violations detected.",
|
||||
"quickTestError": "Error: {{errorMessage}}",
|
||||
"contentFilter": "content filter",
|
||||
"resultsTitle": "Results",
|
||||
"exportCsv": "Export CSV",
|
||||
"falseNegativeTitle": "Allowed content that should have been blocked",
|
||||
"falsePositiveTitle": "Blocked content that should have been allowed",
|
||||
"filterMatches": "Matches",
|
||||
"filterMismatches": "Mismatches",
|
||||
"filterPending": "Pending",
|
||||
"batchEmptyHint": "Select prompts and click Simulate to run batch compliance tests.",
|
||||
"statCorrect": "correct",
|
||||
"statFalseNegative": "false negative",
|
||||
"statFalsePositive": "false positive",
|
||||
"scoreLabel": "Score",
|
||||
"promptCount_one": "{{count}} prompt",
|
||||
"promptCount_other": "{{count}} prompts",
|
||||
"allButton": "All",
|
||||
"selectAllCategory": "Select all",
|
||||
"expectBlock": "Expect Block",
|
||||
"expectAllow": "Expect Allow",
|
||||
"matchLabel": "✓ Match",
|
||||
"gapLabel": "✗ Gap",
|
||||
"collapseAriaLabel": "Collapse",
|
||||
"expandAriaLabel": "Expand",
|
||||
"triggeredBy": "Triggered by:",
|
||||
"verdictLabel": "Verdict:",
|
||||
"verdictCorrect": "Correctly handled",
|
||||
"verdictGap": "Gap — should have been blocked",
|
||||
"verdictFalsePositive": "False positive — incorrectly blocked",
|
||||
"llmResponse": "LLM response:"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4105,6 +4105,95 @@
|
|||
"selectModel": "选择模型",
|
||||
"addCustomModel": "+ 添加自定义模型",
|
||||
"customModelPlaceholder": "自定义模型名称(回车确认)"
|
||||
},
|
||||
"complianceUi": {
|
||||
"testConfiguration": "测试配置",
|
||||
"testConfigurationDesc": "选择策略、护栏或两者以进行测试。",
|
||||
"policiesLabel": "策略",
|
||||
"guardrailsLabel": "护栏",
|
||||
"orSeparator": "或",
|
||||
"guardrailsSelected": "已选 {{count}} 个",
|
||||
"guardrailsNoneSelected": "未选择",
|
||||
"noGuardrailsAvailable": "暂无护栏。请在护栏页面创建护栏。",
|
||||
"stopButton": "停止",
|
||||
"simulateButton": "模拟运行 ({{count}})",
|
||||
"runningStatus": "运行中...",
|
||||
"testPrompts": "测试提示词",
|
||||
"searchPromptsPlaceholder": "搜索提示词...",
|
||||
"selectAll": "全选",
|
||||
"csvButton": "CSV",
|
||||
"enterPromptPlaceholder": "输入测试提示词...",
|
||||
"shouldFail": "应被拦截",
|
||||
"shouldPass": "应通过",
|
||||
"uploadCsvDataset": "上传 CSV 数据集",
|
||||
"downloadTemplate": "下载模板",
|
||||
"csvRequiredColumns": "必填列:",
|
||||
"csvOptionalColumns": "可选列:",
|
||||
"csvFailOrPass": "(fail 或 pass)",
|
||||
"chooseCsvFile": "选择 CSV 文件",
|
||||
"csvErrorNotCsv": "请上传 .csv 文件。",
|
||||
"csvErrorTooLarge": "文件过大(最大 5 MB)。",
|
||||
"csvErrorEmpty": "CSV 文件为空。",
|
||||
"csvErrorMissingColumns": "缺少必填列:{{columns}}。必填:prompt、expected_result。可选:framework、category。",
|
||||
"csvRowMissingPrompt": "第 {{row}} 行:缺少提示词文本",
|
||||
"csvRowInvalidExpected": "第 {{row}} 行:expected_result 必须为 \"fail\" 或 \"pass\",实际值为 \"{{value}}\"",
|
||||
"csvMoreErrors": "...以及 {{count}} 个更多错误",
|
||||
"csvErrorNoValidPrompts": "CSV 中未找到有效的提示词。",
|
||||
"csvErrorParseFailed": "CSV 文件解析失败。",
|
||||
"csvPromptsDesc": "从 CSV 上传的提示词 — {{category}}。",
|
||||
"customPromptsDesc": "自定义提示词 — {{name}}。",
|
||||
"customPromptsSessionDesc": "本次会话添加的自定义提示词。",
|
||||
"policyCount_one": "{{count}} 个策略",
|
||||
"policyCount_other": "{{count}} 个策略",
|
||||
"guardrailCount_one": "{{count}} 个护栏",
|
||||
"guardrailCount_other": "{{count}} 个护栏",
|
||||
"testButton": "测试",
|
||||
"testButtonWithConfig": "测试 {{config}}",
|
||||
"quickTestTab": "快速测试",
|
||||
"batchResultsTab": "批量结果",
|
||||
"testingAgainst": "正在测试:",
|
||||
"noConfigSelected": "未选择策略或护栏 — 请在上方选择以针对特定规则进行测试。",
|
||||
"quickTestEmptyHint": "在下方输入提示词以快速测试。",
|
||||
"blockedLabel": "已拦截",
|
||||
"allowedLabel": "已允许",
|
||||
"returnedLabel": "返回内容:",
|
||||
"enterTextPlaceholder": "输入要测试的文本...",
|
||||
"keyboardHintEnter": "按",
|
||||
"keyboardHintSubmit": "提交",
|
||||
"keyboardHintNewLine": "换行",
|
||||
"quickTestAllowedModel": "已允许 — 收到模型响应。",
|
||||
"quickTestBlocked": "已拦截 — {{triggeredBy}}",
|
||||
"quickTestAllowedClean": "已允许 — 未检测到策略或护栏违规。",
|
||||
"quickTestError": "错误:{{errorMessage}}",
|
||||
"contentFilter": "内容过滤器",
|
||||
"resultsTitle": "结果",
|
||||
"exportCsv": "导出 CSV",
|
||||
"falseNegativeTitle": "应被拦截但被放行的内容",
|
||||
"falsePositiveTitle": "应被放行但被拦截的内容",
|
||||
"filterMatches": "匹配",
|
||||
"filterMismatches": "不匹配",
|
||||
"filterPending": "待处理",
|
||||
"batchEmptyHint": "选择提示词并点击模拟运行以执行批量合规测试。",
|
||||
"statCorrect": "正确",
|
||||
"statFalseNegative": "漏报",
|
||||
"statFalsePositive": "误报",
|
||||
"scoreLabel": "得分",
|
||||
"promptCount_one": "{{count}} 个提示词",
|
||||
"promptCount_other": "{{count}} 个提示词",
|
||||
"allButton": "全部",
|
||||
"selectAllCategory": "全选",
|
||||
"expectBlock": "期望拦截",
|
||||
"expectAllow": "期望放行",
|
||||
"matchLabel": "✓ 匹配",
|
||||
"gapLabel": "✗ 缺口",
|
||||
"collapseAriaLabel": "折叠",
|
||||
"expandAriaLabel": "展开",
|
||||
"triggeredBy": "触发来源:",
|
||||
"verdictLabel": "判定:",
|
||||
"verdictCorrect": "处理正确",
|
||||
"verdictGap": "缺口 — 本应被拦截",
|
||||
"verdictFalsePositive": "误报 — 被错误拦截",
|
||||
"llmResponse": "模型响应:"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue