diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json index ef4f73b26b2..f60e6e5ced4 100644 --- a/ui/litellm-dashboard/eslint-suppressions.json +++ b/ui/litellm-dashboard/eslint-suppressions.json @@ -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 diff --git a/ui/litellm-dashboard/src/components/playground/complianceUI/ComplianceUI.tsx b/ui/litellm-dashboard/src/components/playground/complianceUI/ComplianceUI.tsx index 448ddb5d32b..259c1ea0721 100644 --- a/ui/litellm-dashboard/src/components/playground/complianceUI/ComplianceUI.tsx +++ b/ui/litellm-dashboard/src/components/playground/complianceUI/ComplianceUI.tsx @@ -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>(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 */}
-

Test Configuration

-

Select policies, guardrails, or both to test against.

+

{t("playground.complianceUi.testConfiguration")}

+

{t("playground.complianceUi.testConfigurationDesc")}

{accessToken && (
- or + + {t("playground.complianceUi.orSeparator")} +
@@ -744,7 +755,7 @@ export default function ComplianceUI({
{guardrailOptions.length === 0 ? (
- No guardrails available. Create guardrails in the Guardrails page. + {t("playground.complianceUi.noGuardrailsAvailable")}
) : ( 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")} > @@ -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" > - Stop + {t("playground.complianceUi.stopButton")} ) : ( )} {isRunning && ( - Running... + {t("playground.complianceUi.runningStatus")} )}
@@ -841,7 +853,7 @@ export default function ComplianceUI({
-

Test Prompts

+

{t("playground.complianceUi.testPrompts")}

{selectedPromptIds.size}/{totalPromptCount} @@ -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" />
@@ -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")} ·
@@ -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"}`} > - Add + {t("common.add")}
@@ -906,7 +918,7 @@ export default function ComplianceUI({