mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for guardrails content filter components
This commit is contained in:
parent
59880539d3
commit
535e00626d
9 changed files with 374 additions and 110 deletions
|
|
@ -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;
|
||||
|
|
@ -28,9 +29,10 @@ const CategoryTable: React.FC<CategoryTableProps> = ({
|
|||
onRemove,
|
||||
readOnly = false,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const columns = [
|
||||
{
|
||||
title: "Category",
|
||||
title: t("guardrails.categoryTable.colCategory"),
|
||||
dataIndex: "display_name",
|
||||
key: "display_name",
|
||||
render: (displayName: string, record: ContentCategory) => (
|
||||
|
|
@ -47,7 +49,7 @@ const CategoryTable: React.FC<CategoryTableProps> = ({
|
|||
),
|
||||
},
|
||||
{
|
||||
title: "Severity Threshold",
|
||||
title: t("guardrails.categoryTable.colSeverityThreshold"),
|
||||
dataIndex: "severity_threshold",
|
||||
key: "severity_threshold",
|
||||
width: 180,
|
||||
|
|
@ -67,15 +69,15 @@ const CategoryTable: React.FC<CategoryTableProps> = ({
|
|||
style={{ width: 150 }}
|
||||
size="small"
|
||||
>
|
||||
<Option value="high">High</Option>
|
||||
<Option value="medium">Medium</Option>
|
||||
<Option value="low">Low</Option>
|
||||
<Option value="high">{t("guardrails.categoryTable.severityHigh")}</Option>
|
||||
<Option value="medium">{t("guardrails.categoryTable.severityMedium")}</Option>
|
||||
<Option value="low">{t("guardrails.categoryTable.severityLow")}</Option>
|
||||
</Select>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Action",
|
||||
title: t("guardrails.categoryTable.colAction"),
|
||||
dataIndex: "action",
|
||||
key: "action",
|
||||
width: 150,
|
||||
|
|
@ -90,8 +92,8 @@ const CategoryTable: React.FC<CategoryTableProps> = ({
|
|||
style={{ width: 120 }}
|
||||
size="small"
|
||||
>
|
||||
<Option value="BLOCK">Block</Option>
|
||||
<Option value="MASK">Mask</Option>
|
||||
<Option value="BLOCK">{t("guardrails.categoryTable.actionBlock")}</Option>
|
||||
<Option value="MASK">{t("guardrails.categoryTable.actionMask")}</Option>
|
||||
</Select>
|
||||
);
|
||||
},
|
||||
|
|
@ -105,14 +107,18 @@ const CategoryTable: React.FC<CategoryTableProps> = ({
|
|||
width: 100,
|
||||
render: (_: any, record: ContentCategory) => (
|
||||
<Button type="text" danger size="small" icon={<DeleteOutlined />} onClick={() => onRemove?.(record.id)}>
|
||||
Delete
|
||||
{t("common.delete")}
|
||||
</Button>
|
||||
),
|
||||
} as any);
|
||||
}
|
||||
|
||||
if (categories.length === 0) {
|
||||
return <div style={{ textAlign: "center", padding: "40px 0", color: "#999" }}>No categories configured.</div>;
|
||||
return (
|
||||
<div style={{ textAlign: "center", padding: "40px 0", color: "#999" }}>
|
||||
{t("guardrails.categoryTable.noCategories")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <Table dataSource={categories} columns={columns} rowKey="id" pagination={false} size="small" />;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Card, Typography, Select, Switch, Form, Space, InputNumber } from "antd";
|
||||
import { getMajorAirlines } from "../../networking";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
const { Option } = Select;
|
||||
|
|
@ -51,6 +52,7 @@ const CompetitorIntentConfiguration: React.FC<CompetitorIntentConfigurationProps
|
|||
onChange,
|
||||
accessToken,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const effectiveConfig = config ?? DEFAULT_CONFIG;
|
||||
const [airlineOptions, setAirlineOptions] = useState<MajorAirline[]>([]);
|
||||
const [loadingAirlines, setLoadingAirlines] = useState(false);
|
||||
|
|
@ -117,17 +119,14 @@ const CompetitorIntentConfiguration: React.FC<CompetitorIntentConfigurationProps
|
|||
title={
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<Title level={5} style={{ margin: 0 }}>
|
||||
Competitor Intent Filter
|
||||
{t("guardrails.competitorIntentConfiguration.title")}
|
||||
</Title>
|
||||
<Switch checked={false} onChange={handleEnabledChange} />
|
||||
</div>
|
||||
}
|
||||
size="small"
|
||||
>
|
||||
<Text type="secondary">
|
||||
Block or reframe competitor comparison questions. When enabled, airline type auto-loads competitors from IATA;
|
||||
generic type requires manual competitor list.
|
||||
</Text>
|
||||
<Text type="secondary">{t("guardrails.competitorIntentConfiguration.disabledDescription")}</Text>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
|
@ -137,7 +136,7 @@ const CompetitorIntentConfiguration: React.FC<CompetitorIntentConfigurationProps
|
|||
title={
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<Title level={5} style={{ margin: 0 }}>
|
||||
Competitor Intent Filter
|
||||
{t("guardrails.competitorIntentConfiguration.title")}
|
||||
</Title>
|
||||
<Switch checked={enabled} onChange={handleEnabledChange} />
|
||||
</div>
|
||||
|
|
@ -145,28 +144,27 @@ const CompetitorIntentConfiguration: React.FC<CompetitorIntentConfigurationProps
|
|||
size="small"
|
||||
>
|
||||
<Text type="secondary" style={{ display: "block", marginBottom: 16 }}>
|
||||
Block or reframe competitor comparison questions. Airline type uses major airlines (excluding your brand);
|
||||
generic requires manual competitor list.
|
||||
{t("guardrails.competitorIntentConfiguration.enabledDescription")}
|
||||
</Text>
|
||||
<Form layout="vertical" size="small">
|
||||
<Form.Item label="Type">
|
||||
<Form.Item label={t("common.type")}>
|
||||
<Select
|
||||
value={effectiveConfig.competitor_intent_type}
|
||||
onChange={(v) => handleConfigChange("competitor_intent_type", v)}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
<Option value="airline">Airline (auto-load competitors from IATA)</Option>
|
||||
<Option value="generic">Generic (specify competitors manually)</Option>
|
||||
<Option value="airline">{t("guardrails.competitorIntentConfiguration.typeAirline")}</Option>
|
||||
<Option value="generic">{t("guardrails.competitorIntentConfiguration.typeGeneric")}</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Your Brand (brand_self)"
|
||||
label={t("guardrails.competitorIntentConfiguration.brandSelfLabel")}
|
||||
required
|
||||
help={
|
||||
effectiveConfig.competitor_intent_type === "airline"
|
||||
? "Select your airline from the list (excluded from competitors) or type to add a custom term"
|
||||
: "Names/codes users use for your brand"
|
||||
? t("guardrails.competitorIntentConfiguration.brandSelfHelpAirline")
|
||||
: t("guardrails.competitorIntentConfiguration.brandSelfHelpGeneric")
|
||||
}
|
||||
>
|
||||
<Select
|
||||
|
|
@ -174,10 +172,10 @@ const CompetitorIntentConfiguration: React.FC<CompetitorIntentConfigurationProps
|
|||
style={{ width: "100%" }}
|
||||
placeholder={
|
||||
loadingAirlines
|
||||
? "Loading airlines..."
|
||||
? t("guardrails.competitorIntentConfiguration.loadingAirlines")
|
||||
: effectiveConfig.competitor_intent_type === "airline"
|
||||
? "Search or select airline, or type to add custom"
|
||||
: "Type and press Enter to add"
|
||||
? t("guardrails.competitorIntentConfiguration.brandSelfPlaceholderAirline")
|
||||
: t("guardrails.competitorIntentConfiguration.brandSelfPlaceholderGeneric")
|
||||
}
|
||||
value={effectiveConfig.brand_self}
|
||||
onChange={(v) =>
|
||||
|
|
@ -212,13 +210,13 @@ const CompetitorIntentConfiguration: React.FC<CompetitorIntentConfigurationProps
|
|||
|
||||
{effectiveConfig.competitor_intent_type === "airline" && (
|
||||
<Form.Item
|
||||
label="Locations (optional)"
|
||||
help="Countries, cities, airports for disambiguation (e.g. qatar, doha)"
|
||||
label={t("guardrails.competitorIntentConfiguration.locationsLabel")}
|
||||
help={t("guardrails.competitorIntentConfiguration.locationsHelp")}
|
||||
>
|
||||
<Select
|
||||
mode="tags"
|
||||
style={{ width: "100%" }}
|
||||
placeholder="Type and press Enter to add"
|
||||
placeholder={t("guardrails.competitorIntentConfiguration.tagsPlaceholder")}
|
||||
value={effectiveConfig.locations ?? []}
|
||||
onChange={(v) => handleNestedArrayChange("locations", v ?? [])}
|
||||
tokenSeparators={[","]}
|
||||
|
|
@ -227,11 +225,15 @@ const CompetitorIntentConfiguration: React.FC<CompetitorIntentConfigurationProps
|
|||
)}
|
||||
|
||||
{effectiveConfig.competitor_intent_type === "generic" && (
|
||||
<Form.Item label="Competitors" required help="Competitor names to detect (required for generic type)">
|
||||
<Form.Item
|
||||
label={t("guardrails.competitorIntentConfiguration.competitorsLabel")}
|
||||
required
|
||||
help={t("guardrails.competitorIntentConfiguration.competitorsHelp")}
|
||||
>
|
||||
<Select
|
||||
mode="tags"
|
||||
style={{ width: "100%" }}
|
||||
placeholder="Type and press Enter to add"
|
||||
placeholder={t("guardrails.competitorIntentConfiguration.tagsPlaceholder")}
|
||||
value={effectiveConfig.competitors ?? []}
|
||||
onChange={(v) => handleNestedArrayChange("competitors", v ?? [])}
|
||||
tokenSeparators={[","]}
|
||||
|
|
@ -239,52 +241,57 @@ const CompetitorIntentConfiguration: React.FC<CompetitorIntentConfigurationProps
|
|||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Form.Item label="Policy: Competitor comparison">
|
||||
<Form.Item label={t("guardrails.competitorIntentConfiguration.policyCompetitorLabel")}>
|
||||
<Select
|
||||
value={effectiveConfig.policy?.competitor_comparison ?? "refuse"}
|
||||
onChange={(v) => handlePolicyChange("competitor_comparison", v)}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
<Option value="refuse">Refuse (block request)</Option>
|
||||
<Option value="reframe">Reframe (suggest alternative)</Option>
|
||||
<Option value="refuse">{t("guardrails.competitorIntentConfiguration.policyRefuse")}</Option>
|
||||
<Option value="reframe">{t("guardrails.competitorIntentConfiguration.policyReframe")}</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Policy: Possible competitor comparison">
|
||||
<Form.Item label={t("guardrails.competitorIntentConfiguration.policyPossibleCompetitorLabel")}>
|
||||
<Select
|
||||
value={effectiveConfig.policy?.possible_competitor_comparison ?? "reframe"}
|
||||
onChange={(v) => handlePolicyChange("possible_competitor_comparison", v)}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
<Option value="refuse">Refuse (block request)</Option>
|
||||
<Option value="reframe">Reframe (suggest alternative to backend LLM)</Option>
|
||||
<Option value="refuse">{t("guardrails.competitorIntentConfiguration.policyRefuse")}</Option>
|
||||
<Option value="reframe">{t("guardrails.competitorIntentConfiguration.policyReframeBackend")}</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Confidence thresholds"
|
||||
label={t("guardrails.competitorIntentConfiguration.confidenceThresholdsLabel")}
|
||||
help={
|
||||
<>
|
||||
Classify competitor intent by confidence (0–1). Higher confidence → stronger intent.
|
||||
{t("guardrails.competitorIntentConfiguration.confidenceThresholdsHelp")}
|
||||
<ul style={{ marginBottom: 0, marginTop: 4, paddingLeft: 20 }}>
|
||||
<li>
|
||||
<strong>High (≥)</strong>: Treat as full competitor comparison → uses "Competitor
|
||||
comparison" policy
|
||||
<strong>{t("guardrails.competitorIntentConfiguration.thresholdHighLabel")}</strong>:{" "}
|
||||
{t("guardrails.competitorIntentConfiguration.thresholdHighDesc")}
|
||||
</li>
|
||||
<li>
|
||||
<strong>Medium (≥)</strong>: Treat as possible comparison → uses "Possible competitor
|
||||
comparison" policy
|
||||
<strong>{t("guardrails.competitorIntentConfiguration.thresholdMediumLabel")}</strong>:{" "}
|
||||
{t("guardrails.competitorIntentConfiguration.thresholdMediumDesc")}
|
||||
</li>
|
||||
<li>
|
||||
<strong>Low (≥)</strong>: Log only; allow request. Below Low → allow with no action
|
||||
<strong>{t("guardrails.competitorIntentConfiguration.thresholdLowLabel")}</strong>:{" "}
|
||||
{t("guardrails.competitorIntentConfiguration.thresholdLowDesc")}
|
||||
</li>
|
||||
</ul>
|
||||
Raise thresholds to be more permissive; lower them to be stricter.
|
||||
{t("guardrails.competitorIntentConfiguration.thresholdsNote")}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Space wrap>
|
||||
<Form.Item label="High" style={{ marginBottom: 0 }} help="e.g. 0.7">
|
||||
<Form.Item
|
||||
label={t("guardrails.competitorIntentConfiguration.thresholdHighShort")}
|
||||
style={{ marginBottom: 0 }}
|
||||
help={t("guardrails.competitorIntentConfiguration.thresholdHighExample")}
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
max={1}
|
||||
|
|
@ -294,7 +301,11 @@ const CompetitorIntentConfiguration: React.FC<CompetitorIntentConfigurationProps
|
|||
style={{ width: 80 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="Medium" style={{ marginBottom: 0 }} help="e.g. 0.45">
|
||||
<Form.Item
|
||||
label={t("guardrails.competitorIntentConfiguration.thresholdMediumShort")}
|
||||
style={{ marginBottom: 0 }}
|
||||
help={t("guardrails.competitorIntentConfiguration.thresholdMediumExample")}
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
max={1}
|
||||
|
|
@ -304,7 +315,11 @@ const CompetitorIntentConfiguration: React.FC<CompetitorIntentConfigurationProps
|
|||
style={{ width: 80 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="Low" style={{ marginBottom: 0 }} help="e.g. 0.3">
|
||||
<Form.Item
|
||||
label={t("guardrails.competitorIntentConfiguration.thresholdLowShort")}
|
||||
style={{ marginBottom: 0 }}
|
||||
help={t("guardrails.competitorIntentConfiguration.thresholdLowExample")}
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
max={1}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React from "react";
|
|||
import { Card, Typography, Select, Table, Tag, Collapse, Button } from "antd";
|
||||
import { DeleteOutlined, PlusOutlined, FileTextOutlined } from "@ant-design/icons";
|
||||
import { getCategoryYaml } from "../../networking";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
const { Option } = Select;
|
||||
|
|
@ -42,6 +43,7 @@ const ContentCategoryConfiguration: React.FC<ContentCategoryConfigurationProps>
|
|||
pendingSelection,
|
||||
onPendingSelectionChange,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
// Use controlled state if parent provides it, otherwise use local state
|
||||
const [localSelectedCategoryName, setLocalSelectedCategoryName] = React.useState<string>("");
|
||||
const selectedCategoryName = pendingSelection !== undefined ? pendingSelection : localSelectedCategoryName;
|
||||
|
|
@ -166,7 +168,7 @@ const ContentCategoryConfiguration: React.FC<ContentCategoryConfigurationProps>
|
|||
|
||||
const columns = [
|
||||
{
|
||||
title: "Category",
|
||||
title: t("guardrails.contentCategoryConfiguration.colCategory"),
|
||||
dataIndex: "display_name",
|
||||
key: "display_name",
|
||||
render: (text: string, record: SelectedCategory) => {
|
||||
|
|
@ -182,7 +184,7 @@ const ContentCategoryConfiguration: React.FC<ContentCategoryConfigurationProps>
|
|||
},
|
||||
},
|
||||
{
|
||||
title: "Action",
|
||||
title: t("guardrails.contentCategoryConfiguration.colAction"),
|
||||
dataIndex: "action",
|
||||
key: "action",
|
||||
width: 150,
|
||||
|
|
@ -202,7 +204,7 @@ const ContentCategoryConfiguration: React.FC<ContentCategoryConfigurationProps>
|
|||
),
|
||||
},
|
||||
{
|
||||
title: "Severity Threshold",
|
||||
title: t("guardrails.contentCategoryConfiguration.colSeverityThreshold"),
|
||||
dataIndex: "severity_threshold",
|
||||
key: "severity_threshold",
|
||||
width: 180,
|
||||
|
|
@ -212,9 +214,9 @@ const ContentCategoryConfiguration: React.FC<ContentCategoryConfigurationProps>
|
|||
onChange={(value) => onCategoryUpdate(record.id, "severity_threshold", value)}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
<Option value="low">Low</Option>
|
||||
<Option value="medium">Medium</Option>
|
||||
<Option value="high">High</Option>
|
||||
<Option value="low">{t("guardrails.contentCategoryConfiguration.severityLow")}</Option>
|
||||
<Option value="medium">{t("guardrails.contentCategoryConfiguration.severityMedium")}</Option>
|
||||
<Option value="high">{t("guardrails.contentCategoryConfiguration.severityHigh")}</Option>
|
||||
</Select>
|
||||
),
|
||||
},
|
||||
|
|
@ -224,7 +226,7 @@ const ContentCategoryConfiguration: React.FC<ContentCategoryConfigurationProps>
|
|||
width: 80,
|
||||
render: (_: any, record: SelectedCategory) => (
|
||||
<Button icon={<DeleteOutlined />} onClick={() => onCategoryRemove(record.id)} size="small">
|
||||
Remove
|
||||
{t("common.remove")}
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
|
|
@ -241,10 +243,10 @@ const ContentCategoryConfiguration: React.FC<ContentCategoryConfigurationProps>
|
|||
style={{ display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 8 }}
|
||||
>
|
||||
<Title level={5} style={{ margin: 0 }}>
|
||||
Blocked topics
|
||||
{t("guardrails.contentCategoryConfiguration.title")}
|
||||
</Title>
|
||||
<Text type="secondary" style={{ fontSize: 12, fontWeight: 400 }}>
|
||||
Select topics to block using keyword and semantic analysis
|
||||
{t("guardrails.contentCategoryConfiguration.subtitle")}
|
||||
</Text>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -252,7 +254,7 @@ const ContentCategoryConfiguration: React.FC<ContentCategoryConfigurationProps>
|
|||
>
|
||||
<div style={{ marginBottom: 16, display: "flex", gap: 8 }}>
|
||||
<Select
|
||||
placeholder="Select a content category"
|
||||
placeholder={t("guardrails.contentCategoryConfiguration.selectPlaceholder")}
|
||||
value={selectedCategoryName || undefined}
|
||||
onChange={setSelectedCategoryName}
|
||||
style={{ flex: 1 }}
|
||||
|
|
@ -272,7 +274,7 @@ const ContentCategoryConfiguration: React.FC<ContentCategoryConfigurationProps>
|
|||
))}
|
||||
</Select>
|
||||
<Button type="primary" onClick={handleAddCategory} disabled={!selectedCategoryName} icon={<PlusOutlined />}>
|
||||
Add
|
||||
{t("common.add")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
|
@ -296,7 +298,9 @@ const ContentCategoryConfiguration: React.FC<ContentCategoryConfigurationProps>
|
|||
)}
|
||||
</div>
|
||||
{loadingPreviewYaml ? (
|
||||
<div style={{ padding: "16px", textAlign: "center", color: "#888" }}>Loading content...</div>
|
||||
<div style={{ padding: "16px", textAlign: "center", color: "#888" }}>
|
||||
{t("guardrails.contentCategoryConfiguration.loadingContent")}
|
||||
</div>
|
||||
) : previewYaml ? (
|
||||
<pre
|
||||
style={{
|
||||
|
|
@ -318,7 +322,7 @@ const ContentCategoryConfiguration: React.FC<ContentCategoryConfigurationProps>
|
|||
</pre>
|
||||
) : (
|
||||
<div style={{ padding: "8px", textAlign: "center", color: "#888", fontSize: "12px" }}>
|
||||
Unable to load category content
|
||||
{t("guardrails.contentCategoryConfiguration.unableToLoadContent")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -355,12 +359,17 @@ const ContentCategoryConfiguration: React.FC<ContentCategoryConfigurationProps>
|
|||
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||
<FileTextOutlined />
|
||||
<span>
|
||||
View {fileTypeLabel} for {category.display_name}
|
||||
{t("guardrails.contentCategoryConfiguration.viewFileFor", {
|
||||
fileType: fileTypeLabel,
|
||||
name: category.display_name,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
children: loadingYaml[category.category] ? (
|
||||
<div style={{ padding: "16px", textAlign: "center", color: "#888" }}>Loading content...</div>
|
||||
<div style={{ padding: "16px", textAlign: "center", color: "#888" }}>
|
||||
{t("guardrails.contentCategoryConfiguration.loadingContent")}
|
||||
</div>
|
||||
) : categoryYaml[category.category] ? (
|
||||
<pre
|
||||
style={{
|
||||
|
|
@ -378,7 +387,7 @@ const ContentCategoryConfiguration: React.FC<ContentCategoryConfigurationProps>
|
|||
</pre>
|
||||
) : (
|
||||
<div style={{ padding: "16px", textAlign: "center", color: "#888" }}>
|
||||
Content will load when expanded
|
||||
{t("guardrails.contentCategoryConfiguration.contentWillLoad")}
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
|
@ -396,7 +405,7 @@ const ContentCategoryConfiguration: React.FC<ContentCategoryConfigurationProps>
|
|||
borderRadius: "4px",
|
||||
}}
|
||||
>
|
||||
No blocked topics selected. Add topics to detect and block harmful content.
|
||||
{t("guardrails.contentCategoryConfiguration.noTopicsSelected")}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { Typography, Space, Upload, Card, Button } from "antd";
|
|||
import { PlusOutlined, UploadOutlined } from "@ant-design/icons";
|
||||
import { validateBlockedWordsFile } from "../../networking";
|
||||
import NotificationsManager from "../../molecules/notifications_manager";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import PatternModal from "./PatternModal";
|
||||
import CustomPatternModal from "./CustomPatternModal";
|
||||
import KeywordModal from "./KeywordModal";
|
||||
|
|
@ -102,6 +103,7 @@ const ContentFilterConfiguration: React.FC<ContentFilterConfigurationProps> = ({
|
|||
competitorIntentConfig = null,
|
||||
onCompetitorIntentChange,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [patternModalVisible, setPatternModalVisible] = useState(false);
|
||||
const [keywordModalVisible, setKeywordModalVisible] = useState(false);
|
||||
const [customPatternModalVisible, setCustomPatternModalVisible] = useState(false);
|
||||
|
|
@ -118,7 +120,7 @@ const ContentFilterConfiguration: React.FC<ContentFilterConfigurationProps> = ({
|
|||
|
||||
const handleAddPrebuiltPattern = () => {
|
||||
if (!selectedPatternName) {
|
||||
NotificationsManager.error("Please select a pattern");
|
||||
NotificationsManager.error(t("guardrails.contentFilterConfiguration.pleaseSelectPattern"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -139,7 +141,7 @@ const ContentFilterConfiguration: React.FC<ContentFilterConfigurationProps> = ({
|
|||
|
||||
const handleAddCustomPattern = () => {
|
||||
if (!customPatternName || !customPatternRegex) {
|
||||
NotificationsManager.error("Please provide pattern name and regex");
|
||||
NotificationsManager.error(t("guardrails.contentFilterConfiguration.pleaseProvidePatternNameAndRegex"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -159,7 +161,7 @@ const ContentFilterConfiguration: React.FC<ContentFilterConfigurationProps> = ({
|
|||
|
||||
const handleAddKeyword = () => {
|
||||
if (!newKeyword) {
|
||||
NotificationsManager.error("Please enter a keyword");
|
||||
NotificationsManager.error(t("guardrails.contentFilterConfiguration.pleaseEnterKeyword"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -187,14 +189,21 @@ const ContentFilterConfiguration: React.FC<ContentFilterConfigurationProps> = ({
|
|||
if (onFileUpload) {
|
||||
onFileUpload(content);
|
||||
}
|
||||
NotificationsManager.success(result.message || "File uploaded successfully");
|
||||
NotificationsManager.success(
|
||||
result.message || t("guardrails.contentFilterConfiguration.fileUploadedSuccessfully"),
|
||||
);
|
||||
} else {
|
||||
const errorMessage = result.error || (result.errors && result.errors.join(", ")) || "Invalid file";
|
||||
NotificationsManager.error(`Validation failed: ${errorMessage}`);
|
||||
const errorMessage =
|
||||
result.error ||
|
||||
(result.errors && result.errors.join(", ")) ||
|
||||
t("guardrails.contentFilterConfiguration.invalidFile");
|
||||
NotificationsManager.error(
|
||||
t("guardrails.contentFilterConfiguration.validationFailed", { error: errorMessage }),
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
NotificationsManager.error(`Failed to upload file: ${error}`);
|
||||
NotificationsManager.error(t("guardrails.contentFilterConfiguration.failedToUploadFile", { error }));
|
||||
} finally {
|
||||
setUploadValidating(false);
|
||||
}
|
||||
|
|
@ -210,10 +219,7 @@ const ContentFilterConfiguration: React.FC<ContentFilterConfigurationProps> = ({
|
|||
<div className="space-y-6">
|
||||
{!showStep && (
|
||||
<div>
|
||||
<Text type="secondary">
|
||||
Configure patterns, keywords, and content categories to detect and filter sensitive information in requests
|
||||
and responses.
|
||||
</Text>
|
||||
<Text type="secondary">{t("guardrails.contentFilterConfiguration.description")}</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -222,10 +228,10 @@ const ContentFilterConfiguration: React.FC<ContentFilterConfigurationProps> = ({
|
|||
title={
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<Title level={5} style={{ margin: 0 }}>
|
||||
Pattern Detection
|
||||
{t("guardrails.contentFilterConfiguration.patternDetectionTitle")}
|
||||
</Title>
|
||||
<Text type="secondary" style={{ fontSize: 14, fontWeight: 400 }}>
|
||||
Detect sensitive information using regex patterns (SSN, credit cards, API keys, etc.)
|
||||
{t("guardrails.contentFilterConfiguration.patternDetectionSubtitle")}
|
||||
</Text>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -234,10 +240,10 @@ const ContentFilterConfiguration: React.FC<ContentFilterConfigurationProps> = ({
|
|||
<div style={{ marginBottom: 16 }}>
|
||||
<Space>
|
||||
<Button type="primary" onClick={() => setPatternModalVisible(true)} icon={<PlusOutlined />}>
|
||||
Add prebuilt pattern
|
||||
{t("guardrails.contentFilterConfiguration.addPrebuiltPattern")}
|
||||
</Button>
|
||||
<Button onClick={() => setCustomPatternModalVisible(true)} icon={<PlusOutlined />}>
|
||||
Add custom regex
|
||||
{t("guardrails.contentFilterConfiguration.addCustomRegex")}
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
|
@ -250,10 +256,10 @@ const ContentFilterConfiguration: React.FC<ContentFilterConfigurationProps> = ({
|
|||
title={
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<Title level={5} style={{ margin: 0 }}>
|
||||
Blocked Keywords
|
||||
{t("guardrails.contentFilterConfiguration.blockedKeywordsTitle")}
|
||||
</Title>
|
||||
<Text type="secondary" style={{ fontSize: 14, fontWeight: 400 }}>
|
||||
Block or mask specific sensitive terms and phrases
|
||||
{t("guardrails.contentFilterConfiguration.blockedKeywordsSubtitle")}
|
||||
</Text>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -262,11 +268,11 @@ const ContentFilterConfiguration: React.FC<ContentFilterConfigurationProps> = ({
|
|||
<div style={{ marginBottom: 16 }}>
|
||||
<Space>
|
||||
<Button type="primary" onClick={() => setKeywordModalVisible(true)} icon={<PlusOutlined />}>
|
||||
Add keyword
|
||||
{t("guardrails.contentFilterConfiguration.addKeyword")}
|
||||
</Button>
|
||||
<Upload beforeUpload={handleFileUpload} accept=".yaml,.yml" showUploadList={false}>
|
||||
<Button icon={<UploadOutlined />} loading={uploadValidating}>
|
||||
Upload YAML file
|
||||
{t("guardrails.contentFilterConfiguration.uploadYamlFile")}
|
||||
</Button>
|
||||
</Upload>
|
||||
</Space>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { Card, Text, Badge } from "@tremor/react";
|
|||
import PatternTable from "./PatternTable";
|
||||
import KeywordTable from "./KeywordTable";
|
||||
import CategoryTable from "./CategoryTable";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface Pattern {
|
||||
id: string;
|
||||
|
|
@ -55,6 +56,8 @@ const ContentFilterDisplay: React.FC<ContentFilterDisplayProps> = ({
|
|||
onCategorySeverityChange,
|
||||
onCategoryRemove,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (patterns.length === 0 && blockedWords.length === 0 && categories.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -67,8 +70,10 @@ const ContentFilterDisplay: React.FC<ContentFilterDisplayProps> = ({
|
|||
{categories.length > 0 && (
|
||||
<Card className="mt-6">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Text className="text-lg font-semibold">Content Categories</Text>
|
||||
<Badge color="blue">{categories.length} categories configured</Badge>
|
||||
<Text className="text-lg font-semibold">{t("guardrails.contentFilterDisplay.contentCategories")}</Text>
|
||||
<Badge color="blue">
|
||||
{t("guardrails.contentFilterDisplay.categoriesConfigured", { count: categories.length })}
|
||||
</Badge>
|
||||
</div>
|
||||
<CategoryTable
|
||||
categories={categories}
|
||||
|
|
@ -83,8 +88,10 @@ const ContentFilterDisplay: React.FC<ContentFilterDisplayProps> = ({
|
|||
{patterns.length > 0 && (
|
||||
<Card className="mt-6">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Text className="text-lg font-semibold">Pattern Detection</Text>
|
||||
<Badge color="blue">{patterns.length} patterns configured</Badge>
|
||||
<Text className="text-lg font-semibold">{t("guardrails.contentFilterDisplay.patternDetection")}</Text>
|
||||
<Badge color="blue">
|
||||
{t("guardrails.contentFilterDisplay.patternsConfigured", { count: patterns.length })}
|
||||
</Badge>
|
||||
</div>
|
||||
<PatternTable
|
||||
patterns={patterns}
|
||||
|
|
@ -97,8 +104,10 @@ const ContentFilterDisplay: React.FC<ContentFilterDisplayProps> = ({
|
|||
{blockedWords.length > 0 && (
|
||||
<Card className="mt-6">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Text className="text-lg font-semibold">Blocked Keywords</Text>
|
||||
<Badge color="blue">{blockedWords.length} keywords configured</Badge>
|
||||
<Text className="text-lg font-semibold">{t("guardrails.contentFilterDisplay.blockedKeywords")}</Text>
|
||||
<Badge color="blue">
|
||||
{t("guardrails.contentFilterDisplay.keywordsConfigured", { count: blockedWords.length })}
|
||||
</Badge>
|
||||
</div>
|
||||
<KeywordTable
|
||||
keywords={blockedWords}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import React, { useEffect, useState } from "react";
|
|||
import ContentFilterConfiguration from "./ContentFilterConfiguration";
|
||||
import ContentFilterDisplay from "./ContentFilterDisplay";
|
||||
import type { CompetitorIntentConfig } from "./CompetitorIntentConfiguration";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
|
|
@ -74,6 +75,7 @@ const ContentFilterManager: React.FC<ContentFilterManagerProps> = ({
|
|||
onDataChange,
|
||||
onUnsavedChanges,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [selectedPatterns, setSelectedPatterns] = useState<Pattern[]>([]);
|
||||
const [blockedWords, setBlockedWords] = useState<BlockedWord[]>([]);
|
||||
const [selectedContentCategories, setSelectedContentCategories] = useState<SelectedContentCategory[]>([]);
|
||||
|
|
@ -233,18 +235,13 @@ const ContentFilterManager: React.FC<ContentFilterManagerProps> = ({
|
|||
// Edit mode
|
||||
return (
|
||||
<>
|
||||
<Divider orientation="left">Content Filter Configuration</Divider>
|
||||
<Divider orientation="left">{t("guardrails.contentFilterManager.dividerTitle")}</Divider>
|
||||
{hasUnsavedChanges && (
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
className="mb-4"
|
||||
message={
|
||||
<Text>
|
||||
You have unsaved changes to patterns or keywords. Remember to click "Save Changes" at the
|
||||
bottom.
|
||||
</Text>
|
||||
}
|
||||
message={<Text>{t("guardrails.contentFilterManager.unsavedChangesWarning")}</Text>}
|
||||
/>
|
||||
)}
|
||||
<div className="mb-6">
|
||||
|
|
|
|||
|
|
@ -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,20 @@ const CustomPatternModal: React.FC<CustomPatternModalProps> = ({
|
|||
onAdd,
|
||||
onCancel,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Modal title="Add custom regex pattern" open={visible} onCancel={onCancel} footer={null} width={800}>
|
||||
<Modal
|
||||
title={t("guardrails.customPatternModal.title")}
|
||||
open={visible}
|
||||
onCancel={onCancel}
|
||||
footer={null}
|
||||
width={800}
|
||||
>
|
||||
<Space direction="vertical" style={{ width: "100%" }} size="large">
|
||||
<div>
|
||||
<Text strong>Pattern name</Text>
|
||||
<Text strong>{t("guardrails.customPatternModal.patternNameLabel")}</Text>
|
||||
<Input
|
||||
placeholder="e.g., internal_id, employee_code"
|
||||
placeholder={t("guardrails.customPatternModal.patternNamePlaceholder")}
|
||||
value={patternName}
|
||||
onChange={(e) => onNameChange(e.target.value)}
|
||||
style={{ marginTop: 8 }}
|
||||
|
|
@ -41,34 +49,34 @@ const CustomPatternModal: React.FC<CustomPatternModalProps> = ({
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<Text strong>Regex pattern</Text>
|
||||
<Text strong>{t("guardrails.customPatternModal.regexPatternLabel")}</Text>
|
||||
<Input
|
||||
placeholder="e.g., ID-[0-9]{6}"
|
||||
placeholder={t("guardrails.customPatternModal.regexPatternPlaceholder")}
|
||||
value={patternRegex}
|
||||
onChange={(e) => onRegexChange(e.target.value)}
|
||||
style={{ marginTop: 8 }}
|
||||
/>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
Enter a valid regular expression to match sensitive data
|
||||
{t("guardrails.customPatternModal.regexPatternHelp")}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text strong>Action</Text>
|
||||
<Text strong>{t("guardrails.customPatternModal.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.customPatternModal.actionHelp")}
|
||||
</Text>
|
||||
<Select value={patternAction} onChange={onActionChange} style={{ width: "100%" }}>
|
||||
<Option value="BLOCK">Block</Option>
|
||||
<Option value="MASK">Mask</Option>
|
||||
<Option value="BLOCK">{t("guardrails.customPatternModal.actionBlock")}</Option>
|
||||
<Option value="MASK">{t("guardrails.customPatternModal.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>
|
||||
|
|
|
|||
|
|
@ -2607,6 +2607,113 @@
|
|||
"errorTitle": "{{name}} - Error",
|
||||
"resultCopied": "Result copied to clipboard",
|
||||
"failedToCopyResult": "Failed to copy result"
|
||||
},
|
||||
"categoryTable": {
|
||||
"colCategory": "Category",
|
||||
"colSeverityThreshold": "Severity Threshold",
|
||||
"colAction": "Action",
|
||||
"severityHigh": "High",
|
||||
"severityMedium": "Medium",
|
||||
"severityLow": "Low",
|
||||
"actionBlock": "Block",
|
||||
"actionMask": "Mask",
|
||||
"noCategories": "No categories configured."
|
||||
},
|
||||
"competitorIntentConfiguration": {
|
||||
"title": "Competitor Intent Filter",
|
||||
"disabledDescription": "Block or reframe competitor comparison questions. When enabled, airline type auto-loads competitors from IATA; generic type requires manual competitor list.",
|
||||
"enabledDescription": "Block or reframe competitor comparison questions. Airline type uses major airlines (excluding your brand); generic requires manual competitor list.",
|
||||
"typeAirline": "Airline (auto-load competitors from IATA)",
|
||||
"typeGeneric": "Generic (specify competitors manually)",
|
||||
"brandSelfLabel": "Your Brand (brand_self)",
|
||||
"brandSelfHelpAirline": "Select your airline from the list (excluded from competitors) or type to add a custom term",
|
||||
"brandSelfHelpGeneric": "Names/codes users use for your brand",
|
||||
"loadingAirlines": "Loading airlines...",
|
||||
"brandSelfPlaceholderAirline": "Search or select airline, or type to add custom",
|
||||
"brandSelfPlaceholderGeneric": "Type and press Enter to add",
|
||||
"locationsLabel": "Locations (optional)",
|
||||
"locationsHelp": "Countries, cities, airports for disambiguation (e.g. qatar, doha)",
|
||||
"competitorsLabel": "Competitors",
|
||||
"competitorsHelp": "Competitor names to detect (required for generic type)",
|
||||
"tagsPlaceholder": "Type and press Enter to add",
|
||||
"policyCompetitorLabel": "Policy: Competitor comparison",
|
||||
"policyPossibleCompetitorLabel": "Policy: Possible competitor comparison",
|
||||
"policyRefuse": "Refuse (block request)",
|
||||
"policyReframe": "Reframe (suggest alternative)",
|
||||
"policyReframeBackend": "Reframe (suggest alternative to backend LLM)",
|
||||
"confidenceThresholdsLabel": "Confidence thresholds",
|
||||
"confidenceThresholdsHelp": "Classify competitor intent by confidence (0–1). Higher confidence → stronger intent.",
|
||||
"thresholdHighLabel": "High (≥)",
|
||||
"thresholdHighDesc": "Treat as full competitor comparison → uses \"Competitor comparison\" policy",
|
||||
"thresholdMediumLabel": "Medium (≥)",
|
||||
"thresholdMediumDesc": "Treat as possible comparison → uses \"Possible competitor comparison\" policy",
|
||||
"thresholdLowLabel": "Low (≥)",
|
||||
"thresholdLowDesc": "Log only; allow request. Below Low → allow with no action",
|
||||
"thresholdsNote": "Raise thresholds to be more permissive; lower them to be stricter.",
|
||||
"thresholdHighShort": "High",
|
||||
"thresholdHighExample": "e.g. 0.7",
|
||||
"thresholdMediumShort": "Medium",
|
||||
"thresholdMediumExample": "e.g. 0.45",
|
||||
"thresholdLowShort": "Low",
|
||||
"thresholdLowExample": "e.g. 0.3"
|
||||
},
|
||||
"contentCategoryConfiguration": {
|
||||
"title": "Blocked topics",
|
||||
"subtitle": "Select topics to block using keyword and semantic analysis",
|
||||
"colCategory": "Category",
|
||||
"colAction": "Action",
|
||||
"colSeverityThreshold": "Severity Threshold",
|
||||
"severityLow": "Low",
|
||||
"severityMedium": "Medium",
|
||||
"severityHigh": "High",
|
||||
"selectPlaceholder": "Select a content category",
|
||||
"loadingContent": "Loading content...",
|
||||
"unableToLoadContent": "Unable to load category content",
|
||||
"viewFileFor": "View {{fileType}} for {{name}}",
|
||||
"contentWillLoad": "Content will load when expanded",
|
||||
"noTopicsSelected": "No blocked topics selected. Add topics to detect and block harmful content."
|
||||
},
|
||||
"contentFilterConfiguration": {
|
||||
"description": "Configure patterns, keywords, and content categories to detect and filter sensitive information in requests and responses.",
|
||||
"patternDetectionTitle": "Pattern Detection",
|
||||
"patternDetectionSubtitle": "Detect sensitive information using regex patterns (SSN, credit cards, API keys, etc.)",
|
||||
"addPrebuiltPattern": "Add prebuilt pattern",
|
||||
"addCustomRegex": "Add custom regex",
|
||||
"blockedKeywordsTitle": "Blocked Keywords",
|
||||
"blockedKeywordsSubtitle": "Block or mask specific sensitive terms and phrases",
|
||||
"addKeyword": "Add keyword",
|
||||
"uploadYamlFile": "Upload YAML file",
|
||||
"pleaseSelectPattern": "Please select a pattern",
|
||||
"pleaseProvidePatternNameAndRegex": "Please provide pattern name and regex",
|
||||
"pleaseEnterKeyword": "Please enter a keyword",
|
||||
"fileUploadedSuccessfully": "File uploaded successfully",
|
||||
"invalidFile": "Invalid file",
|
||||
"validationFailed": "Validation failed: {{error}}",
|
||||
"failedToUploadFile": "Failed to upload file: {{error}}"
|
||||
},
|
||||
"contentFilterDisplay": {
|
||||
"contentCategories": "Content Categories",
|
||||
"categoriesConfigured": "{{count}} categories configured",
|
||||
"patternDetection": "Pattern Detection",
|
||||
"patternsConfigured": "{{count}} patterns configured",
|
||||
"blockedKeywords": "Blocked Keywords",
|
||||
"keywordsConfigured": "{{count}} keywords configured"
|
||||
},
|
||||
"contentFilterManager": {
|
||||
"dividerTitle": "Content Filter Configuration",
|
||||
"unsavedChangesWarning": "You have unsaved changes to patterns or keywords. Remember to click \"Save Changes\" at the bottom."
|
||||
},
|
||||
"customPatternModal": {
|
||||
"title": "Add custom regex pattern",
|
||||
"patternNameLabel": "Pattern name",
|
||||
"patternNamePlaceholder": "e.g., internal_id, employee_code",
|
||||
"regexPatternLabel": "Regex pattern",
|
||||
"regexPatternPlaceholder": "e.g., ID-[0-9]{6}",
|
||||
"regexPatternHelp": "Enter a valid regular expression to match sensitive data",
|
||||
"actionLabel": "Action",
|
||||
"actionHelp": "Choose what action the guardrail should take when this pattern is detected",
|
||||
"actionBlock": "Block",
|
||||
"actionMask": "Mask"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2607,6 +2607,113 @@
|
|||
"errorTitle": "{{name}} - 错误",
|
||||
"resultCopied": "结果已复制到剪贴板",
|
||||
"failedToCopyResult": "复制结果失败"
|
||||
},
|
||||
"categoryTable": {
|
||||
"colCategory": "分类",
|
||||
"colSeverityThreshold": "严重程度阈值",
|
||||
"colAction": "操作",
|
||||
"severityHigh": "高",
|
||||
"severityMedium": "中",
|
||||
"severityLow": "低",
|
||||
"actionBlock": "拦截",
|
||||
"actionMask": "遮蔽",
|
||||
"noCategories": "未配置任何分类。"
|
||||
},
|
||||
"competitorIntentConfiguration": {
|
||||
"title": "竞争对手意图过滤器",
|
||||
"disabledDescription": "拦截或重构竞争对手比较问题。启用后,航空公司类型会自动从 IATA 加载竞争对手;通用类型需手动指定竞争对手列表。",
|
||||
"enabledDescription": "拦截或重构竞争对手比较问题。航空公司类型使用主要航空公司(排除您的品牌);通用类型需手动指定竞争对手列表。",
|
||||
"typeAirline": "航空公司(自动从 IATA 加载竞争对手)",
|
||||
"typeGeneric": "通用(手动指定竞争对手)",
|
||||
"brandSelfLabel": "您的品牌(brand_self)",
|
||||
"brandSelfHelpAirline": "从列表中选择您的航空公司(已排除在竞争对手之外),或输入自定义词语",
|
||||
"brandSelfHelpGeneric": "用户用来指代您品牌的名称或代码",
|
||||
"loadingAirlines": "正在加载航空公司...",
|
||||
"brandSelfPlaceholderAirline": "搜索或选择航空公司,或输入自定义内容",
|
||||
"brandSelfPlaceholderGeneric": "输入后按 Enter 添加",
|
||||
"locationsLabel": "地点(可选)",
|
||||
"locationsHelp": "用于消歧的国家、城市、机场(如:qatar、doha)",
|
||||
"competitorsLabel": "竞争对手",
|
||||
"competitorsHelp": "要检测的竞争对手名称(通用类型必填)",
|
||||
"tagsPlaceholder": "输入后按 Enter 添加",
|
||||
"policyCompetitorLabel": "策略:竞争对手比较",
|
||||
"policyPossibleCompetitorLabel": "策略:可能的竞争对手比较",
|
||||
"policyRefuse": "拒绝(拦截请求)",
|
||||
"policyReframe": "重构(建议替代方案)",
|
||||
"policyReframeBackend": "重构(向后端 LLM 建议替代方案)",
|
||||
"confidenceThresholdsLabel": "置信度阈值",
|
||||
"confidenceThresholdsHelp": "按置信度(0–1)对竞争对手意图分类。置信度越高,意图越强。",
|
||||
"thresholdHighLabel": "高(≥)",
|
||||
"thresholdHighDesc": "视为完整的竞争对手比较 → 使用「竞争对手比较」策略",
|
||||
"thresholdMediumLabel": "中(≥)",
|
||||
"thresholdMediumDesc": "视为可能的比较 → 使用「可能的竞争对手比较」策略",
|
||||
"thresholdLowLabel": "低(≥)",
|
||||
"thresholdLowDesc": "仅记录日志,允许请求。低于低阈值 → 无操作直接允许",
|
||||
"thresholdsNote": "提高阈值以放宽限制;降低阈值以收紧限制。",
|
||||
"thresholdHighShort": "高",
|
||||
"thresholdHighExample": "例如:0.7",
|
||||
"thresholdMediumShort": "中",
|
||||
"thresholdMediumExample": "例如:0.45",
|
||||
"thresholdLowShort": "低",
|
||||
"thresholdLowExample": "例如:0.3"
|
||||
},
|
||||
"contentCategoryConfiguration": {
|
||||
"title": "封锁主题",
|
||||
"subtitle": "使用关键词和语义分析选择要封锁的主题",
|
||||
"colCategory": "分类",
|
||||
"colAction": "操作",
|
||||
"colSeverityThreshold": "严重程度阈值",
|
||||
"severityLow": "低",
|
||||
"severityMedium": "中",
|
||||
"severityHigh": "高",
|
||||
"selectPlaceholder": "选择内容分类",
|
||||
"loadingContent": "正在加载内容...",
|
||||
"unableToLoadContent": "无法加载分类内容",
|
||||
"viewFileFor": "查看 {{fileType}} 文件:{{name}}",
|
||||
"contentWillLoad": "展开后将加载内容",
|
||||
"noTopicsSelected": "未选择封锁主题。添加主题以检测和拦截有害内容。"
|
||||
},
|
||||
"contentFilterConfiguration": {
|
||||
"description": "配置模式、关键词和内容分类,以检测和过滤请求及响应中的敏感信息。",
|
||||
"patternDetectionTitle": "模式检测",
|
||||
"patternDetectionSubtitle": "使用正则表达式模式检测敏感信息(身份证号、信用卡、API 密钥等)",
|
||||
"addPrebuiltPattern": "添加预置模式",
|
||||
"addCustomRegex": "添加自定义正则",
|
||||
"blockedKeywordsTitle": "封锁关键词",
|
||||
"blockedKeywordsSubtitle": "拦截或遮蔽特定敏感词语和短语",
|
||||
"addKeyword": "添加关键词",
|
||||
"uploadYamlFile": "上传 YAML 文件",
|
||||
"pleaseSelectPattern": "请选择一个模式",
|
||||
"pleaseProvidePatternNameAndRegex": "请提供模式名称和正则表达式",
|
||||
"pleaseEnterKeyword": "请输入关键词",
|
||||
"fileUploadedSuccessfully": "文件上传成功",
|
||||
"invalidFile": "无效文件",
|
||||
"validationFailed": "验证失败:{{error}}",
|
||||
"failedToUploadFile": "上传文件失败:{{error}}"
|
||||
},
|
||||
"contentFilterDisplay": {
|
||||
"contentCategories": "内容分类",
|
||||
"categoriesConfigured": "已配置 {{count}} 个分类",
|
||||
"patternDetection": "模式检测",
|
||||
"patternsConfigured": "已配置 {{count}} 个模式",
|
||||
"blockedKeywords": "封锁关键词",
|
||||
"keywordsConfigured": "已配置 {{count}} 个关键词"
|
||||
},
|
||||
"contentFilterManager": {
|
||||
"dividerTitle": "内容过滤器配置",
|
||||
"unsavedChangesWarning": "模式或关键词有未保存的更改,请记得点击底部的「保存更改」。"
|
||||
},
|
||||
"customPatternModal": {
|
||||
"title": "添加自定义正则模式",
|
||||
"patternNameLabel": "模式名称",
|
||||
"patternNamePlaceholder": "例如:internal_id、employee_code",
|
||||
"regexPatternLabel": "正则表达式",
|
||||
"regexPatternPlaceholder": "例如:ID-[0-9]{6}",
|
||||
"regexPatternHelp": "输入有效的正则表达式以匹配敏感数据",
|
||||
"actionLabel": "操作",
|
||||
"actionHelp": "选择当检测到此模式时护栏应执行的操作",
|
||||
"actionBlock": "拦截",
|
||||
"actionMask": "遮蔽"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue