feat(ui): i18n for guardrails modals and edit form

This commit is contained in:
吹雪 2026-06-10 18:10:14 +09:00
parent 535e00626d
commit 327f3ad020
8 changed files with 546 additions and 160 deletions

View file

@ -1,5 +1,6 @@
import React from "react";
import { Typography, Select, Modal, Space, Button, Input } from "antd";
import { useTranslation } from "react-i18next";
const { Text } = Typography;
const { Option } = Select;
@ -27,13 +28,15 @@ const KeywordModal: React.FC<KeywordModalProps> = ({
onAdd,
onCancel,
}) => {
const { t } = useTranslation();
return (
<Modal title="Add blocked keyword" open={visible} onCancel={onCancel} footer={null} width={800}>
<Modal title={t("guardrails.keywordModal.title")} open={visible} onCancel={onCancel} footer={null} width={800}>
<Space direction="vertical" style={{ width: "100%" }} size="large">
<div>
<Text strong>Keyword</Text>
<Text strong>{t("guardrails.keywordModal.keywordLabel")}</Text>
<Input
placeholder="Enter sensitive keyword or phrase"
placeholder={t("guardrails.keywordModal.keywordPlaceholder")}
value={keyword}
onChange={(e) => onKeywordChange(e.target.value)}
style={{ marginTop: 8 }}
@ -41,20 +44,20 @@ const KeywordModal: React.FC<KeywordModalProps> = ({
</div>
<div>
<Text strong>Action</Text>
<Text strong>{t("guardrails.keywordModal.actionLabel")}</Text>
<Text type="secondary" style={{ display: "block", marginTop: 4, marginBottom: 8 }}>
Choose what action the guardrail should take when this keyword is detected
{t("guardrails.keywordModal.actionDesc")}
</Text>
<Select value={action} onChange={onActionChange} style={{ width: "100%" }}>
<Option value="BLOCK">Block</Option>
<Option value="MASK">Mask</Option>
<Option value="BLOCK">{t("guardrails.keywordModal.actionBlock")}</Option>
<Option value="MASK">{t("guardrails.keywordModal.actionMask")}</Option>
</Select>
</div>
<div>
<Text strong>Description (optional)</Text>
<Text strong>{t("guardrails.keywordModal.descriptionLabel")}</Text>
<Input.TextArea
placeholder="Explain why this keyword is sensitive"
placeholder={t("guardrails.keywordModal.descriptionPlaceholder")}
value={description}
onChange={(e) => onDescriptionChange(e.target.value)}
rows={3}
@ -64,9 +67,9 @@ const KeywordModal: React.FC<KeywordModalProps> = ({
</Space>
<div style={{ display: "flex", justifyContent: "flex-end", gap: "8px", marginTop: "24px" }}>
<Button onClick={onCancel}>Cancel</Button>
<Button onClick={onCancel}>{t("common.cancel")}</Button>
<Button type="primary" onClick={onAdd}>
Add
{t("common.add")}
</Button>
</div>
</Modal>

View file

@ -1,6 +1,7 @@
import { DeleteOutlined } from "@ant-design/icons";
import { Button, Select, Table, Typography } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
const { Text } = Typography;
const { Option } = Select;
@ -19,14 +20,16 @@ interface KeywordTableProps {
}
const KeywordTable: React.FC<KeywordTableProps> = ({ keywords, onActionChange, onRemove }) => {
const { t } = useTranslation();
const columns = [
{
title: "Keyword",
title: t("guardrails.keywordTable.colKeyword"),
dataIndex: "keyword",
key: "keyword",
},
{
title: "Action",
title: t("guardrails.keywordTable.colAction"),
dataIndex: "action",
key: "action",
width: 150,
@ -37,13 +40,13 @@ const KeywordTable: React.FC<KeywordTableProps> = ({ keywords, onActionChange, o
style={{ width: 120 }}
size="small"
>
<Option value="BLOCK">Block</Option>
<Option value="MASK">Mask</Option>
<Option value="BLOCK">{t("guardrails.keywordTable.actionBlock")}</Option>
<Option value="MASK">{t("guardrails.keywordTable.actionMask")}</Option>
</Select>
),
},
{
title: "Description",
title: t("guardrails.keywordTable.colDescription"),
dataIndex: "description",
key: "description",
render: (desc: string) => desc || "-",
@ -54,14 +57,18 @@ const KeywordTable: React.FC<KeywordTableProps> = ({ keywords, onActionChange, o
width: 100,
render: (_: any, record: BlockedWord) => (
<Button type="text" danger size="small" icon={<DeleteOutlined />} onClick={() => onRemove(record.id)}>
Delete
{t("common.delete")}
</Button>
),
},
];
if (keywords.length === 0) {
return <div style={{ textAlign: "center", padding: "40px 0", color: "#999" }}>No keywords added.</div>;
return (
<div style={{ textAlign: "center", padding: "40px 0", color: "#999" }}>
{t("guardrails.keywordTable.noKeywords")}
</div>
);
}
return <Table dataSource={keywords} columns={columns} rowKey="id" pagination={false} size="small" />;

View file

@ -1,5 +1,6 @@
import React from "react";
import { Typography, Select, Modal, Space, Button } from "antd";
import { useTranslation } from "react-i18next";
const { Text } = Typography;
const { Option } = Select;
@ -34,13 +35,15 @@ const PatternModal: React.FC<PatternModalProps> = ({
onAdd,
onCancel,
}) => {
const { t } = useTranslation();
return (
<Modal title="Add prebuilt pattern" open={visible} onCancel={onCancel} footer={null} width={800}>
<Modal title={t("guardrails.patternModal.title")} open={visible} onCancel={onCancel} footer={null} width={800}>
<Space direction="vertical" style={{ width: "100%" }} size="large">
<div>
<Text strong>Pattern type</Text>
<Text strong>{t("guardrails.patternModal.patternTypeLabel")}</Text>
<Select
placeholder="Choose pattern type"
placeholder={t("guardrails.patternModal.patternTypePlaceholder")}
value={selectedPatternName}
onChange={onPatternNameChange}
style={{ width: "100%", marginTop: 8 }}
@ -74,21 +77,21 @@ const PatternModal: React.FC<PatternModalProps> = ({
</div>
<div>
<Text strong>Action</Text>
<Text strong>{t("guardrails.patternModal.actionLabel")}</Text>
<Text type="secondary" style={{ display: "block", marginTop: 4, marginBottom: 8 }}>
Choose what action the guardrail should take when this pattern is detected
{t("guardrails.patternModal.actionDesc")}
</Text>
<Select value={patternAction} onChange={onActionChange} style={{ width: "100%" }}>
<Option value="BLOCK">Block</Option>
<Option value="MASK">Mask</Option>
<Option value="BLOCK">{t("guardrails.patternModal.actionBlock")}</Option>
<Option value="MASK">{t("guardrails.patternModal.actionMask")}</Option>
</Select>
</div>
</Space>
<div style={{ display: "flex", justifyContent: "flex-end", gap: "8px", marginTop: "24px" }}>
<Button onClick={onCancel}>Cancel</Button>
<Button onClick={onCancel}>{t("common.cancel")}</Button>
<Button type="primary" onClick={onAdd}>
Add
{t("common.add")}
</Button>
</div>
</Modal>

View file

@ -1,6 +1,7 @@
import React from "react";
import { Typography, Select, Table, Tag, Button } from "antd";
import { DeleteOutlined } from "@ant-design/icons";
import { useTranslation } from "react-i18next";
const { Text } = Typography;
const { Option } = Select;
@ -21,24 +22,28 @@ interface PatternTableProps {
}
const PatternTable: React.FC<PatternTableProps> = ({ patterns, onActionChange, onRemove }) => {
const { t } = useTranslation();
const columns = [
{
title: "Type",
title: t("guardrails.patternTable.colType"),
dataIndex: "type",
key: "type",
width: 100,
render: (type: string) => (
<Tag color={type === "prebuilt" ? "blue" : "green"}>{type === "prebuilt" ? "Prebuilt" : "Custom"}</Tag>
<Tag color={type === "prebuilt" ? "blue" : "green"}>
{type === "prebuilt" ? t("guardrails.patternTable.typePrebuilt") : t("guardrails.patternTable.typeCustom")}
</Tag>
),
},
{
title: "Pattern name",
title: t("guardrails.patternTable.colPatternName"),
dataIndex: "name",
key: "name",
render: (_: string, record: Pattern) => record.display_name || record.name,
},
{
title: "Regex pattern",
title: t("guardrails.patternTable.colRegexPattern"),
dataIndex: "pattern",
key: "pattern",
render: (pattern: string) =>
@ -51,7 +56,7 @@ const PatternTable: React.FC<PatternTableProps> = ({ patterns, onActionChange, o
),
},
{
title: "Action",
title: t("guardrails.patternTable.colAction"),
dataIndex: "action",
key: "action",
width: 150,
@ -62,8 +67,8 @@ const PatternTable: React.FC<PatternTableProps> = ({ patterns, onActionChange, o
style={{ width: 120 }}
size="small"
>
<Option value="BLOCK">Block</Option>
<Option value="MASK">Mask</Option>
<Option value="BLOCK">{t("guardrails.patternTable.actionBlock")}</Option>
<Option value="MASK">{t("guardrails.patternTable.actionMask")}</Option>
</Select>
),
},
@ -73,14 +78,18 @@ const PatternTable: React.FC<PatternTableProps> = ({ patterns, onActionChange, o
width: 100,
render: (_: any, record: Pattern) => (
<Button type="text" danger size="small" icon={<DeleteOutlined />} onClick={() => onRemove(record.id)}>
Delete
{t("common.delete")}
</Button>
),
},
];
if (patterns.length === 0) {
return <div style={{ textAlign: "center", padding: "40px 0", color: "#999" }}>No patterns added.</div>;
return (
<div style={{ textAlign: "center", padding: "40px 0", color: "#999" }}>
{t("guardrails.patternTable.noPatterns")}
</div>
);
}
return <Table dataSource={patterns} columns={columns} rowKey="id" pagination={false} size="small" />;

View file

@ -1,4 +1,4 @@
import React, { useState, useRef, useEffect } from "react";
import React, { useState, useRef, useEffect, useMemo } from "react";
import { Modal, Select, Switch, Collapse, Input, Divider } from "antd";
import { Button, TextInput } from "@tremor/react";
import {
@ -11,6 +11,8 @@ import {
UsergroupAddOutlined,
ExportOutlined,
} from "@ant-design/icons";
import { useTranslation } from "react-i18next";
import type { TFunction } from "i18next";
import { createGuardrailCall, updateGuardrailCall, testCustomCodeGuardrail } from "../../networking";
import NotificationsManager from "../../molecules/notifications_manager";
@ -93,47 +95,6 @@ const CODE_TEMPLATES = {
},
};
// Available primitives organized by category
const PRIMITIVES = {
"Return Values": [
{ name: "allow()", desc: "Let request/response through" },
{ name: "block(reason)", desc: "Reject with message" },
{ name: "modify(texts=[], images=[], tool_calls=[])", desc: "Transform content" },
],
"HTTP Requests (async)": [
{ name: "await http_request(url, method, headers, body)", desc: "Make async HTTP request" },
{ name: "await http_get(url, headers)", desc: "Async GET request" },
{ name: "await http_post(url, body, headers)", desc: "Async POST request" },
],
"Regex Functions": [
{ name: "regex_match(text, pattern)", desc: "Returns True if pattern found" },
{ name: "regex_replace(text, pattern, replacement)", desc: "Replace all matches" },
{ name: "regex_find_all(text, pattern)", desc: "Return list of matches" },
],
"JSON Functions": [
{ name: "json_parse(text)", desc: "Parse JSON string, returns None on error" },
{ name: "json_stringify(obj)", desc: "Convert to JSON string" },
{ name: "json_schema_valid(obj, schema)", desc: "Validate against JSON schema" },
],
"URL Functions": [
{ name: "extract_urls(text)", desc: "Extract all URLs from text" },
{ name: "is_valid_url(url)", desc: "Check if URL is valid" },
{ name: "all_urls_valid(text)", desc: "Check all URLs in text are valid" },
],
"Code Detection": [
{ name: "detect_code(text)", desc: "Returns True if code detected" },
{ name: "detect_code_languages(text)", desc: "Returns list of detected languages" },
{ name: 'contains_code_language(text, ["sql"])', desc: "Check for specific languages" },
],
"Text Utilities": [
{ name: "contains(text, substring)", desc: "Check if substring exists" },
{ name: "contains_any(text, [substr1, substr2])", desc: "Check if any substring exists" },
{ name: "word_count(text)", desc: "Count words" },
{ name: "char_count(text)", desc: "Count characters" },
{ name: "lower(text) / upper(text) / trim(text)", desc: "String transforms" },
],
};
const MODE_OPTIONS = [
{ value: "pre_call", label: "pre_call (Request)" },
{ value: "post_call", label: "post_call (Response)" },
@ -144,6 +105,49 @@ const MODE_OPTIONS = [
{ value: "during_mcp_call", label: "during_mcp_call (During MCP Tool Call)" },
];
const getPrimitives = (t: TFunction) => ({
"Return Values": [
{ name: "allow()", desc: t("guardrails.customCodeModal.primAllowDesc") },
{ name: "block(reason)", desc: t("guardrails.customCodeModal.primBlockDesc") },
{ name: "modify(texts=[], images=[], tool_calls=[])", desc: t("guardrails.customCodeModal.primModifyDesc") },
],
"HTTP Requests (async)": [
{
name: "await http_request(url, method, headers, body)",
desc: t("guardrails.customCodeModal.primHttpRequestDesc"),
},
{ name: "await http_get(url, headers)", desc: t("guardrails.customCodeModal.primHttpGetDesc") },
{ name: "await http_post(url, body, headers)", desc: t("guardrails.customCodeModal.primHttpPostDesc") },
],
"Regex Functions": [
{ name: "regex_match(text, pattern)", desc: t("guardrails.customCodeModal.primRegexMatchDesc") },
{ name: "regex_replace(text, pattern, replacement)", desc: t("guardrails.customCodeModal.primRegexReplaceDesc") },
{ name: "regex_find_all(text, pattern)", desc: t("guardrails.customCodeModal.primRegexFindAllDesc") },
],
"JSON Functions": [
{ name: "json_parse(text)", desc: t("guardrails.customCodeModal.primJsonParseDesc") },
{ name: "json_stringify(obj)", desc: t("guardrails.customCodeModal.primJsonStringifyDesc") },
{ name: "json_schema_valid(obj, schema)", desc: t("guardrails.customCodeModal.primJsonSchemaValidDesc") },
],
"URL Functions": [
{ name: "extract_urls(text)", desc: t("guardrails.customCodeModal.primExtractUrlsDesc") },
{ name: "is_valid_url(url)", desc: t("guardrails.customCodeModal.primIsValidUrlDesc") },
{ name: "all_urls_valid(text)", desc: t("guardrails.customCodeModal.primAllUrlsValidDesc") },
],
"Code Detection": [
{ name: "detect_code(text)", desc: t("guardrails.customCodeModal.primDetectCodeDesc") },
{ name: "detect_code_languages(text)", desc: t("guardrails.customCodeModal.primDetectCodeLangsDesc") },
{ name: 'contains_code_language(text, ["sql"])', desc: t("guardrails.customCodeModal.primContainsCodeLangDesc") },
],
"Text Utilities": [
{ name: "contains(text, substring)", desc: t("guardrails.customCodeModal.primContainsDesc") },
{ name: "contains_any(text, [substr1, substr2])", desc: t("guardrails.customCodeModal.primContainsAnyDesc") },
{ name: "word_count(text)", desc: t("guardrails.customCodeModal.primWordCountDesc") },
{ name: "char_count(text)", desc: t("guardrails.customCodeModal.primCharCountDesc") },
{ name: "lower(text) / upper(text) / trim(text)", desc: t("guardrails.customCodeModal.primStringTransformsDesc") },
],
});
// Data for editing an existing guardrail
export interface EditGuardrailData {
guardrail_id: string;
@ -166,7 +170,9 @@ interface CustomCodeModalProps {
}
const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onSuccess, accessToken, editData }) => {
const { t } = useTranslation();
const isEditMode = !!editData;
const primitives = useMemo(() => getPrimitives(t), [t]);
const [guardrailName, setGuardrailName] = useState("");
const [mode, setMode] = useState<string[]>(["pre_call"]);
const [defaultOn, setDefaultOn] = useState(false);
@ -338,15 +344,15 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
// Save guardrail (create or update)
const handleSave = async () => {
if (!guardrailName.trim()) {
NotificationsManager.fromBackend("Please enter a guardrail name");
NotificationsManager.fromBackend(t("guardrails.customCodeModal.noGuardrailName"));
return;
}
if (!code.trim()) {
NotificationsManager.fromBackend("Please enter custom code");
NotificationsManager.fromBackend(t("guardrails.customCodeModal.noCustomCode"));
return;
}
if (!accessToken) {
NotificationsManager.fromBackend("No access token available");
NotificationsManager.fromBackend(t("guardrails.customCodeModal.noAccessToken"));
return;
}
@ -374,7 +380,7 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
}
await updateGuardrailCall(accessToken, editData.guardrail_id, updateData);
NotificationsManager.success("Custom code guardrail updated successfully");
NotificationsManager.success(t("guardrails.customCodeModal.updateSuccess"));
} else {
// Create new guardrail
const guardrailData = {
@ -389,15 +395,17 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
};
await createGuardrailCall(accessToken, guardrailData);
NotificationsManager.success("Custom code guardrail created successfully");
NotificationsManager.success(t("guardrails.customCodeModal.createSuccess"));
}
onSuccess();
onClose();
} catch (error) {
console.error("Failed to save guardrail:", error);
NotificationsManager.fromBackend(
`Failed to ${isEditMode ? "update" : "create"} guardrail: ` +
(error instanceof Error ? error.message : String(error)),
t("guardrails.customCodeModal.saveFailed", {
action: isEditMode ? "update" : "create",
error: error instanceof Error ? error.message : String(error),
}),
);
} finally {
setIsSaving(false);
@ -420,7 +428,7 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
try {
parsedInput = JSON.parse(testInput);
} catch (e) {
setTestResult({ error: "Invalid test input JSON" });
setTestResult({ error: t("guardrails.customCodeModal.invalidTestJson") });
setIsTesting(false);
return;
}
@ -457,7 +465,7 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
error_type: response.error_type,
});
} else {
setTestResult({ error: "Unknown error occurred" });
setTestResult({ error: t("guardrails.customCodeModal.unknownError") });
}
} catch (error) {
console.error("Failed to test custom code:", error);
@ -485,19 +493,27 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
{/* Header */}
<div className="pb-4 border-b border-gray-200">
<h2 className="text-xl font-semibold text-gray-900">
{isEditMode ? "Edit Custom Guardrail" : "Create Custom Guardrail"}
{isEditMode ? t("guardrails.customCodeModal.editTitle") : t("guardrails.customCodeModal.createTitle")}
</h2>
<p className="text-sm text-gray-500 mt-1">Define custom logic using Python-like syntax</p>
<p className="text-sm text-gray-500 mt-1">{t("guardrails.customCodeModal.subtitle")}</p>
</div>
{/* Top Controls */}
<div className="flex items-center gap-4 py-4 border-b border-gray-100">
<div className="flex-1 max-w-[200px]">
<label className="block text-xs font-medium text-gray-600 mb-1">Guardrail Name</label>
<TextInput value={guardrailName} onValueChange={setGuardrailName} placeholder="e.g., block-pii-custom" />
<label className="block text-xs font-medium text-gray-600 mb-1">
{t("guardrails.customCodeModal.guardrailNameLabel")}
</label>
<TextInput
value={guardrailName}
onValueChange={setGuardrailName}
placeholder={t("guardrails.customCodeModal.guardrailNamePlaceholder")}
/>
</div>
<div className="w-[280px]">
<label className="block text-xs font-medium text-gray-600 mb-1">Mode (can select multiple)</label>
<label className="block text-xs font-medium text-gray-600 mb-1">
{t("guardrails.customCodeModal.modeLabel")}
</label>
<Select
mode="multiple"
value={mode}
@ -505,11 +521,13 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
options={MODE_OPTIONS}
className="w-full"
size="middle"
placeholder="Select modes"
placeholder={t("guardrails.customCodeModal.modePlaceholder")}
/>
</div>
<div className="w-[180px]">
<label className="block text-xs font-medium text-gray-600 mb-1">Template</label>
<label className="block text-xs font-medium text-gray-600 mb-1">
{t("guardrails.customCodeModal.templateLabel")}
</label>
<Select
value={selectedTemplate}
onChange={handleTemplateChange}
@ -541,13 +559,13 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
}}
>
<UsergroupAddOutlined />
<span>Browse Community templates</span>
<span>{t("guardrails.customCodeModal.browseCommunityTemplates")}</span>
<ExportOutlined style={{ fontSize: "10px" }} />
</div>
</>
)}
>
<Select.OptGroup label="STANDARD">
<Select.OptGroup label={t("guardrails.customCodeModal.templateGroupStandard")}>
{Object.entries(CODE_TEMPLATES).map(([key, template]) => (
<Select.Option key={key} value={key}>
{template.name}
@ -557,7 +575,7 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
</Select>
</div>
<div className="flex items-center gap-2 pt-5">
<span className="text-sm text-gray-600">Default On</span>
<span className="text-sm text-gray-600">{t("guardrails.customCodeModal.defaultOnLabel")}</span>
<Switch checked={defaultOn} onChange={setDefaultOn} />
</div>
</div>
@ -567,8 +585,10 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
{/* Code Editor */}
<div className="flex-[2] flex flex-col min-w-0 overflow-y-auto">
<div className="flex items-center justify-between mb-2 flex-shrink-0">
<span className="text-xs font-semibold text-gray-500 uppercase tracking-wide">Python Logic</span>
<span className="text-xs text-gray-400">Restricted environment (no imports)</span>
<span className="text-xs font-semibold text-gray-500 uppercase tracking-wide">
{t("guardrails.customCodeModal.pythonLogicLabel")}
</span>
<span className="text-xs text-gray-400">{t("guardrails.customCodeModal.restrictedEnv")}</span>
</div>
<div
className="relative rounded-lg overflow-hidden border border-gray-700 bg-[#1e1e1e] flex-shrink-0"
@ -617,7 +637,7 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
header={
<span className="flex items-center gap-2 text-sm font-medium">
<PlayCircleOutlined className="text-blue-500" />
Test Your Guardrail
{t("guardrails.customCodeModal.testPanelHeader")}
</span>
}
key="test"
@ -625,29 +645,33 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
<div className="space-y-3">
<div>
<div className="flex items-center justify-between mb-2">
<label className="block text-xs font-medium text-gray-600">Test Input (JSON)</label>
<label className="block text-xs font-medium text-gray-600">
{t("guardrails.customCodeModal.testInputLabel")}
</label>
<div className="flex items-center gap-2">
<span className="text-xs text-gray-500">Load example:</span>
<span className="text-xs text-gray-500">
{t("guardrails.customCodeModal.loadExampleLabel")}
</span>
<button
type="button"
onClick={() => setTestInput(JSON.stringify(TEST_INPUT_EXAMPLES.pre_call.data, null, 2))}
className="px-2 py-1 text-xs rounded border border-orange-200 bg-orange-50 text-orange-700 hover:bg-orange-100 transition-colors"
>
Pre-call
{t("guardrails.customCodeModal.examplePreCall")}
</button>
<button
type="button"
onClick={() => setTestInput(JSON.stringify(TEST_INPUT_EXAMPLES.pre_mcp_call.data, null, 2))}
className="px-2 py-1 text-xs rounded border border-purple-200 bg-purple-50 text-purple-700 hover:bg-purple-100 transition-colors"
>
Pre MCP
{t("guardrails.customCodeModal.examplePreMcp")}
</button>
<button
type="button"
onClick={() => setTestInput(JSON.stringify(TEST_INPUT_EXAMPLES.post_call.data, null, 2))}
className="px-2 py-1 text-xs rounded border border-green-200 bg-green-50 text-green-700 hover:bg-green-100 transition-colors"
>
Post-call
{t("guardrails.customCodeModal.examplePostCall")}
</button>
</div>
</div>
@ -686,7 +710,9 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
</div>
<div className="flex items-center gap-3">
<Button size="xs" onClick={handleTest} disabled={isTesting} icon={PlayCircleOutlined}>
{isTesting ? "Running..." : "Run Test"}
{isTesting
? t("guardrails.customCodeModal.runningButton")
: t("guardrails.customCodeModal.runTestButton")}
</Button>
{testResult && (
<div
@ -710,15 +736,16 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
</>
) : testResult.action === "allow" ? (
<>
<CheckCircleOutlined /> Allowed
<CheckCircleOutlined /> {t("guardrails.customCodeModal.testResultAllowed")}
</>
) : testResult.action === "block" ? (
<>
<CloseCircleOutlined /> Blocked: {testResult.reason}
<CloseCircleOutlined />{" "}
{t("guardrails.customCodeModal.testResultBlocked", { reason: testResult.reason })}
</>
) : testResult.action === "modify" ? (
<>
<CheckCircleOutlined /> Modified
<CheckCircleOutlined /> {t("guardrails.customCodeModal.testResultModified")}
{testResult.texts && testResult.texts.length > 0 && (
<span className="text-xs text-gray-500 ml-1">
→ {testResult.texts[0].substring(0, 50)}
@ -728,7 +755,10 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
</>
) : (
<>
<CheckCircleOutlined /> {testResult.action || "Unknown"}
<CheckCircleOutlined />{" "}
{t("guardrails.customCodeModal.testResultUnknown", {
action: testResult.action || t("common.unknown"),
})}
</>
)}
</div>
@ -744,8 +774,10 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
<UsergroupAddOutlined className="text-blue-600 text-lg" />
</div>
<div>
<div className="text-sm font-medium text-gray-900">Built a useful guardrail?</div>
<div className="text-xs text-gray-600">Share it with the community and help others build faster</div>
<div className="text-sm font-medium text-gray-900">
{t("guardrails.customCodeModal.contributeBannerTitle")}
</div>
<div className="text-xs text-gray-600">{t("guardrails.customCodeModal.contributeBannerDesc")}</div>
</div>
</div>
<Button
@ -754,7 +786,7 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
icon={ExportOutlined}
className="bg-blue-600 hover:bg-blue-700 text-white border-0"
>
Contribute Template
{t("guardrails.customCodeModal.contributeButton")}
</Button>
</div>
</div>
@ -763,23 +795,25 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
<div className="w-[300px] flex-shrink-0 overflow-auto border-l border-gray-200 pl-6">
<div className="flex items-center gap-2 mb-3">
<CodeOutlined className="text-blue-500" />
<span className="font-semibold text-gray-700">Available Primitives</span>
<span className="font-semibold text-gray-700">
{t("guardrails.customCodeModal.availablePrimitivesTitle")}
</span>
</div>
<p className="text-xs text-gray-500 mb-3">Click to copy functions to clipboard</p>
<p className="text-xs text-gray-500 mb-3">{t("guardrails.customCodeModal.availablePrimitivesDesc")}</p>
<Collapse
defaultActiveKey={["Return Values"]}
className="primitives-collapse bg-transparent border-0"
expandIconPosition="end"
>
{Object.entries(PRIMITIVES).map(([category, primitives]) => (
{Object.entries(primitives).map(([category, categoryPrimitives]) => (
<Panel
header={<span className="text-sm font-medium text-gray-700">{category}</span>}
key={category}
className="bg-white mb-2 rounded-lg border border-gray-200"
>
<div className="space-y-2">
{primitives.map((p) => (
{categoryPrimitives.map((p) => (
<button
key={p.name}
onClick={() => copyPrimitive(p.name)}
@ -789,7 +823,7 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
>
{copiedPrimitive === p.name ? (
<span className="flex items-center gap-1 text-xs font-mono text-green-700">
<CheckCircleOutlined /> Copied!
<CheckCircleOutlined /> {t("guardrails.customCodeModal.copied")}
</span>
) : (
<>
@ -808,10 +842,10 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
{/* Footer */}
<div className="flex items-center justify-between pt-4 mt-4 border-t border-gray-200">
<span className="text-xs text-gray-400">Changes are auto-saved to local draft</span>
<span className="text-xs text-gray-400">{t("guardrails.customCodeModal.autoSaveNotice")}</span>
<div className="flex items-center gap-3">
<Button variant="secondary" onClick={onClose}>
Cancel
{t("common.cancel")}
</Button>
<Button
onClick={handleSave}
@ -819,7 +853,7 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
disabled={isSaving || !guardrailName.trim()}
icon={SaveOutlined}
>
{isEditMode ? "Update Guardrail" : "Save Guardrail"}
{isEditMode ? t("guardrails.customCodeModal.updateButton") : t("guardrails.customCodeModal.saveButton")}
</Button>
</div>
</div>

View file

@ -1,6 +1,7 @@
import React, { useState, useEffect } from "react";
import { Form, Typography, Select, Input, Switch, Modal } from "antd";
import { Button, TextInput } from "@tremor/react";
import { useTranslation } from "react-i18next";
import {
guardrail_provider_map,
guardrailLogoMap,
@ -54,6 +55,7 @@ const EditGuardrailForm: React.FC<EditGuardrailFormProps> = ({
fullLitellmParams,
initialValues,
}) => {
const { t } = useTranslation();
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [selectedProvider, setSelectedProvider] = useState<string | null>(initialValues?.provider || null);
@ -71,7 +73,7 @@ const EditGuardrailForm: React.FC<EditGuardrailFormProps> = ({
setGuardrailSettings(data);
} catch (error) {
console.error("Error fetching guardrail settings:", error);
NotificationsManager.fromBackend("Failed to load guardrail settings");
NotificationsManager.fromBackend(t("guardrails.editGuardrailForm.loadSettingsFailed"));
}
};
@ -178,7 +180,7 @@ const EditGuardrailForm: React.FC<EditGuardrailFormProps> = ({
guardrail_info = configObj;
}
} catch (error) {
NotificationsManager.fromBackend("Invalid JSON in configuration");
NotificationsManager.fromBackend(t("guardrails.editGuardrailForm.invalidJsonConfig"));
setLoading(false);
return;
}
@ -222,7 +224,7 @@ const EditGuardrailForm: React.FC<EditGuardrailFormProps> = ({
throw new Error(errorData || "Failed to update guardrail");
}
NotificationsManager.success("Guardrail updated successfully");
NotificationsManager.success(t("guardrails.editGuardrailForm.updateSuccess"));
// Reset and close
onSuccess();
@ -230,7 +232,9 @@ const EditGuardrailForm: React.FC<EditGuardrailFormProps> = ({
} catch (error) {
console.error("Failed to update guardrail:", error);
NotificationsManager.fromBackend(
"Failed to update guardrail: " + (error instanceof Error ? error.message : String(error)),
t("guardrails.editGuardrailForm.updateFailed", {
error: error instanceof Error ? error.message : String(error),
}),
);
} finally {
setLoading(false);
@ -264,7 +268,11 @@ const EditGuardrailForm: React.FC<EditGuardrailFormProps> = ({
switch (selectedProvider) {
case "Aporia":
return (
<Form.Item label="Aporia Configuration" name="config" tooltip="JSON configuration for Aporia">
<Form.Item
label={t("guardrails.editGuardrailForm.aporiaConfigLabel")}
name="config"
tooltip={t("guardrails.editGuardrailForm.aporiaConfigTooltip")}
>
<Input.TextArea
rows={4}
placeholder={`{
@ -276,7 +284,11 @@ const EditGuardrailForm: React.FC<EditGuardrailFormProps> = ({
);
case "AimSecurity":
return (
<Form.Item label="Aim Security Configuration" name="config" tooltip="JSON configuration for Aim Security">
<Form.Item
label={t("guardrails.editGuardrailForm.aimConfigLabel")}
name="config"
tooltip={t("guardrails.editGuardrailForm.aimConfigTooltip")}
>
<Input.TextArea
rows={4}
placeholder={`{
@ -288,9 +300,9 @@ const EditGuardrailForm: React.FC<EditGuardrailFormProps> = ({
case "Bedrock":
return (
<Form.Item
label="Amazon Bedrock Configuration"
label={t("guardrails.editGuardrailForm.bedrockConfigLabel")}
name="config"
tooltip="JSON configuration for Amazon Bedrock guardrails"
tooltip={t("guardrails.editGuardrailForm.bedrockConfigTooltip")}
>
<Input.TextArea
rows={4}
@ -303,7 +315,11 @@ const EditGuardrailForm: React.FC<EditGuardrailFormProps> = ({
);
case "CatoNetworks":
return (
<Form.Item label="Cato Networks Configuration" name="config" tooltip="JSON configuration for Cato Networks">
<Form.Item
label={t("guardrails.editGuardrailForm.catoConfigLabel")}
name="config"
tooltip={t("guardrails.editGuardrailForm.catoConfigTooltip")}
>
<Input.TextArea
rows={4}
placeholder={`{
@ -314,7 +330,11 @@ const EditGuardrailForm: React.FC<EditGuardrailFormProps> = ({
);
case "GuardrailsAI":
return (
<Form.Item label="Guardrails.ai Configuration" name="config" tooltip="JSON configuration for Guardrails.ai">
<Form.Item
label={t("guardrails.editGuardrailForm.guardrailsAiConfigLabel")}
name="config"
tooltip={t("guardrails.editGuardrailForm.guardrailsAiConfigTooltip")}
>
<Input.TextArea
rows={4}
placeholder={`{
@ -326,7 +346,11 @@ const EditGuardrailForm: React.FC<EditGuardrailFormProps> = ({
);
case "LakeraAI":
return (
<Form.Item label="Lakera AI Configuration" name="config" tooltip="JSON configuration for Lakera AI">
<Form.Item
label={t("guardrails.editGuardrailForm.lakeraConfigLabel")}
name="config"
tooltip={t("guardrails.editGuardrailForm.lakeraConfigTooltip")}
>
<Input.TextArea
rows={4}
placeholder={`{
@ -338,9 +362,9 @@ const EditGuardrailForm: React.FC<EditGuardrailFormProps> = ({
case "PromptInjection":
return (
<Form.Item
label="Prompt Injection Configuration"
label={t("guardrails.editGuardrailForm.promptInjectionConfigLabel")}
name="config"
tooltip="JSON configuration for prompt injection detection"
tooltip={t("guardrails.editGuardrailForm.promptInjectionConfigTooltip")}
>
<Input.TextArea
rows={4}
@ -352,7 +376,11 @@ const EditGuardrailForm: React.FC<EditGuardrailFormProps> = ({
);
default:
return (
<Form.Item label="Custom Configuration" name="config" tooltip="JSON configuration for your custom guardrail">
<Form.Item
label={t("guardrails.editGuardrailForm.customConfigLabel")}
name="config"
tooltip={t("guardrails.editGuardrailForm.customConfigTooltip")}
>
<Input.TextArea
rows={4}
placeholder={`{
@ -366,25 +394,31 @@ const EditGuardrailForm: React.FC<EditGuardrailFormProps> = ({
};
return (
<Modal title="Edit Guardrail" open={visible} onCancel={onClose} footer={null} width={700}>
<Modal
title={t("guardrails.editGuardrailForm.modalTitle")}
open={visible}
onCancel={onClose}
footer={null}
width={700}
>
<Form form={form} layout="vertical" initialValues={initialValues}>
<Form.Item
name="guardrail_name"
label="Guardrail Name"
rules={[{ required: true, message: "Please enter a guardrail name" }]}
label={t("guardrails.editGuardrailForm.guardrailNameLabel")}
rules={[{ required: true, message: t("guardrails.editGuardrailForm.guardrailNameRequired") }]}
>
<TextInput placeholder="Enter a name for this guardrail" />
<TextInput placeholder={t("guardrails.editGuardrailForm.guardrailNamePlaceholder")} />
</Form.Item>
<Form.Item
name="provider"
label="Guardrail Provider"
rules={[{ required: true, message: "Please select a provider" }]}
label={t("guardrails.editGuardrailForm.providerLabel")}
rules={[{ required: true, message: t("guardrails.editGuardrailForm.providerRequired") }]}
>
<Select
placeholder="Select a guardrail provider"
placeholder={t("guardrails.editGuardrailForm.providerPlaceholder")}
onChange={handleProviderChange}
disabled={true} // Disable changing provider in edit mode
disabled={true}
optionLabelProp="label"
>
{Object.entries(getGuardrailProviders()).map(([key, value]) => (
@ -415,9 +449,9 @@ const EditGuardrailForm: React.FC<EditGuardrailFormProps> = ({
<Form.Item
name="mode"
label="Mode"
tooltip="How the guardrail should be applied"
rules={[{ required: true, message: "Please select a mode" }]}
label={t("guardrails.editGuardrailForm.modeLabel")}
tooltip={t("guardrails.editGuardrailForm.modeTooltip")}
rules={[{ required: true, message: t("guardrails.editGuardrailForm.modeRequired") }]}
>
<Select>
{guardrailSettings?.supported_modes?.map((mode) => (
@ -435,8 +469,8 @@ const EditGuardrailForm: React.FC<EditGuardrailFormProps> = ({
<Form.Item
name="default_on"
label="Always On"
tooltip="If enabled, this guardrail will be applied to all requests by default"
label={t("guardrails.editGuardrailForm.alwaysOnLabel")}
tooltip={t("guardrails.editGuardrailForm.alwaysOnTooltip")}
valuePropName="checked"
>
<Switch />
@ -444,25 +478,25 @@ const EditGuardrailForm: React.FC<EditGuardrailFormProps> = ({
<Form.Item
name="skip_system_message_choice"
label="Skip system messages in guardrail"
tooltip="Unified guardrails only: whether role: system content is omitted from guardrail input (LLM still receives full messages). Use global default follows litellm_settings.skip_system_message_in_guardrail."
label={t("guardrails.editGuardrailForm.skipSystemMsgLabel")}
tooltip={t("guardrails.editGuardrailForm.skipSystemMsgTooltip")}
>
<Select>
<Option value="inherit">Use global default</Option>
<Option value="yes">Yes — exclude from guardrail scan</Option>
<Option value="no">No — always include in scan</Option>
<Option value="inherit">{t("guardrails.editGuardrailForm.useGlobalDefault")}</Option>
<Option value="yes">{t("guardrails.editGuardrailForm.yesExcludeFromScan")}</Option>
<Option value="no">{t("guardrails.editGuardrailForm.noAlwaysInclude")}</Option>
</Select>
</Form.Item>
<Form.Item
name="skip_tool_message_choice"
label="Skip tool messages in guardrail"
tooltip="Unified guardrails only: whether role: tool content is omitted from guardrail input (LLM still receives full messages). Use global default follows litellm_settings.skip_tool_message_in_guardrail."
label={t("guardrails.editGuardrailForm.skipToolMsgLabel")}
tooltip={t("guardrails.editGuardrailForm.skipToolMsgTooltip")}
>
<Select>
<Option value="inherit">Use global default</Option>
<Option value="yes">Yes — exclude from guardrail scan</Option>
<Option value="no">No — always include in scan</Option>
<Option value="inherit">{t("guardrails.editGuardrailForm.useGlobalDefault")}</Option>
<Option value="yes">{t("guardrails.editGuardrailForm.yesExcludeFromScan")}</Option>
<Option value="no">{t("guardrails.editGuardrailForm.noAlwaysInclude")}</Option>
</Select>
</Form.Item>
@ -470,10 +504,10 @@ const EditGuardrailForm: React.FC<EditGuardrailFormProps> = ({
<div className="flex justify-end space-x-2 mt-4">
<Button variant="secondary" onClick={onClose}>
Cancel
{t("common.cancel")}
</Button>
<Button onClick={handleSubmit} loading={loading}>
Update Guardrail
{t("guardrails.editGuardrailForm.updateButton")}
</Button>
</div>
</Form>

View file

@ -2714,6 +2714,154 @@
"actionHelp": "Choose what action the guardrail should take when this pattern is detected",
"actionBlock": "Block",
"actionMask": "Mask"
},
"keywordModal": {
"title": "Add blocked keyword",
"keywordLabel": "Keyword",
"keywordPlaceholder": "Enter sensitive keyword or phrase",
"actionLabel": "Action",
"actionDesc": "Choose what action the guardrail should take when this keyword is detected",
"actionBlock": "Block",
"actionMask": "Mask",
"descriptionLabel": "Description (optional)",
"descriptionPlaceholder": "Explain why this keyword is sensitive"
},
"keywordTable": {
"colKeyword": "Keyword",
"colAction": "Action",
"colDescription": "Description",
"actionBlock": "Block",
"actionMask": "Mask",
"noKeywords": "No keywords added."
},
"patternModal": {
"title": "Add prebuilt pattern",
"patternTypeLabel": "Pattern type",
"patternTypePlaceholder": "Choose pattern type",
"actionLabel": "Action",
"actionDesc": "Choose what action the guardrail should take when this pattern is detected",
"actionBlock": "Block",
"actionMask": "Mask"
},
"patternTable": {
"colType": "Type",
"colPatternName": "Pattern name",
"colRegexPattern": "Regex pattern",
"colAction": "Action",
"typePrebuilt": "Prebuilt",
"typeCustom": "Custom",
"actionBlock": "Block",
"actionMask": "Mask",
"noPatterns": "No patterns added."
},
"customCodeModal": {
"editTitle": "Edit Custom Guardrail",
"createTitle": "Create Custom Guardrail",
"subtitle": "Define custom logic using Python-like syntax",
"guardrailNameLabel": "Guardrail Name",
"guardrailNamePlaceholder": "e.g., block-pii-custom",
"modeLabel": "Mode (can select multiple)",
"modePlaceholder": "Select modes",
"templateLabel": "Template",
"templateGroupStandard": "STANDARD",
"browseCommunityTemplates": "Browse Community templates",
"defaultOnLabel": "Default On",
"pythonLogicLabel": "Python Logic",
"restrictedEnv": "Restricted environment (no imports)",
"testPanelHeader": "Test Your Guardrail",
"testInputLabel": "Test Input (JSON)",
"loadExampleLabel": "Load example:",
"examplePreCall": "Pre-call",
"examplePreMcp": "Pre MCP",
"examplePostCall": "Post-call",
"runTestButton": "Run Test",
"runningButton": "Running...",
"testResultAllowed": "Allowed",
"testResultBlocked": "Blocked: {{reason}}",
"testResultModified": "Modified",
"testResultUnknown": "{{action}}",
"contributeBannerTitle": "Built a useful guardrail?",
"contributeBannerDesc": "Share it with the community and help others build faster",
"contributeButton": "Contribute Template",
"availablePrimitivesTitle": "Available Primitives",
"availablePrimitivesDesc": "Click to copy functions to clipboard",
"copied": "Copied!",
"autoSaveNotice": "Changes are auto-saved to local draft",
"updateButton": "Update Guardrail",
"saveButton": "Save Guardrail",
"noGuardrailName": "Please enter a guardrail name",
"noCustomCode": "Please enter custom code",
"noAccessToken": "No access token available",
"updateSuccess": "Custom code guardrail updated successfully",
"createSuccess": "Custom code guardrail created successfully",
"saveFailed": "Failed to {{action}} guardrail: {{error}}",
"invalidTestJson": "Invalid test input JSON",
"unknownError": "Unknown error occurred",
"primAllowDesc": "Let request/response through",
"primBlockDesc": "Reject with message",
"primModifyDesc": "Transform content",
"primHttpRequestDesc": "Make async HTTP request",
"primHttpGetDesc": "Async GET request",
"primHttpPostDesc": "Async POST request",
"primRegexMatchDesc": "Returns True if pattern found",
"primRegexReplaceDesc": "Replace all matches",
"primRegexFindAllDesc": "Return list of matches",
"primJsonParseDesc": "Parse JSON string, returns None on error",
"primJsonStringifyDesc": "Convert to JSON string",
"primJsonSchemaValidDesc": "Validate against JSON schema",
"primExtractUrlsDesc": "Extract all URLs from text",
"primIsValidUrlDesc": "Check if URL is valid",
"primAllUrlsValidDesc": "Check all URLs in text are valid",
"primDetectCodeDesc": "Returns True if code detected",
"primDetectCodeLangsDesc": "Returns list of detected languages",
"primContainsCodeLangDesc": "Check for specific languages",
"primContainsDesc": "Check if substring exists",
"primContainsAnyDesc": "Check if any substring exists",
"primWordCountDesc": "Count words",
"primCharCountDesc": "Count characters",
"primStringTransformsDesc": "String transforms"
},
"editGuardrailForm": {
"modalTitle": "Edit Guardrail",
"guardrailNameLabel": "Guardrail Name",
"guardrailNameRequired": "Please enter a guardrail name",
"guardrailNamePlaceholder": "Enter a name for this guardrail",
"providerLabel": "Guardrail Provider",
"providerRequired": "Please select a provider",
"providerPlaceholder": "Select a guardrail provider",
"modeLabel": "Mode",
"modeTooltip": "How the guardrail should be applied",
"modeRequired": "Please select a mode",
"alwaysOnLabel": "Always On",
"alwaysOnTooltip": "If enabled, this guardrail will be applied to all requests by default",
"skipSystemMsgLabel": "Skip system messages in guardrail",
"skipSystemMsgTooltip": "Unified guardrails only: whether role: system content is omitted from guardrail input (LLM still receives full messages). Use global default follows litellm_settings.skip_system_message_in_guardrail.",
"skipToolMsgLabel": "Skip tool messages in guardrail",
"skipToolMsgTooltip": "Unified guardrails only: whether role: tool content is omitted from guardrail input (LLM still receives full messages). Use global default follows litellm_settings.skip_tool_message_in_guardrail.",
"useGlobalDefault": "Use global default",
"yesExcludeFromScan": "Yes — exclude from guardrail scan",
"noAlwaysInclude": "No — always include in scan",
"updateButton": "Update Guardrail",
"aporiaConfigLabel": "Aporia Configuration",
"aporiaConfigTooltip": "JSON configuration for Aporia",
"aimConfigLabel": "Aim Security Configuration",
"aimConfigTooltip": "JSON configuration for Aim Security",
"bedrockConfigLabel": "Amazon Bedrock Configuration",
"bedrockConfigTooltip": "JSON configuration for Amazon Bedrock guardrails",
"catoConfigLabel": "Cato Networks Configuration",
"catoConfigTooltip": "JSON configuration for Cato Networks",
"guardrailsAiConfigLabel": "Guardrails.ai Configuration",
"guardrailsAiConfigTooltip": "JSON configuration for Guardrails.ai",
"lakeraConfigLabel": "Lakera AI Configuration",
"lakeraConfigTooltip": "JSON configuration for Lakera AI",
"promptInjectionConfigLabel": "Prompt Injection Configuration",
"promptInjectionConfigTooltip": "JSON configuration for prompt injection detection",
"customConfigLabel": "Custom Configuration",
"customConfigTooltip": "JSON configuration for your custom guardrail",
"updateSuccess": "Guardrail updated successfully",
"invalidJsonConfig": "Invalid JSON in configuration",
"loadSettingsFailed": "Failed to load guardrail settings",
"updateFailed": "Failed to update guardrail: {{error}}"
}
}
}

View file

@ -2714,6 +2714,154 @@
"actionHelp": "选择当检测到此模式时护栏应执行的操作",
"actionBlock": "拦截",
"actionMask": "遮蔽"
},
"keywordModal": {
"title": "添加屏蔽关键词",
"keywordLabel": "关键词",
"keywordPlaceholder": "输入敏感关键词或短语",
"actionLabel": "操作",
"actionDesc": "选择当检测到此关键词时护栏应执行的操作",
"actionBlock": "拦截",
"actionMask": "遮蔽",
"descriptionLabel": "描述(可选)",
"descriptionPlaceholder": "说明为何该关键词属于敏感内容"
},
"keywordTable": {
"colKeyword": "关键词",
"colAction": "操作",
"colDescription": "描述",
"actionBlock": "拦截",
"actionMask": "遮蔽",
"noKeywords": "暂无关键词。"
},
"patternModal": {
"title": "添加预置模式",
"patternTypeLabel": "模式类型",
"patternTypePlaceholder": "选择模式类型",
"actionLabel": "操作",
"actionDesc": "选择当检测到此模式时护栏应执行的操作",
"actionBlock": "拦截",
"actionMask": "遮蔽"
},
"patternTable": {
"colType": "类型",
"colPatternName": "模式名称",
"colRegexPattern": "正则表达式",
"colAction": "操作",
"typePrebuilt": "预置",
"typeCustom": "自定义",
"actionBlock": "拦截",
"actionMask": "遮蔽",
"noPatterns": "暂无模式。"
},
"customCodeModal": {
"editTitle": "编辑自定义护栏",
"createTitle": "创建自定义护栏",
"subtitle": "使用类 Python 语法定义自定义逻辑",
"guardrailNameLabel": "护栏名称",
"guardrailNamePlaceholder": "例如:block-pii-custom",
"modeLabel": "模式(可多选)",
"modePlaceholder": "选择模式",
"templateLabel": "模板",
"templateGroupStandard": "标准",
"browseCommunityTemplates": "浏览社区模板",
"defaultOnLabel": "默认开启",
"pythonLogicLabel": "Python 逻辑",
"restrictedEnv": "受限环境(不可导入模块)",
"testPanelHeader": "测试护栏",
"testInputLabel": "测试输入(JSON)",
"loadExampleLabel": "加载示例:",
"examplePreCall": "Pre-call",
"examplePreMcp": "Pre MCP",
"examplePostCall": "Post-call",
"runTestButton": "运行测试",
"runningButton": "运行中...",
"testResultAllowed": "已放行",
"testResultBlocked": "已拦截:{{reason}}",
"testResultModified": "已修改",
"testResultUnknown": "{{action}}",
"contributeBannerTitle": "构建了实用的护栏?",
"contributeBannerDesc": "分享给社区,帮助他人更快构建",
"contributeButton": "贡献模板",
"availablePrimitivesTitle": "可用原语",
"availablePrimitivesDesc": "点击将函数复制到剪贴板",
"copied": "已复制!",
"autoSaveNotice": "更改已自动保存为本地草稿",
"updateButton": "更新护栏",
"saveButton": "保存护栏",
"noGuardrailName": "请输入护栏名称",
"noCustomCode": "请输入自定义代码",
"noAccessToken": "无可用访问令牌",
"updateSuccess": "自定义代码护栏已成功更新",
"createSuccess": "自定义代码护栏已成功创建",
"saveFailed": "{{action}}护栏失败:{{error}}",
"invalidTestJson": "测试输入 JSON 格式无效",
"unknownError": "发生未知错误",
"primAllowDesc": "放行请求/响应",
"primBlockDesc": "拒绝并返回消息",
"primModifyDesc": "转换内容",
"primHttpRequestDesc": "发起异步 HTTP 请求",
"primHttpGetDesc": "异步 GET 请求",
"primHttpPostDesc": "异步 POST 请求",
"primRegexMatchDesc": "若找到匹配则返回 True",
"primRegexReplaceDesc": "替换所有匹配项",
"primRegexFindAllDesc": "返回所有匹配项列表",
"primJsonParseDesc": "解析 JSON 字符串,出错时返回 None",
"primJsonStringifyDesc": "转换为 JSON 字符串",
"primJsonSchemaValidDesc": "根据 JSON schema 验证",
"primExtractUrlsDesc": "从文本中提取所有 URL",
"primIsValidUrlDesc": "检查 URL 是否有效",
"primAllUrlsValidDesc": "检查文本中所有 URL 是否有效",
"primDetectCodeDesc": "若检测到代码则返回 True",
"primDetectCodeLangsDesc": "返回检测到的语言列表",
"primContainsCodeLangDesc": "检查特定语言",
"primContainsDesc": "检查子字符串是否存在",
"primContainsAnyDesc": "检查是否存在任意子字符串",
"primWordCountDesc": "统计单词数",
"primCharCountDesc": "统计字符数",
"primStringTransformsDesc": "字符串转换"
},
"editGuardrailForm": {
"modalTitle": "编辑护栏",
"guardrailNameLabel": "护栏名称",
"guardrailNameRequired": "请输入护栏名称",
"guardrailNamePlaceholder": "为此护栏输入名称",
"providerLabel": "护栏提供商",
"providerRequired": "请选择提供商",
"providerPlaceholder": "选择护栏提供商",
"modeLabel": "模式",
"modeTooltip": "护栏的应用方式",
"modeRequired": "请选择模式",
"alwaysOnLabel": "始终开启",
"alwaysOnTooltip": "启用后,此护栏将默认应用于所有请求",
"skipSystemMsgLabel": "在护栏中跳过系统消息",
"skipSystemMsgTooltip": "仅统一护栏:是否从护栏输入中省略 role: system 内容(LLM 仍接收完整消息)。使用全局默认值遵循 litellm_settings.skip_system_message_in_guardrail。",
"skipToolMsgLabel": "在护栏中跳过工具消息",
"skipToolMsgTooltip": "仅统一护栏:是否从护栏输入中省略 role: tool 内容(LLM 仍接收完整消息)。使用全局默认值遵循 litellm_settings.skip_tool_message_in_guardrail。",
"useGlobalDefault": "使用全局默认值",
"yesExcludeFromScan": "是 — 从护栏扫描中排除",
"noAlwaysInclude": "否 — 始终包含在扫描中",
"updateButton": "更新护栏",
"aporiaConfigLabel": "Aporia 配置",
"aporiaConfigTooltip": "Aporia 的 JSON 配置",
"aimConfigLabel": "Aim Security 配置",
"aimConfigTooltip": "Aim Security 的 JSON 配置",
"bedrockConfigLabel": "Amazon Bedrock 配置",
"bedrockConfigTooltip": "Amazon Bedrock 护栏的 JSON 配置",
"catoConfigLabel": "Cato Networks 配置",
"catoConfigTooltip": "Cato Networks 的 JSON 配置",
"guardrailsAiConfigLabel": "Guardrails.ai 配置",
"guardrailsAiConfigTooltip": "Guardrails.ai 的 JSON 配置",
"lakeraConfigLabel": "Lakera AI 配置",
"lakeraConfigTooltip": "Lakera AI 的 JSON 配置",
"promptInjectionConfigLabel": "提示词注入配置",
"promptInjectionConfigTooltip": "提示词注入检测的 JSON 配置",
"customConfigLabel": "自定义配置",
"customConfigTooltip": "自定义护栏的 JSON 配置",
"updateSuccess": "护栏已成功更新",
"invalidJsonConfig": "配置中的 JSON 格式无效",
"loadSettingsFailed": "加载护栏设置失败",
"updateFailed": "更新护栏失败:{{error}}"
}
}
}