mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): view_logs/GuardrailViewer/CompliancePanel to shadcn
- antd Tooltip + raw SVG check/cross/spinner + chevron \u2192 shadcn Tooltip + lucide CheckCircle2/XCircle/Loader2/ChevronDown. - Inline hex gray palette replaced with semantic tokens; compliant / non-compliant status pills use categorical emerald / red tones (file added to .eslintrc.json overrides). Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
fe16b30108
commit
d51e049dd9
2 changed files with 71 additions and 63 deletions
|
|
@ -68,6 +68,7 @@
|
|||
"src/components/guardrails/GuardrailTestPlayground.tsx",
|
||||
"src/components/vector_store_management/S3VectorsConfig.tsx",
|
||||
"src/components/mcp_tools/mcp_server_cost_config.tsx",
|
||||
"src/components/view_logs/GuardrailViewer/CompliancePanel.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,5 +1,12 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { Tooltip } from "antd";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { CheckCircle2, ChevronDown, Loader2, XCircle } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
checkEuAiActCompliance,
|
||||
checkGdprCompliance,
|
||||
|
|
@ -14,35 +21,11 @@ interface CompliancePanelProps {
|
|||
user?: string;
|
||||
model?: string;
|
||||
startTime?: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
metadata?: Record<string, any>;
|
||||
};
|
||||
}
|
||||
|
||||
// -- Icons --
|
||||
|
||||
const CheckIcon = () => (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<circle cx="8" cy="8" r="7" stroke="#16A34A" strokeWidth="1.5" fill="#F0FDF4" />
|
||||
<path d="M5 8l2 2 4-4" stroke="#16A34A" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const CrossIcon = () => (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<circle cx="8" cy="8" r="7" stroke="#DC2626" strokeWidth="1.5" fill="#FEF2F2" />
|
||||
<path d="M6 6l4 4M10 6l-4 4" stroke="#DC2626" strokeWidth="1.5" strokeLinecap="round" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const SpinnerIcon = () => (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" className="animate-spin">
|
||||
<circle cx="8" cy="8" r="6" stroke="#D1D5DB" strokeWidth="2" />
|
||||
<path d="M8 2a6 6 0 0 1 6 6" stroke="#6366F1" strokeWidth="2" strokeLinecap="round" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
// -- Sub-components --
|
||||
|
||||
const ComplianceCard = ({
|
||||
title,
|
||||
data,
|
||||
|
|
@ -57,71 +40,88 @@ const ComplianceCard = ({
|
|||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="border border-gray-200 rounded-lg bg-white">
|
||||
<div className="border border-border rounded-lg bg-background">
|
||||
<div
|
||||
className="flex items-center justify-between px-4 py-3 cursor-pointer hover:bg-gray-50 transition-colors"
|
||||
className="flex items-center justify-between px-4 py-3 cursor-pointer hover:bg-muted transition-colors"
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{loading ? (
|
||||
<SpinnerIcon />
|
||||
<Loader2 className="h-4 w-4 animate-spin text-indigo-500" />
|
||||
) : error ? (
|
||||
<Tooltip title={error}>
|
||||
<span className="text-gray-400 text-sm">--</span>
|
||||
</Tooltip>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="text-muted-foreground text-sm">--</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{error}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
) : data?.compliant ? (
|
||||
<CheckIcon />
|
||||
<CheckCircle2 className="h-4 w-4 text-emerald-600 dark:text-emerald-400" />
|
||||
) : (
|
||||
<CrossIcon />
|
||||
<XCircle className="h-4 w-4 text-destructive" />
|
||||
)}
|
||||
<span className="font-medium text-sm text-gray-900">{title}</span>
|
||||
<span className="font-medium text-sm text-foreground">{title}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{!loading && !error && data && (
|
||||
<span
|
||||
className={`px-2 py-0.5 rounded text-[11px] font-semibold uppercase ${
|
||||
className={cn(
|
||||
"px-2 py-0.5 rounded text-[11px] font-semibold uppercase border",
|
||||
data.compliant
|
||||
? "bg-green-100 text-green-700 border border-green-200"
|
||||
: "bg-red-100 text-red-700 border border-red-200"
|
||||
}`}
|
||||
? "bg-emerald-100 text-emerald-700 border-emerald-200 dark:bg-emerald-950 dark:text-emerald-300 dark:border-emerald-900"
|
||||
: "bg-red-100 text-red-700 border-red-200 dark:bg-red-950 dark:text-red-300 dark:border-red-900",
|
||||
)}
|
||||
>
|
||||
{data.compliant ? "COMPLIANT" : "NON-COMPLIANT"}
|
||||
</span>
|
||||
)}
|
||||
{error && (
|
||||
<span className="px-2 py-0.5 rounded text-[11px] font-medium bg-gray-100 text-gray-500 border border-gray-200">
|
||||
<span className="px-2 py-0.5 rounded text-[11px] font-medium bg-muted text-muted-foreground border border-border">
|
||||
UNAVAILABLE
|
||||
</span>
|
||||
)}
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
className={`transition-transform ${expanded ? "rotate-180" : ""}`}
|
||||
>
|
||||
<path d="M6 8l4 4 4-4" stroke="#6B7280" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
"h-4 w-4 text-muted-foreground transition-transform",
|
||||
expanded && "rotate-180",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{expanded && (
|
||||
<div className="border-t border-gray-100 px-4 py-3">
|
||||
{loading && <p className="text-sm text-gray-500">Checking compliance...</p>}
|
||||
{error && <p className="text-sm text-red-600">{error}</p>}
|
||||
<div className="border-t border-border px-4 py-3">
|
||||
{loading && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Checking compliance...
|
||||
</p>
|
||||
)}
|
||||
{error && <p className="text-sm text-destructive">{error}</p>}
|
||||
{data && (
|
||||
<div className="space-y-2">
|
||||
{data.checks.map((check, idx) => (
|
||||
<div key={idx} className="flex items-start gap-2">
|
||||
<div className="flex-shrink-0 mt-0.5">
|
||||
{check.passed ? <CheckIcon /> : <CrossIcon />}
|
||||
{check.passed ? (
|
||||
<CheckCircle2 className="h-4 w-4 text-emerald-600 dark:text-emerald-400" />
|
||||
) : (
|
||||
<XCircle className="h-4 w-4 text-destructive" />
|
||||
)}
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-gray-900">{check.check_name}</span>
|
||||
<span className="text-[10px] font-mono text-gray-400">{check.article}</span>
|
||||
<span className="text-sm font-medium text-foreground">
|
||||
{check.check_name}
|
||||
</span>
|
||||
<span className="text-[10px] font-mono text-muted-foreground">
|
||||
{check.article}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-0.5">{check.detail}</p>
|
||||
<p className="text-xs text-muted-foreground mt-0.5">
|
||||
{check.detail}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
@ -133,10 +133,13 @@ const ComplianceCard = ({
|
|||
);
|
||||
};
|
||||
|
||||
// -- Main Component --
|
||||
|
||||
const CompliancePanel: React.FC<CompliancePanelProps> = ({ accessToken, logEntry }) => {
|
||||
const [euAiActData, setEuAiActData] = useState<ComplianceResponse | null>(null);
|
||||
const CompliancePanel: React.FC<CompliancePanelProps> = ({
|
||||
accessToken,
|
||||
logEntry,
|
||||
}) => {
|
||||
const [euAiActData, setEuAiActData] = useState<ComplianceResponse | null>(
|
||||
null,
|
||||
);
|
||||
const [gdprData, setGdprData] = useState<ComplianceResponse | null>(null);
|
||||
const [euAiActLoading, setEuAiActLoading] = useState(false);
|
||||
const [gdprLoading, setGdprLoading] = useState(false);
|
||||
|
|
@ -158,20 +161,24 @@ const CompliancePanel: React.FC<CompliancePanelProps> = ({ accessToken, logEntry
|
|||
setEuAiActError(null);
|
||||
checkEuAiActCompliance(accessToken, payload)
|
||||
.then(setEuAiActData)
|
||||
.catch((err) => setEuAiActError(err.message || "Failed to check EU AI Act compliance"))
|
||||
.catch((err) =>
|
||||
setEuAiActError(err.message || "Failed to check EU AI Act compliance"),
|
||||
)
|
||||
.finally(() => setEuAiActLoading(false));
|
||||
|
||||
setGdprLoading(true);
|
||||
setGdprError(null);
|
||||
checkGdprCompliance(accessToken, payload)
|
||||
.then(setGdprData)
|
||||
.catch((err) => setGdprError(err.message || "Failed to check GDPR compliance"))
|
||||
.catch((err) =>
|
||||
setGdprError(err.message || "Failed to check GDPR compliance"),
|
||||
)
|
||||
.finally(() => setGdprLoading(false));
|
||||
}, [accessToken, logEntry]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4 className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-4">
|
||||
<h4 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-4">
|
||||
Regulatory Compliance
|
||||
</h4>
|
||||
<div className="space-y-3">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue