diff --git a/litellm-proxy-extras/litellm_proxy_extras/migrations/20260214124140_baseline_diff/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260214124140_baseline_diff/migration.sql new file mode 100644 index 00000000000..2f725d83806 --- /dev/null +++ b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260214124140_baseline_diff/migration.sql @@ -0,0 +1,2 @@ +-- This is an empty migration. + diff --git a/ui/litellm-dashboard/src/components/guardrails/content_filter/CategoryTable.tsx b/ui/litellm-dashboard/src/components/guardrails/content_filter/CategoryTable.tsx new file mode 100644 index 00000000000..444be689f85 --- /dev/null +++ b/ui/litellm-dashboard/src/components/guardrails/content_filter/CategoryTable.tsx @@ -0,0 +1,147 @@ +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 ContentCategory { + id: string; + category: string; + display_name: string; + action: "BLOCK" | "MASK"; + severity_threshold: "high" | "medium" | "low"; +} + +interface CategoryTableProps { + categories: ContentCategory[]; + onActionChange?: (id: string, action: "BLOCK" | "MASK") => void; + onSeverityChange?: (id: string, severity: "high" | "medium" | "low") => void; + onRemove?: (id: string) => void; + readOnly?: boolean; +} + +const CategoryTable: React.FC = ({ + categories, + onActionChange, + onSeverityChange, + onRemove, + readOnly = false, +}) => { + const columns = [ + { + title: "Category", + dataIndex: "display_name", + key: "display_name", + render: (displayName: string, record: ContentCategory) => ( +
+ {displayName} + {displayName !== record.category && ( +
+ + {record.category} + +
+ )} +
+ ), + }, + { + title: "Severity Threshold", + dataIndex: "severity_threshold", + key: "severity_threshold", + width: 180, + render: (severity: string, record: ContentCategory) => { + if (readOnly) { + const colorMap = { + high: "red", + medium: "orange", + low: "yellow", + } as const; + return ( + + {severity.toUpperCase()} + + ); + } + return ( + + ); + }, + }, + { + title: "Action", + dataIndex: "action", + key: "action", + width: 150, + render: (action: string, record: ContentCategory) => { + if (readOnly) { + return ( + + {action} + + ); + } + return ( + + ); + }, + }, + ]; + + if (!readOnly) { + columns.push({ + title: "", + key: "actions", + width: 100, + render: (_: any, record: ContentCategory) => ( + + ), + } as any); + } + + if (categories.length === 0) { + return ( +
+ No categories configured. +
+ ); + } + + return ( + + ); +}; + +export default CategoryTable; 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 0c1e12d8860..db7345fa361 100644 --- a/ui/litellm-dashboard/src/components/guardrails/content_filter/ContentFilterDisplay.tsx +++ b/ui/litellm-dashboard/src/components/guardrails/content_filter/ContentFilterDisplay.tsx @@ -2,6 +2,7 @@ import React from "react"; import { Card, Text, Badge } from "@tremor/react"; import PatternTable from "./PatternTable"; import KeywordTable from "./KeywordTable"; +import CategoryTable from "./CategoryTable"; interface Pattern { id: string; @@ -19,26 +20,42 @@ interface BlockedWord { description?: string; } +interface ContentCategory { + id: string; + category: string; + display_name: string; + action: "BLOCK" | "MASK"; + severity_threshold: "high" | "medium" | "low"; +} + interface ContentFilterDisplayProps { patterns: Pattern[]; blockedWords: BlockedWord[]; + categories?: ContentCategory[]; readOnly?: boolean; onPatternActionChange?: (id: string, action: "BLOCK" | "MASK") => void; onPatternRemove?: (id: string) => void; 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; + onCategoryRemove?: (id: string) => void; } const ContentFilterDisplay: React.FC = ({ patterns, blockedWords, + categories = [], readOnly = true, onPatternActionChange, onPatternRemove, onBlockedWordUpdate, onBlockedWordRemove, + onCategoryActionChange, + onCategorySeverityChange, + onCategoryRemove, }) => { - if (patterns.length === 0 && blockedWords.length === 0) { + if (patterns.length === 0 && blockedWords.length === 0 && categories.length === 0) { return null; } @@ -47,6 +64,22 @@ const ContentFilterDisplay: React.FC = ({ return ( <> + {categories.length > 0 && ( + +
+ Content Categories + {categories.length} categories configured +
+ +
+ )} + {patterns.length > 0 && (
diff --git a/ui/litellm-dashboard/src/components/guardrails/content_filter/ContentFilterManager.tsx b/ui/litellm-dashboard/src/components/guardrails/content_filter/ContentFilterManager.tsx index aa23c0e1db0..1070453425b 100644 --- a/ui/litellm-dashboard/src/components/guardrails/content_filter/ContentFilterManager.tsx +++ b/ui/litellm-dashboard/src/components/guardrails/content_filter/ContentFilterManager.tsx @@ -158,7 +158,14 @@ const ContentFilterManager: React.FC = ({ // Read-only display mode if (!isEditing) { - return ; + return ( + + ); } // Edit mode