From 3ee523b8ae68a68e857a6f641b0dd5001eba4244 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 23 Apr 2026 16:15:27 +0000 Subject: [PATCH] feat(ui): prompts/prompt_table to shadcn - @tremor Button + @heroicons SwitchVert/ChevronUp/Down/Trash + antd Tooltip + @ant-design CopyOutlined \u2192 shadcn Button/Tooltip + lucide ArrowUpDown/ChevronUp/ChevronDown/Copy/Trash2. - Tremor Table family preserved (phase 1 whitelist). - Environment pill keeps categorical red/amber/emerald (production/ staging/development) palette; blue prompt-id chip + fallback logo palette preserved. File added to .eslintrc.json overrides. Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang --- ui/litellm-dashboard/.eslintrc.json | 1 + .../src/components/prompts/prompt_table.tsx | 317 ++++++++++++------ 2 files changed, 208 insertions(+), 110 deletions(-) diff --git a/ui/litellm-dashboard/.eslintrc.json b/ui/litellm-dashboard/.eslintrc.json index 94419020d62..f8f6b92aba4 100644 --- a/ui/litellm-dashboard/.eslintrc.json +++ b/ui/litellm-dashboard/.eslintrc.json @@ -59,6 +59,7 @@ "src/components/claude_code_plugins/plugin_table.tsx", "src/components/policies/policy_table.tsx", "src/components/guardrails/guardrail_table.tsx", + "src/components/prompts/prompt_table.tsx", "src/components/permissions/VectorStorePermissions.tsx", "src/components/permissions/AgentPermissions.tsx", "src/components/permissions/MCPServerPermissions.tsx", diff --git a/ui/litellm-dashboard/src/components/prompts/prompt_table.tsx b/ui/litellm-dashboard/src/components/prompts/prompt_table.tsx index 18bb35d4888..a5909733afa 100644 --- a/ui/litellm-dashboard/src/components/prompts/prompt_table.tsx +++ b/ui/litellm-dashboard/src/components/prompts/prompt_table.tsx @@ -1,8 +1,28 @@ import React, { useState, useEffect } from "react"; -import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow, Button } from "@tremor/react"; -import { SwitchVerticalIcon, ChevronUpIcon, ChevronDownIcon, TrashIcon } from "@heroicons/react/outline"; -import { Tooltip } from "antd"; -import { CopyOutlined } from "@ant-design/icons"; +// eslint-disable-next-line litellm-ui/no-banned-ui-imports +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeaderCell, + TableRow, +} from "@tremor/react"; +import { Button } from "@/components/ui/button"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { cn } from "@/lib/utils"; +import { + ArrowUpDown, + ChevronDown, + ChevronUp, + Copy, + Trash2, +} from "lucide-react"; import { PromptSpec, modelHubCall } from "@/components/networking"; import { ColumnDef, @@ -27,6 +47,7 @@ interface PromptTableProps { interface ModelGroupInfo { model_group: string; providers: string[]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; } @@ -38,13 +59,17 @@ const PromptTable: React.FC = ({ accessToken, isAdmin, }) => { - const [sorting, setSorting] = useState([{ id: "created_at", desc: true }]); - const [modelHubData, setModelHubData] = useState>(new Map()); + const [sorting, setSorting] = useState([ + { id: "created_at", desc: true }, + ]); + const [modelHubData, setModelHubData] = useState>( + new Map(), + ); useEffect(() => { const fetchModelHubData = async () => { if (!accessToken) return; - + try { const response = await modelHubCall(accessToken); if (response?.data) { @@ -62,7 +87,6 @@ const PromptTable: React.FC = ({ fetchModelHubData(); }, [accessToken]); - // Format date helper function const formatDate = (dateString?: string) => { if (!dateString) return "-"; const date = new Date(dateString); @@ -77,30 +101,44 @@ const PromptTable: React.FC = ({ { header: "Prompt ID", accessorKey: "prompt_id", - cell: (info: any) => { + cell: (info) => { const fullId = String(info.getValue() || ""); - const displayId = fullId.length > 25 ? `${fullId.slice(0, 25)}...` : fullId; + const displayId = + fullId.length > 25 ? `${fullId.slice(0, 25)}...` : fullId; return (
- - - - - { - e.stopPropagation(); - copyToClipboard(fullId); - }} - className="cursor-pointer text-gray-500 hover:text-blue-500 text-xs" - /> - + + + + + + {fullId} + + + + + + + + Copy prompt ID + +
); }, @@ -111,52 +149,61 @@ const PromptTable: React.FC = ({ cell: ({ row }) => { const prompt = row.original; const model = extractModel(prompt); - + if (!model) { - return -; + return -; } - + const provider = getProviderFromModelHub(model, modelHubData); const { logo } = getProviderLogoAndName(provider || ""); - - return ( - -
- {/* Provider Icon */} -
- {provider && logo ? ( - {`${provider} { - const target = e.currentTarget as HTMLImageElement; - const parent = target.parentElement; - if (!parent || !parent.contains(target)) { - return; - } - try { - const fallbackDiv = document.createElement('div'); - fallbackDiv.className = 'w-4 h-4 rounded-full bg-gray-200 flex items-center justify-center text-xs'; - fallbackDiv.textContent = provider?.charAt(0) || '-'; - parent.replaceChild(fallbackDiv, target); - } catch (error) { - console.error('Failed to replace provider logo fallback:', error); - } - }} - /> - ) : ( -
- - + return ( + + + +
+
+ {provider && logo ? ( + // eslint-disable-next-line @next/next/no-img-element + {`${provider} { + const target = e.currentTarget as HTMLImageElement; + const parent = target.parentElement; + if (!parent || !parent.contains(target)) { + return; + } + + try { + const fallbackDiv = document.createElement("div"); + fallbackDiv.className = + "w-4 h-4 rounded-full bg-muted flex items-center justify-center text-xs"; + fallbackDiv.textContent = + provider?.charAt(0) || "-"; + parent.replaceChild(fallbackDiv, target); + } catch (error) { + console.error( + "Failed to replace provider logo fallback:", + error, + ); + } + }} + /> + ) : ( +
+ - +
+ )}
- )} -
- - {/* Model Name */} - {model} -
- + + {model} +
+ + {model} + + ); }, }, @@ -166,9 +213,14 @@ const PromptTable: React.FC = ({ cell: ({ row }) => { const prompt = row.original; return ( - - {formatDate(prompt.created_at)} - + + + + {formatDate(prompt.created_at)} + + {prompt.created_at} + + ); }, }, @@ -178,9 +230,14 @@ const PromptTable: React.FC = ({ cell: ({ row }) => { const prompt = row.original; return ( - - {formatDate(prompt.updated_at)} - + + + + {formatDate(prompt.updated_at)} + + {prompt.updated_at} + + ); }, }, @@ -191,12 +248,20 @@ const PromptTable: React.FC = ({ const prompt = row.original; const env = prompt.environment || "development"; const colorMap: Record = { - production: "text-red-600 bg-red-50", - staging: "text-yellow-600 bg-yellow-50", - development: "text-green-600 bg-green-50", + production: + "text-red-600 bg-red-50 dark:text-red-300 dark:bg-red-950/30", + staging: + "text-amber-600 bg-amber-50 dark:text-amber-300 dark:bg-amber-950/30", + development: + "text-emerald-600 bg-emerald-50 dark:text-emerald-300 dark:bg-emerald-950/30", }; return ( - + {env} ); @@ -208,7 +273,7 @@ const PromptTable: React.FC = ({ cell: ({ row }) => { const prompt = row.original; return ( - + {prompt.created_by || "-"} ); @@ -220,9 +285,16 @@ const PromptTable: React.FC = ({ cell: ({ row }) => { const prompt = row.original; return ( - - {prompt.prompt_info.prompt_type} - + + + + + {prompt.prompt_info.prompt_type} + + + {prompt.prompt_info.prompt_type} + + ); }, }, @@ -232,25 +304,31 @@ const PromptTable: React.FC = ({ header: "Actions", id: "actions", enableSorting: false, - cell: ({ row }: any) => { + cell: ({ row }: { row: { original: PromptSpec } }) => { const prompt = row.original; const promptName = prompt.prompt_id || "Unknown Prompt"; return (
- - + + Delete prompt + +
); }, @@ -262,9 +340,7 @@ const PromptTable: React.FC = ({ const table = useReactTable({ data: promptsList, columns, - state: { - sorting, - }, + state: { sorting }, onSortingChange: setSorting, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), @@ -286,16 +362,25 @@ const PromptTable: React.FC = ({ >
- {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )}
{header.column.getIsSorted() ? ( { - asc: , - desc: , + asc: ( + + ), + desc: ( + + ), }[header.column.getIsSorted() as string] ) : ( - + )}
@@ -307,8 +392,11 @@ const PromptTable: React.FC = ({ {isLoading ? ( - -
+ +

Loading...

@@ -317,16 +405,25 @@ const PromptTable: React.FC = ({ table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} ))} )) ) : ( - -
+ +

No prompts found