From 80a2c25e5acb61e9a893ff9c12cd1a7b08359a1b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 23 Apr 2026 12:06:27 +0000 Subject: [PATCH] feat(ui): guardrails/content_filter \u2014 PatternTable + PatternModal + ContentFilterDisplay - PatternTable: antd Table/Select/Tag/Typography/Button + @ant-design DeleteOutlined \u2192 shadcn Table/Select/Badge/Button + lucide Trash2. Categorical blue (prebuilt) / emerald (custom) type badges added to overrides. - PatternModal: antd Modal/Select.OptGroup/Space/Button \u2192 shadcn Dialog/Select (with SelectGroup/SelectLabel for categories) + Button. - ContentFilterDisplay: @tremor Card/Text/Badge \u2192 shadcn Card/Badge + plain span. Blue category counter badge preserved and file added to overrides. Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang --- ui/litellm-dashboard/.eslintrc.json | 2 + .../content_filter/ContentFilterDisplay.tsx | 49 ++++-- .../content_filter/PatternModal.tsx | 152 +++++++++-------- .../content_filter/PatternTable.tsx | 159 ++++++++++-------- 4 files changed, 201 insertions(+), 161 deletions(-) diff --git a/ui/litellm-dashboard/.eslintrc.json b/ui/litellm-dashboard/.eslintrc.json index 22c897e5f7f..27cba563d6f 100644 --- a/ui/litellm-dashboard/.eslintrc.json +++ b/ui/litellm-dashboard/.eslintrc.json @@ -56,6 +56,8 @@ "src/components/permissions/MCPServerPermissions.tsx", "src/components/survey/ClaudeCodeModal.tsx", "src/components/view_logs/ToolsSection/ToolItem.tsx", + "src/components/guardrails/content_filter/PatternTable.tsx", + "src/components/guardrails/content_filter/ContentFilterDisplay.tsx", "src/components/policies/impact_preview_alert.tsx", "src/components/policies/impact_popover.tsx", "src/components/DeletedKeysPage/DeletedKeysPage.tsx", diff --git a/ui/litellm-dashboard/src/components/guardrails/content_filter/ContentFilterDisplay.tsx b/ui/litellm-dashboard/src/components/guardrails/content_filter/ContentFilterDisplay.tsx index db7345fa361..69b09141741 100644 --- a/ui/litellm-dashboard/src/components/guardrails/content_filter/ContentFilterDisplay.tsx +++ b/ui/litellm-dashboard/src/components/guardrails/content_filter/ContentFilterDisplay.tsx @@ -1,5 +1,6 @@ import React from "react"; -import { Card, Text, Badge } from "@tremor/react"; +import { Badge } from "@/components/ui/badge"; +import { Card } from "@/components/ui/card"; import PatternTable from "./PatternTable"; import KeywordTable from "./KeywordTable"; import CategoryTable from "./CategoryTable"; @@ -35,10 +36,14 @@ interface ContentFilterDisplayProps { readOnly?: boolean; onPatternActionChange?: (id: string, action: "BLOCK" | "MASK") => void; onPatternRemove?: (id: string) => void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any onBlockedWordUpdate?: (id: string, field: string, value: any) => void; onBlockedWordRemove?: (id: string) => void; onCategoryActionChange?: (id: string, action: "BLOCK" | "MASK") => void; - onCategorySeverityChange?: (id: string, severity: "high" | "medium" | "low") => void; + onCategorySeverityChange?: ( + id: string, + severity: "high" | "medium" | "low", + ) => void; onCategoryRemove?: (id: string) => void; } @@ -55,20 +60,25 @@ const ContentFilterDisplay: React.FC = ({ onCategorySeverityChange, onCategoryRemove, }) => { - if (patterns.length === 0 && blockedWords.length === 0 && categories.length === 0) { + if ( + patterns.length === 0 && + blockedWords.length === 0 && + categories.length === 0 + ) { return null; } - // No-op handlers for read-only mode const noOp = () => {}; return ( <> {categories.length > 0 && ( - +
- Content Categories - {categories.length} categories configured + Content Categories + + {categories.length} categories configured +
= ({ )} {patterns.length > 0 && ( - +
- Pattern Detection - {patterns.length} patterns configured + Pattern Detection + + {patterns.length} patterns configured +
)} {blockedWords.length > 0 && ( - +
- Blocked Keywords - {blockedWords.length} keywords configured + Blocked Keywords + + {blockedWords.length} keywords configured +
)} @@ -112,4 +126,3 @@ const ContentFilterDisplay: React.FC = ({ }; export default ContentFilterDisplay; - diff --git a/ui/litellm-dashboard/src/components/guardrails/content_filter/PatternModal.tsx b/ui/litellm-dashboard/src/components/guardrails/content_filter/PatternModal.tsx index 651cee44d85..d8c1579e743 100644 --- a/ui/litellm-dashboard/src/components/guardrails/content_filter/PatternModal.tsx +++ b/ui/litellm-dashboard/src/components/guardrails/content_filter/PatternModal.tsx @@ -1,8 +1,22 @@ import React from "react"; -import { Typography, Select, Modal, Space, Button } from "antd"; - -const { Text } = Typography; -const { Option } = Select; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; interface PrebuiltPattern { name: string; @@ -35,77 +49,75 @@ const PatternModal: React.FC = ({ onCancel, }) => { return ( - (!o ? onCancel() : undefined)} > - -
- Pattern type - -
+ + + Add prebuilt pattern + +
+
+ + - - - -
- + return ( + + {category} + {categoryPatterns.map((pattern) => ( + + {pattern.display_name} + + ))} + + ); + })} + + +
-
- - -
-
+
+ +

+ Choose what action the guardrail should take when this pattern is + detected +

+ +
+ + + + + + + ); }; export default PatternModal; - diff --git a/ui/litellm-dashboard/src/components/guardrails/content_filter/PatternTable.tsx b/ui/litellm-dashboard/src/components/guardrails/content_filter/PatternTable.tsx index 7cffe1e711d..0455be9ac45 100644 --- a/ui/litellm-dashboard/src/components/guardrails/content_filter/PatternTable.tsx +++ b/ui/litellm-dashboard/src/components/guardrails/content_filter/PatternTable.tsx @@ -1,9 +1,22 @@ +import { Trash2 } from "lucide-react"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; import React from "react"; -import { Typography, Select, Table, Tag, Button } from "antd"; -import { DeleteOutlined } from "@ant-design/icons"; - -const { Text } = Typography; -const { Option } = Select; interface Pattern { id: string; @@ -25,83 +38,83 @@ const PatternTable: React.FC = ({ onActionChange, onRemove, }) => { - const columns = [ - { - title: "Type", - dataIndex: "type", - key: "type", - width: 100, - render: (type: string) => ( - - {type === "prebuilt" ? "Prebuilt" : "Custom"} - - ), - }, - { - title: "Pattern name", - dataIndex: "name", - key: "name", - render: (_: string, record: Pattern) => record.display_name || record.name, - }, - { - title: "Regex pattern", - dataIndex: "pattern", - key: "pattern", - render: (pattern: string) => (pattern ? {pattern.substring(0, 40)}... : "-"), - }, - { - title: "Action", - dataIndex: "action", - key: "action", - width: 150, - render: (action: string, record: Pattern) => ( - - ), - }, - { - title: "", - key: "actions", - width: 100, - render: (_: any, record: Pattern) => ( - - ), - }, - ]; - if (patterns.length === 0) { return ( -
+
No patterns added.
); } return ( - +
+
+ + + Type + Pattern name + Regex pattern + Action + + + + + {patterns.map((record) => ( + + + + {record.type === "prebuilt" ? "Prebuilt" : "Custom"} + + + {record.display_name || record.name} + + {record.pattern ? ( + + {record.pattern.substring(0, 40)}... + + ) : ( + "-" + )} + + + + + + + + + ))} + +
+
); }; export default PatternTable; -