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:
Cursor Agent 2026-04-23 21:27:15 +00:00
parent 3735048454
commit 01ed0aca93
No known key found for this signature in database

View file

@ -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 &quot;Use Template&quot; 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 &quot;Use
Template&quot; 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>
);
};