feat(ui): guardrails \u2014 guardrail_garden + pii_configuration + GuardrailsMonitorView

- GuardrailsMonitorView: tremor DateRangePickerValue type-only import
  now has an inline eslint-disable (kept as a shared type in phase 1).
- pii_configuration: antd Typography \u2192 plain <h4>/<span> with
  semantic tokens.
- guardrail_garden: antd Input + @ant-design/icons SearchOutlined /
  ArrowRightOutlined \u2192 shadcn Input with lucide Search/ArrowRight.
  Replaced every inline style attribute with Tailwind utilities and
  semantic tokens (text-foreground/text-muted-foreground/text-primary).
  The only retained inline style is the CSS grid template column
  value which relies on a dynamic auto-fill minmax() that Tailwind
  can't express succinctly.

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 11:08:08 +00:00
parent 8d254dc977
commit b8acfb4f9f
No known key found for this signature in database
3 changed files with 73 additions and 48 deletions

View file

@ -1,3 +1,4 @@
// eslint-disable-next-line litellm-ui/no-banned-ui-imports
import type { DateRangePickerValue } from "@tremor/react";
import React, { useCallback, useMemo, useState } from "react";
import { formatDate } from "@/components/networking";

View file

@ -1,6 +1,6 @@
import React, { useState } from "react";
import { Input } from "antd";
import { SearchOutlined, ArrowRightOutlined } from "@ant-design/icons";
import { Input } from "@/components/ui/input";
import { ArrowRight, Search } from "lucide-react";
import { GuardrailCardInfo, ALL_CARDS } from "./guardrail_garden_data";
import GuardrailCard from "./guardrail_garden_card";
import GuardrailDetailView from "./guardrail_garden_detail";
@ -10,9 +10,14 @@ interface GuardrailGardenProps {
onGuardrailCreated: () => void;
}
const GuardrailGarden: React.FC<GuardrailGardenProps> = ({ accessToken, onGuardrailCreated }) => {
const GuardrailGarden: React.FC<GuardrailGardenProps> = ({
accessToken,
onGuardrailCreated,
}) => {
const [searchQuery, setSearchQuery] = useState("");
const [selectedCard, setSelectedCard] = useState<GuardrailCardInfo | null>(null);
const [selectedCard, setSelectedCard] = useState<GuardrailCardInfo | null>(
null,
);
const [showAllLitellm, setShowAllLitellm] = useState(false);
const CARDS_PER_ROW = 5;
const VISIBLE_ROWS = 2;
@ -43,63 +48,83 @@ const GuardrailGarden: React.FC<GuardrailGardenProps> = ({ accessToken, onGuardr
return (
<div>
{/* Search Bar */}
<div style={{ marginBottom: 24 }}>
<Input
size="large"
placeholder="Search guardrails"
prefix={<SearchOutlined style={{ color: "#9ca3af" }} />}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
style={{ borderRadius: 8 }}
/>
<div className="mb-6">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" />
<Input
placeholder="Search guardrails"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9 h-11 rounded-lg"
/>
</div>
</div>
{/* LiteLLM Content Filter Section */}
<div style={{ marginBottom: 40 }}>
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 4 }}>
<h2 style={{ fontSize: 20, fontWeight: 600, color: "#111827", margin: 0 }}>LiteLLM Content Filter</h2>
<span
style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 14, color: "#1a73e8", cursor: "pointer" }}
<div className="mb-10">
<div className="flex items-center justify-between mb-1">
<h2 className="text-xl font-semibold text-foreground m-0">
LiteLLM Content Filter
</h2>
<button
type="button"
onClick={() => setShowAllLitellm(!showAllLitellm)}
className="inline-flex items-center gap-1.5 text-sm text-primary cursor-pointer bg-transparent border-none"
>
{showAllLitellm ? (
<>Show less</>
) : (
<>
<ArrowRightOutlined style={{ fontSize: 12 }} />
<ArrowRight className="h-3 w-3" />
{`Show all (${litellmCards.length})`}
</>
)}
</span>
</button>
</div>
<p style={{ fontSize: 13, color: "#6b7280", margin: "4px 0 20px 0" }}>
Built-in guardrails powered by LiteLLM. Zero latency, no external dependencies, no additional cost.
<p className="text-sm text-muted-foreground mt-1 mb-5">
Built-in guardrails powered by LiteLLM. Zero latency, no external
dependencies, no additional cost.
</p>
<div style={{
display: "grid",
gridTemplateColumns: "repeat(auto-fill, minmax(220px, 1fr))",
gap: 16,
}}>
{(showAllLitellm ? litellmCards : litellmCards.slice(0, CARDS_PER_ROW * VISIBLE_ROWS)).map((card) => (
<GuardrailCard key={card.id} card={card} onClick={() => setSelectedCard(card)} />
<div
className="grid gap-4"
style={{
gridTemplateColumns:
"repeat(auto-fill, minmax(220px, 1fr))",
}}
>
{(showAllLitellm
? litellmCards
: litellmCards.slice(0, CARDS_PER_ROW * VISIBLE_ROWS)
).map((card) => (
<GuardrailCard
key={card.id}
card={card}
onClick={() => setSelectedCard(card)}
/>
))}
</div>
</div>
{/* Partner Guardrails Section */}
<div style={{ marginBottom: 40 }}>
<h2 style={{ fontSize: 20, fontWeight: 600, color: "#111827", margin: "0 0 4px 0" }}>Partner Guardrails</h2>
<p style={{ fontSize: 13, color: "#6b7280", margin: "4px 0 20px 0" }}>
Third-party guardrail integrations from leading AI security providers.
<div className="mb-10">
<h2 className="text-xl font-semibold text-foreground mt-0 mb-1">
Partner Guardrails
</h2>
<p className="text-sm text-muted-foreground mt-1 mb-5">
Third-party guardrail integrations from leading AI security
providers.
</p>
<div style={{
display: "grid",
gridTemplateColumns: "repeat(auto-fill, minmax(220px, 1fr))",
gap: 16,
}}>
<div
className="grid gap-4"
style={{
gridTemplateColumns:
"repeat(auto-fill, minmax(220px, 1fr))",
}}
>
{partnerCards.map((card) => (
<GuardrailCard key={card.id} card={card} onClick={() => setSelectedCard(card)} />
<GuardrailCard
key={card.id}
card={card}
onClick={() => setSelectedCard(card)}
/>
))}
</div>
</div>

View file

@ -1,10 +1,7 @@
import { Typography } from "antd";
import React, { useState } from "react";
import { CategoryFilter, PiiEntityList, QuickActions } from "./pii_components";
import { PiiConfigurationProps } from "./types";
const { Title, Text } = Typography;
/**
* A reusable component for rendering PII entity selection and action configuration
* Used in both add and edit guardrail forms
@ -57,11 +54,13 @@ const PiiConfiguration: React.FC<PiiConfigurationProps> = ({
<div className="pii-configuration">
<div className="flex justify-between items-center mb-5">
<div className="flex items-center">
<Title level={4} className="!m-0 font-semibold text-gray-800">
<h4 className="m-0 font-semibold text-foreground text-base">
Configure PII Protection
</Title>
</h4>
</div>
<Text className="text-gray-500">{selectedEntities.length} items selected</Text>
<span className="text-muted-foreground">
{selectedEntities.length} items selected
</span>
</div>
<div className="mb-6">