mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): guardrails/pii_components to shadcn
- antd Typography/Select/Button/Checkbox/Tooltip/Tag + @ant-design Close/EyeInvisible/Stop/Filter \u2192 shadcn Badge/Button/Checkbox/ Select/Tooltip + lucide Ban/EyeOff/Filter/Info/X. - CategoryFilter keeps antd Select mode='multiple' \u2014 shadcn Select doesn't support multiple. Blue category badge in PiiEntityList preserved as categorical (file added to .eslintrc.json overrides). - Quick Action buttons use destructive/primary outline variants for Mask/Block/Unselect respectively. Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
aa0ab33ac7
commit
1f4656bc4c
2 changed files with 133 additions and 92 deletions
|
|
@ -70,6 +70,7 @@
|
|||
"src/components/mcp_tools/mcp_server_cost_config.tsx",
|
||||
"src/components/view_logs/GuardrailViewer/CompliancePanel.tsx",
|
||||
"src/components/view_logs/LogDetailsDrawer/DrawerHeader.tsx",
|
||||
"src/components/guardrails/pii_components.tsx",
|
||||
"src/components/prompts/prompt_editor_view/PromptEditorHeader.tsx",
|
||||
"src/components/prompts/prompt_editor_view/VersionHistorySidePanel.tsx",
|
||||
"src/app/(dashboard)/teams/components/TeamsTable/ModelsCell.tsx",
|
||||
|
|
|
|||
|
|
@ -1,12 +1,27 @@
|
|||
import React from "react";
|
||||
import { Typography, Select, Button, Checkbox, Tooltip, Tag } from "antd";
|
||||
import { CloseOutlined, EyeInvisibleOutlined, StopOutlined, FilterOutlined } from "@ant-design/icons";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Ban, Eye, EyeOff, Filter, Info, X } from "lucide-react";
|
||||
import { Select as AntSelect } from "antd";
|
||||
import { PiiEntityCategory } from "./types";
|
||||
|
||||
const { Text } = Typography;
|
||||
const { Option } = Select;
|
||||
const { Option } = AntSelect;
|
||||
|
||||
// Helper functions
|
||||
export const formatEntityName = (name: string) => {
|
||||
return name.replace(/_/g, " ");
|
||||
};
|
||||
|
|
@ -14,29 +29,35 @@ export const formatEntityName = (name: string) => {
|
|||
export const getActionIcon = (action: string) => {
|
||||
switch (action) {
|
||||
case "MASK":
|
||||
return <EyeInvisibleOutlined style={{ marginRight: 4 }} />;
|
||||
return <EyeOff className="h-3.5 w-3.5 mr-1" />;
|
||||
case "BLOCK":
|
||||
return <StopOutlined style={{ marginRight: 4 }} />;
|
||||
return <Ban className="h-3.5 w-3.5 mr-1" />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// CategoryFilter component
|
||||
// CategoryFilter
|
||||
export interface CategoryFilterProps {
|
||||
categories: PiiEntityCategory[];
|
||||
selectedCategories: string[];
|
||||
onChange: (categories: string[]) => void;
|
||||
}
|
||||
|
||||
export const CategoryFilter: React.FC<CategoryFilterProps> = ({ categories, selectedCategories, onChange }) => {
|
||||
export const CategoryFilter: React.FC<CategoryFilterProps> = ({
|
||||
categories,
|
||||
selectedCategories,
|
||||
onChange,
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center mb-2">
|
||||
<FilterOutlined className="text-gray-500 mr-1" />
|
||||
<Text className="text-gray-500 font-medium">Filter by category</Text>
|
||||
<Filter className="h-3.5 w-3.5 text-muted-foreground mr-1" />
|
||||
<span className="text-muted-foreground font-medium">
|
||||
Filter by category
|
||||
</span>
|
||||
</div>
|
||||
<Select
|
||||
<AntSelect
|
||||
mode="multiple"
|
||||
placeholder="Select categories to filter by"
|
||||
style={{ width: "100%" }}
|
||||
|
|
@ -46,78 +67,80 @@ export const CategoryFilter: React.FC<CategoryFilterProps> = ({ categories, sele
|
|||
showSearch
|
||||
optionFilterProp="children"
|
||||
className="mb-4"
|
||||
tagRender={(props) => (
|
||||
<Tag color="blue" closable={props.closable} onClose={props.onClose} className="mr-2 mb-2">
|
||||
{props.label}
|
||||
</Tag>
|
||||
)}
|
||||
>
|
||||
{categories.map((cat) => (
|
||||
<Option key={cat.category} value={cat.category}>
|
||||
{cat.category}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</AntSelect>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// QuickActions component
|
||||
// QuickActions
|
||||
export interface QuickActionsProps {
|
||||
onSelectAll: (action: string) => void;
|
||||
onUnselectAll: () => void;
|
||||
hasSelectedEntities: boolean;
|
||||
}
|
||||
|
||||
export const QuickActions: React.FC<QuickActionsProps> = ({ onSelectAll, onUnselectAll, hasSelectedEntities }) => {
|
||||
export const QuickActions: React.FC<QuickActionsProps> = ({
|
||||
onSelectAll,
|
||||
onUnselectAll,
|
||||
hasSelectedEntities,
|
||||
}) => {
|
||||
return (
|
||||
<div className="bg-gray-50 p-5 rounded-lg mb-6 border border-gray-200 shadow-sm">
|
||||
<div className="bg-muted p-5 rounded-lg mb-6 border border-border shadow-sm">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center">
|
||||
<Text strong className="text-gray-700 text-base">
|
||||
<span className="font-bold text-foreground text-base">
|
||||
Quick Actions
|
||||
</Text>
|
||||
<Tooltip title="Apply action to all PII types at once">
|
||||
<div className="ml-2 text-gray-400 cursor-help text-xs">ⓘ</div>
|
||||
</Tooltip>
|
||||
</span>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="ml-2 h-3 w-3 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Apply action to all PII types at once
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<Button
|
||||
color="danger"
|
||||
variant="outlined"
|
||||
variant="outline"
|
||||
onClick={onUnselectAll}
|
||||
disabled={!hasSelectedEntities}
|
||||
icon={<CloseOutlined />}
|
||||
className="text-destructive border-destructive/30 hover:bg-destructive/10"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
Unselect All
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Button
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
variant="outline"
|
||||
onClick={() => onSelectAll("MASK")}
|
||||
className="h-10"
|
||||
block
|
||||
icon={<EyeInvisibleOutlined />}
|
||||
className="h-10 text-primary border-primary/30 hover:bg-primary/10 w-full"
|
||||
>
|
||||
Select All & Mask
|
||||
<EyeOff className="h-4 w-4" />
|
||||
Select All & Mask
|
||||
</Button>
|
||||
<Button
|
||||
color="danger"
|
||||
variant="outlined"
|
||||
variant="outline"
|
||||
onClick={() => onSelectAll("BLOCK")}
|
||||
className="h-10 hover:bg-red-100"
|
||||
block
|
||||
icon={<StopOutlined />}
|
||||
className="h-10 text-destructive border-destructive/30 hover:bg-destructive/10 w-full"
|
||||
>
|
||||
Select All & Block
|
||||
<Ban className="h-4 w-4" />
|
||||
Select All & Block
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// PiiEntityList component
|
||||
// PiiEntityList
|
||||
export interface PiiEntityListProps {
|
||||
entities: string[];
|
||||
selectedEntities: string[];
|
||||
|
|
@ -138,62 +161,79 @@ export const PiiEntityList: React.FC<PiiEntityListProps> = ({
|
|||
entityToCategoryMap,
|
||||
}) => {
|
||||
return (
|
||||
<div className="border rounded-lg overflow-hidden shadow-sm">
|
||||
<div className="bg-gray-50 px-5 py-3 border-b flex">
|
||||
<Text strong className="flex-1 text-gray-700">
|
||||
PII Type
|
||||
</Text>
|
||||
<Text strong className="w-32 text-right text-gray-700">
|
||||
Action
|
||||
</Text>
|
||||
<div className="border border-border rounded-lg overflow-hidden shadow-sm">
|
||||
<div className="bg-muted px-5 py-3 border-b border-border flex">
|
||||
<span className="flex-1 text-foreground font-bold">PII Type</span>
|
||||
<span className="w-32 text-right text-foreground font-bold">Action</span>
|
||||
</div>
|
||||
<div className="max-h-[400px] overflow-y-auto">
|
||||
{entities.length === 0 ? (
|
||||
<div className="py-10 text-center text-gray-500">No PII types match your filter criteria</div>
|
||||
<div className="py-10 text-center text-muted-foreground">
|
||||
No PII types match your filter criteria
|
||||
</div>
|
||||
) : (
|
||||
entities.map((entity) => (
|
||||
<div
|
||||
key={entity}
|
||||
className={`px-5 py-3 flex items-center justify-between hover:bg-gray-50 border-b ${
|
||||
selectedEntities.includes(entity) ? "bg-blue-50" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center flex-1">
|
||||
<Checkbox
|
||||
checked={selectedEntities.includes(entity)}
|
||||
onChange={() => onEntitySelect(entity)}
|
||||
className="mr-3"
|
||||
/>
|
||||
<Text className={selectedEntities.includes(entity) ? "font-medium text-gray-900" : "text-gray-700"}>
|
||||
{formatEntityName(entity)}
|
||||
</Text>
|
||||
{entityToCategoryMap.get(entity) && (
|
||||
<Tag className="ml-2 text-xs" color="blue">
|
||||
{entityToCategoryMap.get(entity)}
|
||||
</Tag>
|
||||
entities.map((entity) => {
|
||||
const isSelected = selectedEntities.includes(entity);
|
||||
return (
|
||||
<div
|
||||
key={entity}
|
||||
className={cn(
|
||||
"px-5 py-3 flex items-center justify-between hover:bg-muted border-b border-border",
|
||||
isSelected && "bg-blue-50 dark:bg-blue-950/30",
|
||||
)}
|
||||
>
|
||||
<label className="flex items-center flex-1 cursor-pointer">
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
onCheckedChange={() => onEntitySelect(entity)}
|
||||
className="mr-3"
|
||||
/>
|
||||
<span
|
||||
className={cn(
|
||||
isSelected
|
||||
? "font-medium text-foreground"
|
||||
: "text-foreground/80",
|
||||
)}
|
||||
>
|
||||
{formatEntityName(entity)}
|
||||
</span>
|
||||
{entityToCategoryMap.get(entity) && (
|
||||
<Badge className="ml-2 text-xs bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300">
|
||||
{entityToCategoryMap.get(entity)}
|
||||
</Badge>
|
||||
)}
|
||||
</label>
|
||||
<div className="w-32">
|
||||
<Select
|
||||
value={
|
||||
isSelected ? selectedActions[entity] || "MASK" : "MASK"
|
||||
}
|
||||
onValueChange={(value) => onActionSelect(entity, value)}
|
||||
disabled={!isSelected}
|
||||
>
|
||||
<SelectTrigger
|
||||
className={cn(
|
||||
"w-[120px] h-8",
|
||||
!isSelected && "opacity-50",
|
||||
)}
|
||||
>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{actions.map((action) => (
|
||||
<SelectItem key={action} value={action}>
|
||||
<div className="flex items-center">
|
||||
{getActionIcon(action)}
|
||||
{action}
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-32">
|
||||
<Select
|
||||
value={selectedEntities.includes(entity) ? selectedActions[entity] || "MASK" : "MASK"}
|
||||
onChange={(value) => onActionSelect(entity, value)}
|
||||
style={{ width: 120 }}
|
||||
disabled={!selectedEntities.includes(entity)}
|
||||
className={`${!selectedEntities.includes(entity) ? "opacity-50" : ""}`}
|
||||
dropdownMatchSelectWidth={false}
|
||||
>
|
||||
{actions.map((action) => (
|
||||
<Option key={action} value={action}>
|
||||
<div className="flex items-center">
|
||||
{getActionIcon(action)}
|
||||
{action}
|
||||
</div>
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue