mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for team guardrails tab
This commit is contained in:
parent
fbb8477d23
commit
6484d548ec
3 changed files with 340 additions and 129 deletions
|
|
@ -1,6 +1,9 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Trans } from "react-i18next";
|
||||
import type { TFunction } from "i18next";
|
||||
import {
|
||||
SearchIcon,
|
||||
PlusIcon,
|
||||
|
|
@ -109,26 +112,30 @@ function submissionToTeamGuardrail(item: GuardrailSubmissionItem): TeamGuardrail
|
|||
};
|
||||
}
|
||||
|
||||
const STATUS_CONFIG: Record<GuardrailStatus, { label: string; bg: string; text: string; dot: string }> = {
|
||||
active: {
|
||||
label: "Active",
|
||||
bg: "bg-green-50",
|
||||
text: "text-green-700",
|
||||
dot: "bg-green-500",
|
||||
},
|
||||
pending: {
|
||||
label: "Pending Review",
|
||||
bg: "bg-yellow-50",
|
||||
text: "text-yellow-700",
|
||||
dot: "bg-yellow-500",
|
||||
},
|
||||
rejected: {
|
||||
label: "Rejected",
|
||||
bg: "bg-red-50",
|
||||
text: "text-red-700",
|
||||
dot: "bg-red-500",
|
||||
},
|
||||
};
|
||||
type StatusConfig = { label: string; bg: string; text: string; dot: string };
|
||||
|
||||
function getStatusConfig(t: TFunction): Record<GuardrailStatus, StatusConfig> {
|
||||
return {
|
||||
active: {
|
||||
label: t("guardrails.teamGuardrailsTab.statusActive"),
|
||||
bg: "bg-green-50",
|
||||
text: "text-green-700",
|
||||
dot: "bg-green-500",
|
||||
},
|
||||
pending: {
|
||||
label: t("guardrails.teamGuardrailsTab.statusPendingReview"),
|
||||
bg: "bg-yellow-50",
|
||||
text: "text-yellow-700",
|
||||
dot: "bg-yellow-500",
|
||||
},
|
||||
rejected: {
|
||||
label: t("guardrails.teamGuardrailsTab.statusRejected"),
|
||||
bg: "bg-red-50",
|
||||
text: "text-red-700",
|
||||
dot: "bg-red-500",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const TEAM_COLORS: Record<string, string> = {
|
||||
"ML Platform": "bg-purple-100 text-purple-700",
|
||||
|
|
@ -227,7 +234,9 @@ function GuardrailCard({
|
|||
onApprove,
|
||||
onReject,
|
||||
}: GuardrailCardProps) {
|
||||
const status = STATUS_CONFIG[g.status];
|
||||
const { t } = useTranslation();
|
||||
const statusConfig = getStatusConfig(t);
|
||||
const status = statusConfig[g.status];
|
||||
const teamColor = TEAM_COLORS[g.team] ?? "bg-gray-100 text-gray-700";
|
||||
return (
|
||||
<div
|
||||
|
|
@ -238,7 +247,9 @@ function GuardrailCard({
|
|||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1.5 flex-wrap">
|
||||
<span className={`text-xs font-medium px-2 py-0.5 rounded-full ${teamColor}`}>Team: {g.team}</span>
|
||||
<span className={`text-xs font-medium px-2 py-0.5 rounded-full ${teamColor}`}>
|
||||
{t("guardrails.teamGuardrailsTab.teamLabel", { team: g.team })}
|
||||
</span>
|
||||
<span
|
||||
className={`inline-flex items-center gap-1.5 text-xs font-medium px-2 py-0.5 rounded-full ${status.bg} ${status.text}`}
|
||||
>
|
||||
|
|
@ -254,16 +265,20 @@ function GuardrailCard({
|
|||
</div>
|
||||
<div className="flex items-center gap-4 text-xs text-gray-500">
|
||||
<span>
|
||||
Model: <span className="font-medium text-gray-700">{g.model}</span>
|
||||
{t("guardrails.teamGuardrailsTab.modelLabel")}{" "}
|
||||
<span className="font-medium text-gray-700">{g.model}</span>
|
||||
</span>
|
||||
<span>
|
||||
Submitted: <span className="font-medium text-gray-700">{g.submittedAt}</span>
|
||||
{t("guardrails.teamGuardrailsTab.submittedLabel")}{" "}
|
||||
<span className="font-medium text-gray-700">{g.submittedAt}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-end gap-2 flex-shrink-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-gray-500 whitespace-nowrap">Forward API Key</span>
|
||||
<span className="text-xs text-gray-500 whitespace-nowrap">
|
||||
{t("guardrails.teamGuardrailsTab.forwardApiKey")}
|
||||
</span>
|
||||
<Toggle enabled={g.forwardKey} onToggle={onToggleForwardKey} />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
|
|
@ -272,7 +287,7 @@ function GuardrailCard({
|
|||
onClick={onSelect}
|
||||
className="text-xs border border-gray-300 text-gray-600 hover:bg-gray-50 px-3 py-1.5 rounded-md transition-colors font-medium"
|
||||
>
|
||||
{isSelected ? "Close" : "Review"}
|
||||
{isSelected ? t("common.close") : t("guardrails.teamGuardrailsTab.review")}
|
||||
</button>
|
||||
{g.status === "pending" && (
|
||||
<>
|
||||
|
|
@ -281,14 +296,14 @@ function GuardrailCard({
|
|||
onClick={onApprove}
|
||||
className="text-xs bg-green-500 hover:bg-green-600 text-white px-3 py-1.5 rounded-md transition-colors font-medium"
|
||||
>
|
||||
Approve
|
||||
{t("guardrails.teamGuardrailsTab.approve")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onReject}
|
||||
className="text-xs border border-red-300 text-red-600 hover:bg-red-50 px-3 py-1.5 rounded-md transition-colors font-medium"
|
||||
>
|
||||
Reject
|
||||
{t("guardrails.teamGuardrailsTab.reject")}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -302,7 +317,7 @@ function GuardrailCard({
|
|||
className="flex items-center gap-1.5 text-xs text-gray-500 hover:text-gray-700 transition-colors"
|
||||
>
|
||||
{isHeadersExpanded ? <ChevronUpIcon className="h-3.5 w-3.5" /> : <ChevronDownIcon className="h-3.5 w-3.5" />}
|
||||
Static headers
|
||||
{t("guardrails.teamGuardrailsTab.staticHeaders")}
|
||||
{g.customHeaders.length > 0 && (
|
||||
<span className="ml-1 bg-gray-100 text-gray-600 rounded-full px-1.5 py-0.5 text-xs">
|
||||
{g.customHeaders.length}
|
||||
|
|
@ -312,7 +327,7 @@ function GuardrailCard({
|
|||
{isHeadersExpanded && (
|
||||
<div className="mt-2">
|
||||
{g.customHeaders.length === 0 ? (
|
||||
<p className="text-xs text-gray-400 italic">No static headers configured.</p>
|
||||
<p className="text-xs text-gray-400 italic">{t("guardrails.teamGuardrailsTab.noStaticHeaders")}</p>
|
||||
) : (
|
||||
<div className="space-y-1">
|
||||
{g.customHeaders.map((h, i) => (
|
||||
|
|
@ -361,11 +376,13 @@ function DetailPanel({
|
|||
onUpdateCustomHeaders,
|
||||
onUpdateExtraHeaders,
|
||||
}: DetailPanelProps) {
|
||||
const { t } = useTranslation();
|
||||
const [configExpanded, setConfigExpanded] = useState(false);
|
||||
const [newExtraHeader, setNewExtraHeader] = useState("");
|
||||
const [newStaticHeaderKey, setNewStaticHeaderKey] = useState("");
|
||||
const [newStaticHeaderValue, setNewStaticHeaderValue] = useState("");
|
||||
const status = STATUS_CONFIG[g.status];
|
||||
const statusConfig = getStatusConfig(t);
|
||||
const status = statusConfig[g.status];
|
||||
const teamColor = TEAM_COLORS[g.team] ?? "bg-gray-100 text-gray-700";
|
||||
return (
|
||||
<div className="w-96 flex-shrink-0 bg-white overflow-auto">
|
||||
|
|
@ -373,7 +390,9 @@ function DetailPanel({
|
|||
<div className="flex items-start justify-between mb-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className={`text-xs font-medium px-2 py-0.5 rounded-full ${teamColor}`}>Team: {g.team}</span>
|
||||
<span className={`text-xs font-medium px-2 py-0.5 rounded-full ${teamColor}`}>
|
||||
{t("guardrails.teamGuardrailsTab.teamLabel", { team: g.team })}
|
||||
</span>
|
||||
<span
|
||||
className={`inline-flex items-center gap-1.5 text-xs font-medium px-2 py-0.5 rounded-full ${status.bg} ${status.text}`}
|
||||
>
|
||||
|
|
@ -383,21 +402,21 @@ function DetailPanel({
|
|||
</div>
|
||||
<h2 className="text-base font-semibold text-gray-900">{g.name}</h2>
|
||||
<p className="text-xs text-gray-500 mt-0.5">
|
||||
Submitted by {g.submittedBy} on {g.submittedAt}
|
||||
{t("guardrails.teamGuardrailsTab.submittedBy", { by: g.submittedBy, at: g.submittedAt })}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="text-gray-400 hover:text-gray-600 transition-colors"
|
||||
aria-label="Close detail panel"
|
||||
aria-label={t("guardrails.teamGuardrailsTab.closeDetailPanel")}
|
||||
>
|
||||
<XIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 mb-5">{g.description}</p>
|
||||
<div className="space-y-4">
|
||||
<ConfigRow label="Endpoint">
|
||||
<ConfigRow label={t("guardrails.teamGuardrailsTab.endpoint")}>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<code className="text-xs font-mono text-gray-700 break-all">{g.endpoint}</code>
|
||||
<a
|
||||
|
|
@ -410,7 +429,7 @@ function DetailPanel({
|
|||
</a>
|
||||
</div>
|
||||
</ConfigRow>
|
||||
<ConfigRow label="Method">
|
||||
<ConfigRow label={t("guardrails.teamGuardrailsTab.method")}>
|
||||
<span className="text-xs font-mono font-medium text-gray-700 bg-gray-100 px-2 py-0.5 rounded">
|
||||
{g.method}
|
||||
</span>
|
||||
|
|
@ -419,29 +438,33 @@ function DetailPanel({
|
|||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<KeyIcon className="h-3.5 w-3.5 text-blue-500" />
|
||||
<span className="text-xs font-semibold text-blue-800">Forward LiteLLM API Key</span>
|
||||
<span className="text-xs font-semibold text-blue-800">
|
||||
{t("guardrails.teamGuardrailsTab.forwardLiteLLMApiKey")}
|
||||
</span>
|
||||
</div>
|
||||
<Toggle enabled={g.forwardKey} onToggle={onToggleForwardKey} />
|
||||
</div>
|
||||
<p className="text-xs text-blue-700 leading-relaxed">
|
||||
When enabled, the caller's LiteLLM API key is forwarded as an{" "}
|
||||
<code className="font-mono bg-blue-100 px-1 rounded">Authorization</code> header to your guardrail
|
||||
endpoint. This allows your guardrail to authenticate model calls using the original caller's
|
||||
credentials.
|
||||
<Trans
|
||||
i18nKey="guardrails.teamGuardrailsTab.forwardKeyDesc"
|
||||
components={{ code: <code className="font-mono bg-blue-100 px-1 rounded" /> }}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-1.5 mb-2">
|
||||
<span className="text-xs font-semibold text-gray-700">Static headers</span>
|
||||
<span className="text-xs font-semibold text-gray-700">
|
||||
{t("guardrails.teamGuardrailsTab.staticHeaders")}
|
||||
</span>
|
||||
{g.customHeaders.length > 0 && (
|
||||
<span className="bg-gray-100 text-gray-600 rounded-full px-1.5 py-0.5 text-xs">
|
||||
{g.customHeaders.length}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-gray-400 mb-2">Sent with every request to the guardrail.</p>
|
||||
<p className="text-xs text-gray-400 mb-2">{t("guardrails.teamGuardrailsTab.staticHeadersDesc")}</p>
|
||||
{g.customHeaders.length === 0 ? (
|
||||
<p className="text-xs text-gray-400 italic mb-2">No static headers configured.</p>
|
||||
<p className="text-xs text-gray-400 italic mb-2">{t("guardrails.teamGuardrailsTab.noStaticHeaders")}</p>
|
||||
) : (
|
||||
<ul className="list-none space-y-1 mb-2">
|
||||
{g.customHeaders.map((h, i) => (
|
||||
|
|
@ -456,7 +479,7 @@ function DetailPanel({
|
|||
type="button"
|
||||
onClick={() => onUpdateCustomHeaders(g.customHeaders.filter((_, idx) => idx !== i))}
|
||||
className="text-gray-400 hover:text-red-600 flex-shrink-0"
|
||||
aria-label={`Remove ${h.key}`}
|
||||
aria-label={t("guardrails.teamGuardrailsTab.removeHeader", { name: h.key })}
|
||||
>
|
||||
<XIcon className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
|
|
@ -469,7 +492,7 @@ function DetailPanel({
|
|||
type="text"
|
||||
value={newStaticHeaderKey}
|
||||
onChange={(e) => setNewStaticHeaderKey(e.target.value)}
|
||||
placeholder="Header name (e.g. X-API-Key)"
|
||||
placeholder={t("guardrails.teamGuardrailsTab.headerNamePlaceholder")}
|
||||
className="flex-1 min-w-0 text-xs font-mono border border-gray-200 rounded px-2 py-1.5 text-gray-700 placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
|
|
@ -488,7 +511,7 @@ function DetailPanel({
|
|||
type="text"
|
||||
value={newStaticHeaderValue}
|
||||
onChange={(e) => setNewStaticHeaderValue(e.target.value)}
|
||||
placeholder="Value"
|
||||
placeholder={t("guardrails.teamGuardrailsTab.headerValuePlaceholder")}
|
||||
className="flex-1 min-w-0 text-xs font-mono border border-gray-200 rounded px-2 py-1.5 text-gray-700 placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
|
|
@ -516,24 +539,26 @@ function DetailPanel({
|
|||
}}
|
||||
className="text-xs font-medium text-blue-600 hover:text-blue-700 border border-blue-200 bg-blue-50 hover:bg-blue-100 px-2 py-1.5 rounded transition-colors flex-shrink-0"
|
||||
>
|
||||
Add
|
||||
{t("common.add")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-1.5 mb-2">
|
||||
<span className="text-xs font-semibold text-gray-700">Forward client headers</span>
|
||||
<span className="text-xs font-semibold text-gray-700">
|
||||
{t("guardrails.teamGuardrailsTab.forwardClientHeaders")}
|
||||
</span>
|
||||
{g.extraHeaders.length > 0 && (
|
||||
<span className="bg-gray-100 text-gray-600 rounded-full px-1.5 py-0.5 text-xs">
|
||||
{g.extraHeaders.length}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-gray-400 mb-2">
|
||||
Allowed header names to forward from the client request to the guardrail (e.g. x-request-id).
|
||||
</p>
|
||||
<p className="text-xs text-gray-400 mb-2">{t("guardrails.teamGuardrailsTab.forwardClientHeadersDesc")}</p>
|
||||
{g.extraHeaders.length === 0 ? (
|
||||
<p className="text-xs text-gray-400 italic mb-2">No forward client headers configured.</p>
|
||||
<p className="text-xs text-gray-400 italic mb-2">
|
||||
{t("guardrails.teamGuardrailsTab.noForwardClientHeaders")}
|
||||
</p>
|
||||
) : (
|
||||
<ul className="list-none space-y-1 mb-2">
|
||||
{g.extraHeaders.map((name, i) => (
|
||||
|
|
@ -546,7 +571,7 @@ function DetailPanel({
|
|||
type="button"
|
||||
onClick={() => onUpdateExtraHeaders(g.extraHeaders.filter((_, idx) => idx !== i))}
|
||||
className="text-gray-400 hover:text-red-600 flex-shrink-0"
|
||||
aria-label={`Remove ${name}`}
|
||||
aria-label={t("guardrails.teamGuardrailsTab.removeHeader", { name })}
|
||||
>
|
||||
<XIcon className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
|
|
@ -559,7 +584,7 @@ function DetailPanel({
|
|||
type="text"
|
||||
value={newExtraHeader}
|
||||
onChange={(e) => setNewExtraHeader(e.target.value)}
|
||||
placeholder="e.g. x-request-id"
|
||||
placeholder={t("guardrails.teamGuardrailsTab.extraHeaderPlaceholder")}
|
||||
className="flex-1 min-w-0 text-xs font-mono border border-gray-200 rounded px-2 py-1.5 text-gray-700 placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
|
|
@ -583,7 +608,7 @@ function DetailPanel({
|
|||
}}
|
||||
className="text-xs font-medium text-blue-600 hover:text-blue-700 border border-blue-200 bg-blue-50 hover:bg-blue-100 px-2 py-1.5 rounded transition-colors"
|
||||
>
|
||||
Add
|
||||
{t("common.add")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -593,7 +618,7 @@ function DetailPanel({
|
|||
onClick={() => setConfigExpanded(!configExpanded)}
|
||||
className="w-full flex items-center justify-between px-3 py-2 text-left text-xs font-semibold text-gray-700 bg-gray-50 hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
<span>Equivalent config</span>
|
||||
<span>{t("guardrails.teamGuardrailsTab.equivalentConfig")}</span>
|
||||
{configExpanded ? (
|
||||
<ChevronUpIcon className="h-3.5 w-3.5 text-gray-500" />
|
||||
) : (
|
||||
|
|
@ -609,17 +634,19 @@ function DetailPanel({
|
|||
<div className="flex items-start gap-2 bg-gray-50 border border-gray-200 rounded-lg p-3">
|
||||
<InfoIcon className="h-3.5 w-3.5 text-gray-400 flex-shrink-0 mt-0.5" />
|
||||
<p className="text-xs text-gray-500 leading-relaxed">
|
||||
This guardrail runs on a separate instance. It receives the user request and forwards the result to the
|
||||
next step in the pipeline. See{" "}
|
||||
<a
|
||||
href="https://docs.litellm.ai/docs/adding_provider/generic_guardrail_api"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-500 hover:underline"
|
||||
>
|
||||
LiteLLM Generic Guardrail API docs
|
||||
</a>{" "}
|
||||
for configuration details.
|
||||
<Trans
|
||||
i18nKey="guardrails.teamGuardrailsTab.guardrailInfoNote"
|
||||
components={{
|
||||
docsLink: (
|
||||
<a
|
||||
href="https://docs.litellm.ai/docs/adding_provider/generic_guardrail_api"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-500 hover:underline"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -629,7 +656,7 @@ function DetailPanel({
|
|||
className="w-full flex items-center justify-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 text-sm font-medium py-2 rounded-md transition-colors"
|
||||
>
|
||||
<ExternalLinkIcon className="h-4 w-4" />
|
||||
Test Endpoint
|
||||
{t("guardrails.teamGuardrailsTab.testEndpoint")}
|
||||
</button>
|
||||
{g.status === "pending" && (
|
||||
<div className="flex gap-2">
|
||||
|
|
@ -639,7 +666,7 @@ function DetailPanel({
|
|||
className="flex-1 flex items-center justify-center gap-1.5 bg-green-500 hover:bg-green-600 text-white text-sm font-medium py-2 rounded-md transition-colors"
|
||||
>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
Approve
|
||||
{t("guardrails.teamGuardrailsTab.approve")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -647,7 +674,7 @@ function DetailPanel({
|
|||
className="flex-1 flex items-center justify-center gap-1.5 border border-red-300 text-red-600 hover:bg-red-50 text-sm font-medium py-2 rounded-md transition-colors"
|
||||
>
|
||||
<XIcon className="h-4 w-4" />
|
||||
Reject
|
||||
{t("guardrails.teamGuardrailsTab.reject")}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -665,6 +692,7 @@ type ConfirmDialogProps = {
|
|||
};
|
||||
|
||||
function ConfirmDialog({ action, guardrailName, onConfirm, onCancel }: ConfirmDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
const isApprove = action === "approve";
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/30 flex items-center justify-center z-50">
|
||||
|
|
@ -681,14 +709,14 @@ function ConfirmDialog({ action, guardrailName, onConfirm, onCancel }: ConfirmDi
|
|||
)}
|
||||
</div>
|
||||
<h3 className="text-base font-semibold text-gray-900 mb-1">
|
||||
{isApprove ? "Approve Guardrail" : "Reject Guardrail"}
|
||||
{isApprove
|
||||
? t("guardrails.teamGuardrailsTab.approveGuardrailTitle")
|
||||
: t("guardrails.teamGuardrailsTab.rejectGuardrailTitle")}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-500 mb-5">
|
||||
Are you sure you want to {action}{" "}
|
||||
<span className="font-medium text-gray-700">"{guardrailName}"</span>?{" "}
|
||||
{isApprove
|
||||
? "This will make it active and available for use."
|
||||
: "This will mark it as rejected and notify the team."}
|
||||
? t("guardrails.teamGuardrailsTab.approveGuardrailConfirm", { name: guardrailName })
|
||||
: t("guardrails.teamGuardrailsTab.rejectGuardrailConfirm", { name: guardrailName })}
|
||||
</p>
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
|
|
@ -696,7 +724,7 @@ function ConfirmDialog({ action, guardrailName, onConfirm, onCancel }: ConfirmDi
|
|||
onClick={onCancel}
|
||||
className="flex-1 border border-gray-300 text-gray-700 hover:bg-gray-50 text-sm font-medium py-2 rounded-md transition-colors"
|
||||
>
|
||||
Cancel
|
||||
{t("common.cancel")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -705,7 +733,7 @@ function ConfirmDialog({ action, guardrailName, onConfirm, onCancel }: ConfirmDi
|
|||
isApprove ? "bg-green-500 hover:bg-green-600" : "bg-red-500 hover:bg-red-600"
|
||||
}`}
|
||||
>
|
||||
{isApprove ? "Approve" : "Reject"}
|
||||
{isApprove ? t("guardrails.teamGuardrailsTab.approve") : t("guardrails.teamGuardrailsTab.reject")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -718,6 +746,7 @@ interface TeamGuardrailsTabProps {
|
|||
}
|
||||
|
||||
export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
|
||||
const { t } = useTranslation();
|
||||
const [guardrails, setGuardrails] = useState<TeamGuardrail[]>([]);
|
||||
const [summary, setSummary] = useState({
|
||||
total: 0,
|
||||
|
|
@ -741,8 +770,8 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
|
|||
const registerGuardrail = useRegisterGuardrail();
|
||||
|
||||
useEffect(() => {
|
||||
const t = setTimeout(() => setSearchDebounced(search), 300);
|
||||
return () => clearTimeout(t);
|
||||
const timer = setTimeout(() => setSearchDebounced(search), 300);
|
||||
return () => clearTimeout(timer);
|
||||
}, [search]);
|
||||
|
||||
const fetchSubmissions = useCallback(async () => {
|
||||
|
|
@ -762,12 +791,12 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
|
|||
setGuardrails(res.submissions.map(submissionToTeamGuardrail));
|
||||
setSummary(res.summary);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to load submissions");
|
||||
setError(err instanceof Error ? err.message : t("guardrails.teamGuardrailsTab.failedToLoadSubmissions"));
|
||||
setGuardrails([]);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [accessToken, statusFilter, searchDebounced]);
|
||||
}, [accessToken, statusFilter, searchDebounced, t]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchSubmissions();
|
||||
|
|
@ -790,9 +819,13 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
|
|||
litellm_params: { forward_api_key: newValue },
|
||||
});
|
||||
setGuardrails((prev) => prev.map((x) => (x.id === id ? { ...x, forwardKey: newValue } : x)));
|
||||
NotificationsManager.success(newValue ? "Forward API key enabled" : "Forward API key disabled");
|
||||
NotificationsManager.success(
|
||||
newValue
|
||||
? t("guardrails.teamGuardrailsTab.forwardApiKeyEnabled")
|
||||
: t("guardrails.teamGuardrailsTab.forwardApiKeyDisabled"),
|
||||
);
|
||||
} catch {
|
||||
NotificationsManager.fromBackend("Failed to update forward API key");
|
||||
NotificationsManager.fromBackend(t("guardrails.teamGuardrailsTab.failedToUpdateForwardApiKey"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -816,9 +849,9 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
|
|||
: x,
|
||||
),
|
||||
);
|
||||
NotificationsManager.success("Static headers updated");
|
||||
NotificationsManager.success(t("guardrails.teamGuardrailsTab.staticHeadersUpdated"));
|
||||
} catch {
|
||||
NotificationsManager.fromBackend("Failed to update static headers");
|
||||
NotificationsManager.fromBackend(t("guardrails.teamGuardrailsTab.failedToUpdateStaticHeaders"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -829,9 +862,9 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
|
|||
litellm_params: { extra_headers: extraHeaders },
|
||||
});
|
||||
setGuardrails((prev) => prev.map((x) => (x.id === id ? { ...x, extraHeaders } : x)));
|
||||
NotificationsManager.success("Forward client headers updated");
|
||||
NotificationsManager.success(t("guardrails.teamGuardrailsTab.forwardClientHeadersUpdated"));
|
||||
} catch {
|
||||
NotificationsManager.fromBackend("Failed to update forward client headers");
|
||||
NotificationsManager.fromBackend(t("guardrails.teamGuardrailsTab.failedToUpdateForwardClientHeaders"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -842,9 +875,9 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
|
|||
setConfirmAction(null);
|
||||
if (selectedId === id) setSelectedId(null);
|
||||
await fetchSubmissions();
|
||||
NotificationsManager.success("Guardrail approved");
|
||||
NotificationsManager.success(t("guardrails.teamGuardrailsTab.guardrailApproved"));
|
||||
} catch {
|
||||
NotificationsManager.fromBackend("Failed to approve guardrail");
|
||||
NotificationsManager.fromBackend(t("guardrails.teamGuardrailsTab.failedToApproveGuardrail"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -855,9 +888,9 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
|
|||
setConfirmAction(null);
|
||||
if (selectedId === id) setSelectedId(null);
|
||||
await fetchSubmissions();
|
||||
NotificationsManager.success("Guardrail rejected");
|
||||
NotificationsManager.success(t("guardrails.teamGuardrailsTab.guardrailRejected"));
|
||||
} catch {
|
||||
NotificationsManager.fromBackend("Failed to reject guardrail");
|
||||
NotificationsManager.fromBackend(t("guardrails.teamGuardrailsTab.failedToRejectGuardrail"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -874,17 +907,25 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
|
|||
<div className="flex h-full">
|
||||
<div className={`flex-1 min-w-0 p-6 overflow-auto ${selected ? "border-r border-gray-200" : ""}`}>
|
||||
<div className="grid grid-cols-4 gap-4 mb-6">
|
||||
<StatCard label="Total Submitted" value={totalCount} color="text-gray-900" />
|
||||
<StatCard label="Pending Review" value={pendingCount} color="text-yellow-600" />
|
||||
<StatCard label="Active" value={activeCount} color="text-green-600" />
|
||||
<StatCard label="Rejected" value={rejectedCount} color="text-red-600" />
|
||||
<StatCard label={t("guardrails.teamGuardrailsTab.totalSubmitted")} value={totalCount} color="text-gray-900" />
|
||||
<StatCard
|
||||
label={t("guardrails.teamGuardrailsTab.statusPendingReview")}
|
||||
value={pendingCount}
|
||||
color="text-yellow-600"
|
||||
/>
|
||||
<StatCard label={t("guardrails.teamGuardrailsTab.statusActive")} value={activeCount} color="text-green-600" />
|
||||
<StatCard
|
||||
label={t("guardrails.teamGuardrailsTab.statusRejected")}
|
||||
value={rejectedCount}
|
||||
color="text-red-600"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 mb-5">
|
||||
<div className="relative flex-1 max-w-xs">
|
||||
<SearchIcon className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search guardrails..."
|
||||
placeholder={t("guardrails.teamGuardrailsTab.searchPlaceholder")}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="w-full pl-9 pr-4 py-2 border border-gray-200 rounded-md text-sm text-gray-700 placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
|
||||
|
|
@ -895,10 +936,10 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
|
|||
onChange={(e) => setStatusFilter(e.target.value as typeof statusFilter)}
|
||||
className="border border-gray-200 rounded-md px-3 py-2 text-sm text-gray-700 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 bg-white"
|
||||
>
|
||||
<option value="all">All Status</option>
|
||||
<option value="pending">Pending Review</option>
|
||||
<option value="active">Active</option>
|
||||
<option value="rejected">Rejected</option>
|
||||
<option value="all">{t("guardrails.teamGuardrailsTab.filterAllStatus")}</option>
|
||||
<option value="pending">{t("guardrails.teamGuardrailsTab.statusPendingReview")}</option>
|
||||
<option value="active">{t("guardrails.teamGuardrailsTab.statusActive")}</option>
|
||||
<option value="rejected">{t("guardrails.teamGuardrailsTab.statusRejected")}</option>
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -906,14 +947,20 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
|
|||
className="ml-auto flex items-center gap-2 bg-blue-500 hover:bg-blue-600 text-white text-sm font-medium px-4 py-2 rounded-md transition-colors"
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
Add Guardrail
|
||||
{t("guardrails.teamGuardrailsTab.addGuardrail")}
|
||||
</button>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{isLoading && <div className="text-center py-12 text-gray-500 text-sm">Loading submissions…</div>}
|
||||
{isLoading && (
|
||||
<div className="text-center py-12 text-gray-500 text-sm">
|
||||
{t("guardrails.teamGuardrailsTab.loadingSubmissions")}
|
||||
</div>
|
||||
)}
|
||||
{error && <div className="text-center py-12 text-red-600 text-sm">{error}</div>}
|
||||
{!isLoading && !error && filtered.length === 0 && (
|
||||
<div className="text-center py-12 text-gray-400 text-sm">No guardrails match your filters.</div>
|
||||
<div className="text-center py-12 text-gray-400 text-sm">
|
||||
{t("guardrails.teamGuardrailsTab.noGuardrailsMatch")}
|
||||
</div>
|
||||
)}
|
||||
{!isLoading &&
|
||||
!error &&
|
||||
|
|
@ -955,17 +1002,17 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
|
|||
)}
|
||||
|
||||
<Modal
|
||||
title="Submit Guardrail for Review"
|
||||
title={t("guardrails.teamGuardrailsTab.submitModalTitle")}
|
||||
open={isSubmitModalOpen}
|
||||
onCancel={() => {
|
||||
setIsSubmitModalOpen(false);
|
||||
submitForm.resetFields();
|
||||
}}
|
||||
onOk={() => submitForm.submit()}
|
||||
okText="Submit for Review"
|
||||
okText={t("guardrails.teamGuardrailsTab.submitForReview")}
|
||||
>
|
||||
<div className="rounded-md bg-blue-50 border border-blue-200 px-4 py-3 text-sm text-blue-800 mb-4">
|
||||
Your guardrail will be sent for admin review before it becomes active.
|
||||
{t("guardrails.teamGuardrailsTab.submitCallout")}
|
||||
</div>
|
||||
<Form
|
||||
form={submitForm}
|
||||
|
|
@ -985,7 +1032,7 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
|
|||
litellm_params,
|
||||
guardrail_info: values.guardrail_info ? JSON.parse(values.guardrail_info) : undefined,
|
||||
});
|
||||
NotificationsManager.success("Guardrail submitted for review");
|
||||
NotificationsManager.success(t("guardrails.teamGuardrailsTab.guardrailSubmitted"));
|
||||
setIsSubmitModalOpen(false);
|
||||
submitForm.resetFields();
|
||||
fetchSubmissions();
|
||||
|
|
@ -994,37 +1041,45 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
|
|||
}
|
||||
}}
|
||||
>
|
||||
<Form.Item label="Team" name="team_id" rules={[{ required: true, message: "Select a team" }]}>
|
||||
<Form.Item
|
||||
label={t("guardrails.teamGuardrailsTab.formTeam")}
|
||||
name="team_id"
|
||||
rules={[{ required: true, message: t("guardrails.teamGuardrailsTab.formTeamRequired") }]}
|
||||
>
|
||||
<TeamDropdown />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Guardrail Name"
|
||||
label={t("guardrails.teamGuardrailsTab.formGuardrailName")}
|
||||
name="guardrail_name"
|
||||
rules={[{ required: true, message: "Enter a guardrail name" }]}
|
||||
rules={[{ required: true, message: t("guardrails.teamGuardrailsTab.formGuardrailNameRequired") }]}
|
||||
>
|
||||
<Input placeholder="e.g. pii-detection" />
|
||||
<Input placeholder={t("guardrails.teamGuardrailsTab.formGuardrailNamePlaceholder")} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Mode" name="mode" rules={[{ required: true, message: "Select a mode" }]}>
|
||||
<Form.Item
|
||||
label={t("guardrails.teamGuardrailsTab.formMode")}
|
||||
name="mode"
|
||||
rules={[{ required: true, message: t("guardrails.teamGuardrailsTab.formModeRequired") }]}
|
||||
>
|
||||
<Select>
|
||||
<Select.Option value="pre_call">Pre Call</Select.Option>
|
||||
<Select.Option value="post_call">Post Call</Select.Option>
|
||||
<Select.Option value="during_call">During Call</Select.Option>
|
||||
<Select.Option value="pre_call">{t("guardrails.teamGuardrailsTab.modePreCall")}</Select.Option>
|
||||
<Select.Option value="post_call">{t("guardrails.teamGuardrailsTab.modePostCall")}</Select.Option>
|
||||
<Select.Option value="during_call">{t("guardrails.teamGuardrailsTab.modeDuringCall")}</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="API Base URL"
|
||||
label={t("guardrails.teamGuardrailsTab.formApiBaseUrl")}
|
||||
name="api_base"
|
||||
rules={[
|
||||
{ required: true, message: "Enter the API base URL" },
|
||||
{ type: "url", message: "Must be a valid URL" },
|
||||
{ required: true, message: t("guardrails.teamGuardrailsTab.formApiBaseUrlRequired") },
|
||||
{ type: "url", message: t("guardrails.teamGuardrailsTab.formApiBaseUrlInvalid") },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="https://your-guardrail-api.com/v1/check" className="font-mono" />
|
||||
<Input placeholder={t("guardrails.teamGuardrailsTab.formApiBaseUrlPlaceholder")} className="font-mono" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Additional litellm_params (optional)"
|
||||
label={t("guardrails.teamGuardrailsTab.formExtraParams")}
|
||||
name="extra_litellm_params"
|
||||
tooltip="JSON object merged into litellm_params. e.g. forward_api_key, headers, model, unreachable_fallback"
|
||||
tooltip={t("guardrails.teamGuardrailsTab.formExtraParamsTooltip")}
|
||||
rules={[
|
||||
{
|
||||
validator: (_, value) => {
|
||||
|
|
@ -1032,11 +1087,11 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
|
|||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
if (typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||
return Promise.reject("Must be a JSON object");
|
||||
return Promise.reject(t("guardrails.teamGuardrailsTab.formMustBeJsonObject"));
|
||||
}
|
||||
return Promise.resolve();
|
||||
} catch {
|
||||
return Promise.reject("Invalid JSON");
|
||||
return Promise.reject(t("guardrails.teamGuardrailsTab.formInvalidJson"));
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -1045,11 +1100,11 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
|
|||
<Input.TextArea
|
||||
rows={3}
|
||||
className="font-mono text-xs"
|
||||
placeholder='{"forward_api_key": true, "headers": {"X-Custom": "value"}}'
|
||||
placeholder={t("guardrails.teamGuardrailsTab.formExtraParamsPlaceholder")}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Guardrail Info (optional)"
|
||||
label={t("guardrails.teamGuardrailsTab.formGuardrailInfo")}
|
||||
name="guardrail_info"
|
||||
rules={[
|
||||
{
|
||||
|
|
@ -1059,7 +1114,7 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
|
|||
JSON.parse(value);
|
||||
return Promise.resolve();
|
||||
} catch {
|
||||
return Promise.reject("Invalid JSON");
|
||||
return Promise.reject(t("guardrails.teamGuardrailsTab.formInvalidJson"));
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -1068,7 +1123,7 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
|
|||
<Input.TextArea
|
||||
rows={3}
|
||||
className="font-mono text-xs"
|
||||
placeholder='{"description": "Detects PII in requests"}'
|
||||
placeholder={t("guardrails.teamGuardrailsTab.formGuardrailInfoPlaceholder")}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -1222,7 +1222,85 @@
|
|||
"labelMode": "Mode",
|
||||
"labelDefaultOn": "Default On",
|
||||
"deleteSuccess": "Guardrail \"{{name}}\" deleted successfully",
|
||||
"deleteFailed": "Failed to delete guardrail"
|
||||
"deleteFailed": "Failed to delete guardrail",
|
||||
"teamGuardrailsTab": {
|
||||
"statusActive": "Active",
|
||||
"statusPendingReview": "Pending Review",
|
||||
"statusRejected": "Rejected",
|
||||
"teamLabel": "Team: {{team}}",
|
||||
"modelLabel": "Model:",
|
||||
"submittedLabel": "Submitted:",
|
||||
"forwardApiKey": "Forward API Key",
|
||||
"review": "Review",
|
||||
"approve": "Approve",
|
||||
"reject": "Reject",
|
||||
"staticHeaders": "Static headers",
|
||||
"noStaticHeaders": "No static headers configured.",
|
||||
"submittedBy": "Submitted by {{by}} on {{at}}",
|
||||
"closeDetailPanel": "Close detail panel",
|
||||
"endpoint": "Endpoint",
|
||||
"method": "Method",
|
||||
"forwardLiteLLMApiKey": "Forward LiteLLM API Key",
|
||||
"forwardKeyDesc": "When enabled, the caller's LiteLLM API key is forwarded as an <code>Authorization</code> header to your guardrail endpoint. This allows your guardrail to authenticate model calls using the original caller's credentials.",
|
||||
"staticHeadersDesc": "Sent with every request to the guardrail.",
|
||||
"removeHeader": "Remove {{name}}",
|
||||
"headerNamePlaceholder": "Header name (e.g. X-API-Key)",
|
||||
"headerValuePlaceholder": "Value",
|
||||
"forwardClientHeaders": "Forward client headers",
|
||||
"forwardClientHeadersDesc": "Allowed header names to forward from the client request to the guardrail (e.g. x-request-id).",
|
||||
"noForwardClientHeaders": "No forward client headers configured.",
|
||||
"extraHeaderPlaceholder": "e.g. x-request-id",
|
||||
"equivalentConfig": "Equivalent config",
|
||||
"guardrailInfoNote": "This guardrail runs on a separate instance. It receives the user request and forwards the result to the next step in the pipeline. See <docsLink>LiteLLM Generic Guardrail API docs</docsLink> for configuration details.",
|
||||
"testEndpoint": "Test Endpoint",
|
||||
"approveGuardrailTitle": "Approve Guardrail",
|
||||
"rejectGuardrailTitle": "Reject Guardrail",
|
||||
"approveGuardrailConfirm": "Are you sure you want to approve \"{{name}}\"? This will make it active and available for use.",
|
||||
"rejectGuardrailConfirm": "Are you sure you want to reject \"{{name}}\"? This will mark it as rejected and notify the team.",
|
||||
"totalSubmitted": "Total Submitted",
|
||||
"searchPlaceholder": "Search guardrails...",
|
||||
"filterAllStatus": "All Status",
|
||||
"addGuardrail": "Add Guardrail",
|
||||
"loadingSubmissions": "Loading submissions…",
|
||||
"noGuardrailsMatch": "No guardrails match your filters.",
|
||||
"failedToLoadSubmissions": "Failed to load submissions",
|
||||
"forwardApiKeyEnabled": "Forward API key enabled",
|
||||
"forwardApiKeyDisabled": "Forward API key disabled",
|
||||
"failedToUpdateForwardApiKey": "Failed to update forward API key",
|
||||
"staticHeadersUpdated": "Static headers updated",
|
||||
"failedToUpdateStaticHeaders": "Failed to update static headers",
|
||||
"forwardClientHeadersUpdated": "Forward client headers updated",
|
||||
"failedToUpdateForwardClientHeaders": "Failed to update forward client headers",
|
||||
"guardrailApproved": "Guardrail approved",
|
||||
"failedToApproveGuardrail": "Failed to approve guardrail",
|
||||
"guardrailRejected": "Guardrail rejected",
|
||||
"failedToRejectGuardrail": "Failed to reject guardrail",
|
||||
"submitModalTitle": "Submit Guardrail for Review",
|
||||
"submitForReview": "Submit for Review",
|
||||
"submitCallout": "Your guardrail will be sent for admin review before it becomes active.",
|
||||
"guardrailSubmitted": "Guardrail submitted for review",
|
||||
"formTeam": "Team",
|
||||
"formTeamRequired": "Select a team",
|
||||
"formGuardrailName": "Guardrail Name",
|
||||
"formGuardrailNameRequired": "Enter a guardrail name",
|
||||
"formGuardrailNamePlaceholder": "e.g. pii-detection",
|
||||
"formMode": "Mode",
|
||||
"formModeRequired": "Select a mode",
|
||||
"modePreCall": "Pre Call",
|
||||
"modePostCall": "Post Call",
|
||||
"modeDuringCall": "During Call",
|
||||
"formApiBaseUrl": "API Base URL",
|
||||
"formApiBaseUrlRequired": "Enter the API base URL",
|
||||
"formApiBaseUrlInvalid": "Must be a valid URL",
|
||||
"formApiBaseUrlPlaceholder": "https://your-guardrail-api.com/v1/check",
|
||||
"formExtraParams": "Additional litellm_params (optional)",
|
||||
"formExtraParamsTooltip": "JSON object merged into litellm_params. e.g. forward_api_key, headers, model, unreachable_fallback",
|
||||
"formExtraParamsPlaceholder": "{\"forward_api_key\": true, \"headers\": {\"X-Custom\": \"value\"}}",
|
||||
"formMustBeJsonObject": "Must be a JSON object",
|
||||
"formInvalidJson": "Invalid JSON",
|
||||
"formGuardrailInfo": "Guardrail Info (optional)",
|
||||
"formGuardrailInfoPlaceholder": "{\"description\": \"Detects PII in requests\"}"
|
||||
}
|
||||
},
|
||||
"keyValueInput": {
|
||||
"headerNamePlaceholder": "Header Name",
|
||||
|
|
|
|||
|
|
@ -1208,6 +1208,84 @@
|
|||
"statusNotSet": "未设置"
|
||||
},
|
||||
"guardrails": {
|
||||
"teamGuardrailsTab": {
|
||||
"statusActive": "已激活",
|
||||
"statusPendingReview": "待审核",
|
||||
"statusRejected": "已拒绝",
|
||||
"teamLabel": "团队:{{team}}",
|
||||
"modelLabel": "模型:",
|
||||
"submittedLabel": "提交时间:",
|
||||
"forwardApiKey": "转发 API 密钥",
|
||||
"review": "审阅",
|
||||
"approve": "批准",
|
||||
"reject": "拒绝",
|
||||
"staticHeaders": "静态请求头",
|
||||
"noStaticHeaders": "未配置静态请求头。",
|
||||
"submittedBy": "由 {{by}} 于 {{at}} 提交",
|
||||
"closeDetailPanel": "关闭详情面板",
|
||||
"endpoint": "端点",
|
||||
"method": "方法",
|
||||
"forwardLiteLLMApiKey": "转发 LiteLLM API 密钥",
|
||||
"forwardKeyDesc": "启用后,调用方的 LiteLLM API 密钥将作为 <code>Authorization</code> 请求头转发到您的护栏端点,使护栏能够使用原始调用方凭据验证模型调用。",
|
||||
"staticHeadersDesc": "随每次护栏请求一同发送。",
|
||||
"removeHeader": "移除 {{name}}",
|
||||
"headerNamePlaceholder": "请求头名称(如 X-API-Key)",
|
||||
"headerValuePlaceholder": "值",
|
||||
"forwardClientHeaders": "转发客户端请求头",
|
||||
"forwardClientHeadersDesc": "允许从客户端请求转发到护栏的请求头名称(如 x-request-id)。",
|
||||
"noForwardClientHeaders": "未配置转发客户端请求头。",
|
||||
"extraHeaderPlaceholder": "如 x-request-id",
|
||||
"equivalentConfig": "等效配置",
|
||||
"guardrailInfoNote": "此护栏运行在独立实例上,接收用户请求并将结果转发到流水线下一步。请参阅 <docsLink>LiteLLM Generic Guardrail API 文档</docsLink>了解配置详情。",
|
||||
"testEndpoint": "测试端点",
|
||||
"approveGuardrailTitle": "批准护栏",
|
||||
"rejectGuardrailTitle": "拒绝护栏",
|
||||
"approveGuardrailConfirm": "确定要批准「{{name}}」吗?批准后将使其激活并可供使用。",
|
||||
"rejectGuardrailConfirm": "确定要拒绝「{{name}}」吗?拒绝后将标记为已拒绝并通知团队。",
|
||||
"totalSubmitted": "已提交总数",
|
||||
"searchPlaceholder": "搜索护栏...",
|
||||
"filterAllStatus": "所有状态",
|
||||
"addGuardrail": "添加护栏",
|
||||
"loadingSubmissions": "加载提交记录中…",
|
||||
"noGuardrailsMatch": "没有护栏匹配您的筛选条件。",
|
||||
"failedToLoadSubmissions": "加载提交记录失败",
|
||||
"forwardApiKeyEnabled": "转发 API 密钥已启用",
|
||||
"forwardApiKeyDisabled": "转发 API 密钥已禁用",
|
||||
"failedToUpdateForwardApiKey": "更新转发 API 密钥失败",
|
||||
"staticHeadersUpdated": "静态请求头已更新",
|
||||
"failedToUpdateStaticHeaders": "更新静态请求头失败",
|
||||
"forwardClientHeadersUpdated": "转发客户端请求头已更新",
|
||||
"failedToUpdateForwardClientHeaders": "更新转发客户端请求头失败",
|
||||
"guardrailApproved": "护栏已批准",
|
||||
"failedToApproveGuardrail": "批准护栏失败",
|
||||
"guardrailRejected": "护栏已拒绝",
|
||||
"failedToRejectGuardrail": "拒绝护栏失败",
|
||||
"submitModalTitle": "提交护栏审核",
|
||||
"submitForReview": "提交审核",
|
||||
"submitCallout": "您的护栏将在激活前发送给管理员审核。",
|
||||
"guardrailSubmitted": "护栏已提交审核",
|
||||
"formTeam": "团队",
|
||||
"formTeamRequired": "请选择团队",
|
||||
"formGuardrailName": "护栏名称",
|
||||
"formGuardrailNameRequired": "请输入护栏名称",
|
||||
"formGuardrailNamePlaceholder": "如 pii-detection",
|
||||
"formMode": "模式",
|
||||
"formModeRequired": "请选择模式",
|
||||
"modePreCall": "调用前",
|
||||
"modePostCall": "调用后",
|
||||
"modeDuringCall": "调用中",
|
||||
"formApiBaseUrl": "API 基础 URL",
|
||||
"formApiBaseUrlRequired": "请输入 API 基础 URL",
|
||||
"formApiBaseUrlInvalid": "请输入有效的 URL",
|
||||
"formApiBaseUrlPlaceholder": "https://your-guardrail-api.com/v1/check",
|
||||
"formExtraParams": "附加 litellm_params(可选)",
|
||||
"formExtraParamsTooltip": "合并到 litellm_params 的 JSON 对象,如 forward_api_key、headers、model、unreachable_fallback",
|
||||
"formExtraParamsPlaceholder": "{\"forward_api_key\": true, \"headers\": {\"X-Custom\": \"value\"}}",
|
||||
"formMustBeJsonObject": "必须为 JSON 对象",
|
||||
"formInvalidJson": "JSON 格式无效",
|
||||
"formGuardrailInfo": "护栏信息(可选)",
|
||||
"formGuardrailInfoPlaceholder": "{\"description\": \"Detects PII in requests\"}"
|
||||
},
|
||||
"guardrailGardenTab": "护栏库",
|
||||
"guardrailsTab": "护栏",
|
||||
"testPlaygroundTab": "测试 Playground",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue