mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-03 02:22:24 +00:00
feat(ui): policies/guardrail_selection_modal to shadcn
- antd Modal/Button/Checkbox/Divider/Tag + antd icons (CheckCircleOutlined, InfoCircleOutlined) \u2192 shadcn Dialog/Button/ Checkbox/Badge + lucide CheckCircle2/Info/Loader2. - Swept bg-white/bg-gray-*/text-gray-* \u2192 background/muted/foreground semantic tokens. Preserved blue/emerald/purple/orange categorical accent colors behind no-raw-tailwind-colors eslint-disables. Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
3735048454
commit
01ed0aca93
1 changed files with 232 additions and 179 deletions
|
|
@ -1,18 +1,32 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { Modal, Checkbox, Button, Divider, Tag } from "antd";
|
||||
import { CheckCircleOutlined, InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
} from "@/components/ui/dialog";
|
||||
import { CheckCircle2, Info, Loader2 } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface GuardrailInfo {
|
||||
guardrail_name: string;
|
||||
description: string;
|
||||
alreadyExists: boolean;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
definition: any;
|
||||
}
|
||||
|
||||
interface GuardrailSelectionModalProps {
|
||||
visible: boolean;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
template: any;
|
||||
existingGuardrails: Set<string>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
onConfirm: (selectedGuardrails: any[]) => void;
|
||||
onCancel: () => void;
|
||||
isLoading?: boolean;
|
||||
|
|
@ -29,12 +43,12 @@ const GuardrailSelectionModal: React.FC<GuardrailSelectionModalProps> = ({
|
|||
progressInfo,
|
||||
}) => {
|
||||
const [selectedGuardrails, setSelectedGuardrails] = useState<Set<string>>(
|
||||
new Set()
|
||||
new Set(),
|
||||
);
|
||||
|
||||
// Prepare guardrail info with existence status
|
||||
const guardrailsInfo: GuardrailInfo[] = (
|
||||
template?.guardrailDefinitions || []
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
).map((def: any) => ({
|
||||
guardrail_name: def.guardrail_name,
|
||||
description: def.guardrail_info?.description || "No description available",
|
||||
|
|
@ -42,7 +56,6 @@ const GuardrailSelectionModal: React.FC<GuardrailSelectionModalProps> = ({
|
|||
definition: def,
|
||||
}));
|
||||
|
||||
// Initialize selection: select only new guardrails by default
|
||||
useEffect(() => {
|
||||
if (visible && template) {
|
||||
const newGuardrails = guardrailsInfo
|
||||
|
|
@ -50,6 +63,7 @@ const GuardrailSelectionModal: React.FC<GuardrailSelectionModalProps> = ({
|
|||
.map((g) => g.guardrail_name);
|
||||
setSelectedGuardrails(new Set(newGuardrails));
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [visible, template]);
|
||||
|
||||
const handleToggle = (guardrailName: string) => {
|
||||
|
|
@ -83,200 +97,239 @@ const GuardrailSelectionModal: React.FC<GuardrailSelectionModalProps> = ({
|
|||
};
|
||||
|
||||
const newGuardrailsCount = guardrailsInfo.filter(
|
||||
(g) => !g.alreadyExists
|
||||
(g) => !g.alreadyExists,
|
||||
).length;
|
||||
const existingCount = guardrailsInfo.filter((g) => g.alreadyExists).length;
|
||||
const selectedCount = selectedGuardrails.size;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={
|
||||
<div>
|
||||
<Dialog open={visible} onOpenChange={(o) => (!o ? onCancel() : undefined)}>
|
||||
<DialogContent className="max-w-[700px]">
|
||||
<DialogHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="text-lg font-semibold mb-0">{template?.title}</h3>
|
||||
<DialogTitle>{template?.title}</DialogTitle>
|
||||
{progressInfo && (
|
||||
<span className="px-2 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-600 border border-blue-100">
|
||||
// eslint-disable-next-line litellm-ui/no-raw-tailwind-colors
|
||||
<span className="px-2 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-600 border border-blue-100 dark:bg-blue-950/30 dark:text-blue-200 dark:border-blue-900">
|
||||
Template {progressInfo.current} of {progressInfo.total}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 font-normal mt-1">
|
||||
<DialogDescription>
|
||||
Review and select guardrails to create for this template
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
open={visible}
|
||||
onCancel={onCancel}
|
||||
width={700}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={onCancel} disabled={isLoading}>
|
||||
Cancel
|
||||
</Button>,
|
||||
<Button
|
||||
key="confirm"
|
||||
type="primary"
|
||||
onClick={handleConfirm}
|
||||
loading={isLoading}
|
||||
disabled={selectedCount === 0 && existingCount === 0}
|
||||
>
|
||||
{selectedCount > 0
|
||||
? `Create ${selectedCount} Guardrail${selectedCount > 1 ? "s" : ""} & Use Template`
|
||||
: "Use Template"}
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<div className="py-4">
|
||||
{/* Summary Stats */}
|
||||
<div className="flex items-center gap-4 mb-4 p-3 bg-blue-50 rounded-lg border border-blue-100">
|
||||
<InfoCircleOutlined className="text-blue-600 text-lg" />
|
||||
<div className="flex-1">
|
||||
<div className="text-sm">
|
||||
<span className="font-medium text-gray-900">
|
||||
{guardrailsInfo.length} total guardrails
|
||||
</span>
|
||||
<span className="text-gray-600 mx-2">•</span>
|
||||
<span className="text-green-600 font-medium">
|
||||
{newGuardrailsCount} new
|
||||
</span>
|
||||
{existingCount > 0 && (
|
||||
<>
|
||||
<span className="text-gray-600 mx-2">•</span>
|
||||
<span className="text-gray-600">
|
||||
{existingCount} already exist
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{newGuardrailsCount > 0 && (
|
||||
<div className="flex gap-2">
|
||||
<Button size="small" onClick={handleSelectAll}>
|
||||
Select All New
|
||||
</Button>
|
||||
<Button size="small" onClick={handleDeselectAll}>
|
||||
Deselect All
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{/* Guardrails List */}
|
||||
<div className="space-y-3 max-h-96 overflow-y-auto">
|
||||
{guardrailsInfo.map((guardrail) => (
|
||||
<div
|
||||
key={guardrail.guardrail_name}
|
||||
className={`border rounded-lg p-4 ${
|
||||
guardrail.alreadyExists
|
||||
? "bg-gray-50 border-gray-200"
|
||||
: "bg-white border-gray-300 hover:border-blue-400"
|
||||
} transition-colors`}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex-shrink-0 pt-0.5">
|
||||
{guardrail.alreadyExists ? (
|
||||
<CheckCircleOutlined className="text-green-600 text-lg" />
|
||||
) : (
|
||||
<Checkbox
|
||||
checked={selectedGuardrails.has(guardrail.guardrail_name)}
|
||||
onChange={() => handleToggle(guardrail.guardrail_name)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="font-mono text-sm font-medium text-gray-900">
|
||||
{guardrail.guardrail_name}
|
||||
</span>
|
||||
{guardrail.alreadyExists && (
|
||||
<Tag color="green" className="text-xs">
|
||||
Already exists
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-gray-600">
|
||||
{guardrail.description}
|
||||
</p>
|
||||
|
||||
{/* Show guardrail type and mode */}
|
||||
<div className="flex gap-2 mt-2">
|
||||
<Tag className="text-xs">
|
||||
{guardrail.definition?.litellm_params?.guardrail || "unknown"}
|
||||
</Tag>
|
||||
<Tag className="text-xs" color="blue">
|
||||
{guardrail.definition?.litellm_params?.mode || "unknown"}
|
||||
</Tag>
|
||||
{guardrail.definition?.litellm_params?.patterns && (
|
||||
<Tag className="text-xs" color="purple">
|
||||
{guardrail.definition.litellm_params.patterns.length} pattern(s)
|
||||
</Tag>
|
||||
)}
|
||||
{guardrail.definition?.litellm_params?.categories && (
|
||||
<Tag className="text-xs" color="orange">
|
||||
{guardrail.definition.litellm_params.categories.length} category/categories
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{guardrailsInfo.length === 0 && (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
<p>No guardrails defined for this template.</p>
|
||||
<p className="text-sm mt-2">
|
||||
This template will use existing guardrails in your system.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Discovered Competitors */}
|
||||
{template?.discoveredCompetitors?.length > 0 && (
|
||||
<>
|
||||
<Divider />
|
||||
<div className="p-3 bg-purple-50 rounded-lg border border-purple-100">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-lg">✨</span>
|
||||
<span className="font-medium text-purple-900 text-sm">
|
||||
AI-Discovered Competitors ({template.discoveredCompetitors.length})
|
||||
<div className="py-2">
|
||||
{/* Summary Stats */}
|
||||
{/* eslint-disable-next-line litellm-ui/no-raw-tailwind-colors */}
|
||||
<div className="flex items-center gap-4 mb-4 p-3 bg-blue-50 rounded-lg border border-blue-100 dark:bg-blue-950/30 dark:border-blue-900">
|
||||
{/* eslint-disable-next-line litellm-ui/no-raw-tailwind-colors */}
|
||||
<Info className="h-5 w-5 text-blue-600 dark:text-blue-300 shrink-0" />
|
||||
<div className="flex-1">
|
||||
<div className="text-sm">
|
||||
<span className="font-medium">
|
||||
{guardrailsInfo.length} total guardrails
|
||||
</span>
|
||||
<span className="text-muted-foreground mx-2">•</span>
|
||||
{/* eslint-disable-next-line litellm-ui/no-raw-tailwind-colors */}
|
||||
<span className="text-emerald-600 font-medium">
|
||||
{newGuardrailsCount} new
|
||||
</span>
|
||||
{existingCount > 0 && (
|
||||
<>
|
||||
<span className="text-muted-foreground mx-2">•</span>
|
||||
<span className="text-muted-foreground">
|
||||
{existingCount} already exist
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{template.discoveredCompetitors.map((name: string) => (
|
||||
<Tag key={name} color="purple" className="text-xs">
|
||||
{name}
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
{newGuardrailsCount > 0 && (
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleSelectAll}
|
||||
>
|
||||
Select All New
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleDeselectAll}
|
||||
>
|
||||
Deselect All
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-purple-600 mt-2">
|
||||
These competitor names will be automatically blocked by the competitor-name-blocker guardrail.
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Guardrails List */}
|
||||
<div className="space-y-3 max-h-96 overflow-y-auto">
|
||||
{guardrailsInfo.map((guardrail) => (
|
||||
<div
|
||||
key={guardrail.guardrail_name}
|
||||
className={cn(
|
||||
"border rounded-lg p-4 transition-colors",
|
||||
guardrail.alreadyExists
|
||||
? "bg-muted border-border"
|
||||
: "bg-background border-border hover:border-primary/50",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="shrink-0 pt-0.5">
|
||||
{guardrail.alreadyExists ? (
|
||||
// eslint-disable-next-line litellm-ui/no-raw-tailwind-colors
|
||||
<CheckCircle2 className="h-5 w-5 text-emerald-600" />
|
||||
) : (
|
||||
<Checkbox
|
||||
checked={selectedGuardrails.has(
|
||||
guardrail.guardrail_name,
|
||||
)}
|
||||
onCheckedChange={() =>
|
||||
handleToggle(guardrail.guardrail_name)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="font-mono text-sm font-medium">
|
||||
{guardrail.guardrail_name}
|
||||
</span>
|
||||
{guardrail.alreadyExists && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
Already exists
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{guardrail.description}
|
||||
</p>
|
||||
|
||||
{/* Show guardrail type and mode */}
|
||||
<div className="flex gap-2 mt-2 flex-wrap">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{guardrail.definition?.litellm_params?.guardrail ||
|
||||
"unknown"}
|
||||
</Badge>
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{guardrail.definition?.litellm_params?.mode ||
|
||||
"unknown"}
|
||||
</Badge>
|
||||
{guardrail.definition?.litellm_params?.patterns && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{
|
||||
guardrail.definition.litellm_params.patterns
|
||||
.length
|
||||
}{" "}
|
||||
pattern(s)
|
||||
</Badge>
|
||||
)}
|
||||
{guardrail.definition?.litellm_params?.categories && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{
|
||||
guardrail.definition.litellm_params.categories
|
||||
.length
|
||||
}{" "}
|
||||
category/categories
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{guardrailsInfo.length === 0 && (
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
<p>No guardrails defined for this template.</p>
|
||||
<p className="text-sm mt-2">
|
||||
This template will use existing guardrails in your system.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* Selected Summary */}
|
||||
<div className="text-sm text-gray-600">
|
||||
{selectedCount > 0 ? (
|
||||
<p>
|
||||
<span className="font-medium text-gray-900">{selectedCount}</span>{" "}
|
||||
guardrail{selectedCount > 1 ? "s" : ""} will be created
|
||||
</p>
|
||||
) : existingCount > 0 ? (
|
||||
<p className="text-green-600">
|
||||
All guardrails already exist. You can proceed to use this template.
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-orange-600">
|
||||
Select at least one guardrail to create, or click "Use Template" to proceed without creating new guardrails.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Discovered Competitors */}
|
||||
{template?.discoveredCompetitors?.length > 0 && (
|
||||
<>
|
||||
<hr className="border-border my-4" />
|
||||
{/* eslint-disable-next-line litellm-ui/no-raw-tailwind-colors */}
|
||||
<div className="p-3 bg-purple-50 rounded-lg border border-purple-100 dark:bg-purple-950/30 dark:border-purple-900">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-lg">✨</span>
|
||||
{/* eslint-disable-next-line litellm-ui/no-raw-tailwind-colors */}
|
||||
<span className="font-medium text-purple-900 dark:text-purple-200 text-sm">
|
||||
AI-Discovered Competitors (
|
||||
{template.discoveredCompetitors.length})
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{template.discoveredCompetitors.map((name: string) => (
|
||||
<Badge key={name} variant="secondary" className="text-xs">
|
||||
{name}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
{/* eslint-disable-next-line litellm-ui/no-raw-tailwind-colors */}
|
||||
<p className="text-xs text-purple-600 dark:text-purple-300 mt-2">
|
||||
These competitor names will be automatically blocked by the
|
||||
competitor-name-blocker guardrail.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<hr className="border-border my-4" />
|
||||
|
||||
{/* Selected Summary */}
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{selectedCount > 0 ? (
|
||||
<p>
|
||||
<span className="font-medium text-foreground">
|
||||
{selectedCount}
|
||||
</span>{" "}
|
||||
guardrail{selectedCount > 1 ? "s" : ""} will be created
|
||||
</p>
|
||||
) : existingCount > 0 ? (
|
||||
// eslint-disable-next-line litellm-ui/no-raw-tailwind-colors
|
||||
<p className="text-emerald-600">
|
||||
All guardrails already exist. You can proceed to use this
|
||||
template.
|
||||
</p>
|
||||
) : (
|
||||
// eslint-disable-next-line litellm-ui/no-raw-tailwind-colors
|
||||
<p className="text-orange-600">
|
||||
Select at least one guardrail to create, or click "Use
|
||||
Template" to proceed without creating new guardrails.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={onCancel} disabled={isLoading}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleConfirm}
|
||||
disabled={
|
||||
isLoading || (selectedCount === 0 && existingCount === 0)
|
||||
}
|
||||
>
|
||||
{isLoading && <Loader2 className="h-4 w-4 animate-spin" />}
|
||||
{selectedCount > 0
|
||||
? `Create ${selectedCount} Guardrail${
|
||||
selectedCount > 1 ? "s" : ""
|
||||
} & Use Template`
|
||||
: "Use Template"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue