mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): playground/A2AMetrics to shadcn
- antd Tooltip/Button + full @ant-design/icons set (CheckCircle/Clock/ Loading/ExclamationCircle/Copy/Down/Right/Link/FileText/Robot) \u2192 shadcn Tooltip/Button + lucide AlertCircle/Bot/CheckCircle2/ ChevronDown/ChevronRight/Clock/Copy/FileText/Link/Loader2. - Status pill palette (emerald/blue/red/muted) + categorical TTFT green / latency blue accents. File added to .eslintrc overrides. - Inline code blocks and details panel use bg-muted/bg-background semantic tokens. Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
9b86cd428f
commit
c235ee3a7f
2 changed files with 132 additions and 95 deletions
|
|
@ -62,6 +62,7 @@
|
|||
"src/components/prompts/prompt_table.tsx",
|
||||
"src/components/common_components/ModelAliasManager.tsx",
|
||||
"src/components/mcp_tools/mcp_server_columns.tsx",
|
||||
"src/components/playground/chat_ui/A2AMetrics.tsx",
|
||||
"src/components/permissions/VectorStorePermissions.tsx",
|
||||
"src/components/permissions/AgentPermissions.tsx",
|
||||
"src/components/permissions/MCPServerPermissions.tsx",
|
||||
|
|
|
|||
|
|
@ -1,17 +1,24 @@
|
|||
import React, { useState } from "react";
|
||||
import { Tooltip, Button } from "antd";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
CheckCircleOutlined,
|
||||
ClockCircleOutlined,
|
||||
LoadingOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
CopyOutlined,
|
||||
DownOutlined,
|
||||
RightOutlined,
|
||||
LinkOutlined,
|
||||
FileTextOutlined,
|
||||
RobotOutlined,
|
||||
} from "@ant-design/icons";
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
AlertCircle,
|
||||
Bot,
|
||||
CheckCircle2,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Clock,
|
||||
Copy,
|
||||
FileText,
|
||||
Link as LinkIcon,
|
||||
Loader2,
|
||||
} from "lucide-react";
|
||||
|
||||
export interface A2ATaskMetadata {
|
||||
taskId?: string;
|
||||
|
|
@ -21,6 +28,7 @@ export interface A2ATaskMetadata {
|
|||
timestamp?: string;
|
||||
message?: string;
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
|
|
@ -33,30 +41,30 @@ interface A2AMetricsProps {
|
|||
const getStatusIcon = (state?: string) => {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
return <CheckCircleOutlined className="text-green-500" />;
|
||||
return <CheckCircle2 className="h-3.5 w-3.5 text-emerald-500" />;
|
||||
case "working":
|
||||
case "submitted":
|
||||
return <LoadingOutlined className="text-blue-500" />;
|
||||
return <Loader2 className="h-3.5 w-3.5 animate-spin text-blue-500" />;
|
||||
case "failed":
|
||||
case "canceled":
|
||||
return <ExclamationCircleOutlined className="text-red-500" />;
|
||||
return <AlertCircle className="h-3.5 w-3.5 text-red-500" />;
|
||||
default:
|
||||
return <ClockCircleOutlined className="text-gray-500" />;
|
||||
return <Clock className="h-3.5 w-3.5 text-muted-foreground" />;
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusColor = (state?: string) => {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
return "bg-green-100 text-green-700";
|
||||
return "bg-emerald-100 text-emerald-700 dark:bg-emerald-950/30 dark:text-emerald-300";
|
||||
case "working":
|
||||
case "submitted":
|
||||
return "bg-blue-100 text-blue-700";
|
||||
return "bg-blue-100 text-blue-700 dark:bg-blue-950/30 dark:text-blue-300";
|
||||
case "failed":
|
||||
case "canceled":
|
||||
return "bg-red-100 text-red-700";
|
||||
return "bg-red-100 text-red-700 dark:bg-red-950/30 dark:text-red-300";
|
||||
default:
|
||||
return "bg-gray-100 text-gray-700";
|
||||
return "bg-muted text-muted-foreground";
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -79,7 +87,11 @@ const copyToClipboard = (text: string) => {
|
|||
navigator.clipboard.writeText(text);
|
||||
};
|
||||
|
||||
const A2AMetrics: React.FC<A2AMetricsProps> = ({ a2aMetadata, timeToFirstToken, totalLatency }) => {
|
||||
const A2AMetrics: React.FC<A2AMetricsProps> = ({
|
||||
a2aMetadata,
|
||||
timeToFirstToken,
|
||||
totalLatency,
|
||||
}) => {
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
|
||||
if (!a2aMetadata && !timeToFirstToken && !totalLatency) return null;
|
||||
|
|
@ -88,115 +100,138 @@ const A2AMetrics: React.FC<A2AMetricsProps> = ({ a2aMetadata, timeToFirstToken,
|
|||
const formattedTime = formatTimestamp(status?.timestamp);
|
||||
|
||||
return (
|
||||
<div className="a2a-metrics mt-3 pt-2 border-t border-gray-200 text-xs">
|
||||
{/* A2A Metadata Header */}
|
||||
<div className="flex items-center mb-2 text-gray-600">
|
||||
<RobotOutlined className="mr-1.5 text-blue-500" />
|
||||
<span className="font-medium text-gray-700">A2A Metadata</span>
|
||||
<div className="a2a-metrics mt-3 pt-2 border-t border-border text-xs">
|
||||
<div className="flex items-center mb-2 text-muted-foreground">
|
||||
<Bot className="h-3.5 w-3.5 mr-1.5 text-blue-500" />
|
||||
<span className="font-medium text-foreground">A2A Metadata</span>
|
||||
</div>
|
||||
|
||||
{/* Main metrics row */}
|
||||
<div className="flex flex-wrap items-center gap-2 text-gray-500 ml-4">
|
||||
{/* Status badge */}
|
||||
<div className="flex flex-wrap items-center gap-2 text-muted-foreground ml-4">
|
||||
{status?.state && (
|
||||
<span className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${getStatusColor(status.state)}`}>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium gap-1",
|
||||
getStatusColor(status.state),
|
||||
)}
|
||||
>
|
||||
{getStatusIcon(status.state)}
|
||||
<span className="ml-1 capitalize">{status.state}</span>
|
||||
<span className="capitalize">{status.state}</span>
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Timestamp */}
|
||||
{formattedTime && (
|
||||
<Tooltip title={status?.timestamp}>
|
||||
<span className="flex items-center">
|
||||
<ClockCircleOutlined className="mr-1" />
|
||||
{formattedTime}
|
||||
</span>
|
||||
</Tooltip>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="flex items-center">
|
||||
<Clock className="h-3.5 w-3.5 mr-1" />
|
||||
{formattedTime}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{status?.timestamp}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
|
||||
{/* Latency */}
|
||||
{totalLatency !== undefined && (
|
||||
<Tooltip title="Total latency">
|
||||
<span className="flex items-center text-blue-600">
|
||||
<ClockCircleOutlined className="mr-1" />
|
||||
{(totalLatency / 1000).toFixed(2)}s
|
||||
</span>
|
||||
</Tooltip>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="flex items-center text-blue-600">
|
||||
<Clock className="h-3.5 w-3.5 mr-1" />
|
||||
{(totalLatency / 1000).toFixed(2)}s
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Total latency</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
|
||||
{/* Time to first token */}
|
||||
{timeToFirstToken !== undefined && (
|
||||
<Tooltip title="Time to first token">
|
||||
<span className="flex items-center text-green-600">
|
||||
TTFT: {(timeToFirstToken / 1000).toFixed(2)}s
|
||||
</span>
|
||||
</Tooltip>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="flex items-center text-emerald-600">
|
||||
TTFT: {(timeToFirstToken / 1000).toFixed(2)}s
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Time to first token</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* IDs row */}
|
||||
<div className="flex flex-wrap items-center gap-3 text-gray-500 ml-4 mt-1.5">
|
||||
{/* Task ID */}
|
||||
<div className="flex flex-wrap items-center gap-3 text-muted-foreground ml-4 mt-1.5">
|
||||
{taskId && (
|
||||
<Tooltip title={`Click to copy: ${taskId}`}>
|
||||
<span
|
||||
className="flex items-center cursor-pointer hover:text-gray-700"
|
||||
onClick={() => copyToClipboard(taskId)}
|
||||
>
|
||||
<FileTextOutlined className="mr-1" />
|
||||
Task: {truncateId(taskId)}
|
||||
<CopyOutlined className="ml-1 text-gray-400 hover:text-gray-600" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span
|
||||
className="flex items-center cursor-pointer hover:text-foreground"
|
||||
onClick={() => copyToClipboard(taskId)}
|
||||
>
|
||||
<FileText className="h-3.5 w-3.5 mr-1" />
|
||||
Task: {truncateId(taskId)}
|
||||
<Copy className="h-3 w-3 ml-1 text-muted-foreground hover:text-foreground" />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Click to copy: {taskId}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
|
||||
{/* Context/Session ID */}
|
||||
{contextId && (
|
||||
<Tooltip title={`Click to copy: ${contextId}`}>
|
||||
<span
|
||||
className="flex items-center cursor-pointer hover:text-gray-700"
|
||||
onClick={() => copyToClipboard(contextId)}
|
||||
>
|
||||
<LinkOutlined className="mr-1" />
|
||||
Session: {truncateId(contextId)}
|
||||
<CopyOutlined className="ml-1 text-gray-400 hover:text-gray-600" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span
|
||||
className="flex items-center cursor-pointer hover:text-foreground"
|
||||
onClick={() => copyToClipboard(contextId)}
|
||||
>
|
||||
<LinkIcon className="h-3.5 w-3.5 mr-1" />
|
||||
Session: {truncateId(contextId)}
|
||||
<Copy className="h-3 w-3 ml-1 text-muted-foreground hover:text-foreground" />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Click to copy: {contextId}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
|
||||
{/* Details toggle */}
|
||||
{(metadata || status?.message) && (
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-xs text-blue-500 hover:text-blue-700 p-0 h-auto"
|
||||
onClick={() => setShowDetails(!showDetails)}
|
||||
>
|
||||
{showDetails ? <DownOutlined /> : <RightOutlined />}
|
||||
{showDetails ? (
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
) : (
|
||||
<ChevronRight className="h-3 w-3" />
|
||||
)}
|
||||
<span className="ml-1">Details</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Expandable details panel */}
|
||||
{showDetails && (
|
||||
<div className="mt-2 ml-4 p-3 bg-gray-50 rounded-md text-gray-600 border border-gray-200">
|
||||
{/* Status message */}
|
||||
<div className="mt-2 ml-4 p-3 bg-muted rounded-md text-muted-foreground border border-border">
|
||||
{status?.message && (
|
||||
<div className="mb-2">
|
||||
<span className="font-medium text-gray-700">Status Message:</span>
|
||||
<span className="font-medium text-foreground">Status Message:</span>
|
||||
<span className="ml-2">{status.message}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Full IDs */}
|
||||
{taskId && (
|
||||
<div className="mb-1.5 flex items-center">
|
||||
<span className="font-medium text-gray-700 w-24">Task ID:</span>
|
||||
<code className="ml-2 px-2 py-1 bg-white border border-gray-200 rounded text-xs font-mono">{taskId}</code>
|
||||
<CopyOutlined
|
||||
className="ml-2 cursor-pointer text-gray-400 hover:text-blue-500"
|
||||
<span className="font-medium text-foreground w-24">Task ID:</span>
|
||||
<code className="ml-2 px-2 py-1 bg-background border border-border rounded text-xs font-mono">
|
||||
{taskId}
|
||||
</code>
|
||||
<Copy
|
||||
className="ml-2 h-3.5 w-3.5 cursor-pointer text-muted-foreground hover:text-primary"
|
||||
onClick={() => copyToClipboard(taskId)}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -204,20 +239,21 @@ const A2AMetrics: React.FC<A2AMetricsProps> = ({ a2aMetadata, timeToFirstToken,
|
|||
|
||||
{contextId && (
|
||||
<div className="mb-1.5 flex items-center">
|
||||
<span className="font-medium text-gray-700 w-24">Session ID:</span>
|
||||
<code className="ml-2 px-2 py-1 bg-white border border-gray-200 rounded text-xs font-mono">{contextId}</code>
|
||||
<CopyOutlined
|
||||
className="ml-2 cursor-pointer text-gray-400 hover:text-blue-500"
|
||||
<span className="font-medium text-foreground w-24">Session ID:</span>
|
||||
<code className="ml-2 px-2 py-1 bg-background border border-border rounded text-xs font-mono">
|
||||
{contextId}
|
||||
</code>
|
||||
<Copy
|
||||
className="ml-2 h-3.5 w-3.5 cursor-pointer text-muted-foreground hover:text-primary"
|
||||
onClick={() => copyToClipboard(contextId)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Metadata fields */}
|
||||
{metadata && Object.keys(metadata).length > 0 && (
|
||||
<div className="mt-3">
|
||||
<span className="font-medium text-gray-700">Custom Metadata:</span>
|
||||
<pre className="mt-1.5 p-2 bg-white border border-gray-200 rounded text-xs font-mono overflow-x-auto whitespace-pre-wrap">
|
||||
<span className="font-medium text-foreground">Custom Metadata:</span>
|
||||
<pre className="mt-1.5 p-2 bg-background border border-border rounded text-xs font-mono overflow-x-auto whitespace-pre-wrap">
|
||||
{JSON.stringify(metadata, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue