Spaces:
Paused
Paused
| import React, { useState } from "react"; | |
| import { | |
| Table, | |
| TableBody, | |
| TableCell, | |
| TableHead, | |
| TableHeaderCell, | |
| TableRow, | |
| Icon, | |
| Button, | |
| } from "@tremor/react"; | |
| import { | |
| TrashIcon, | |
| SwitchVerticalIcon, | |
| ChevronUpIcon, | |
| ChevronDownIcon, | |
| } from "@heroicons/react/outline"; | |
| import { Tooltip } from "antd"; | |
| import { | |
| ColumnDef, | |
| flexRender, | |
| getCoreRowModel, | |
| getSortedRowModel, | |
| SortingState, | |
| useReactTable, | |
| } from "@tanstack/react-table"; | |
| import { getGuardrailLogoAndName, guardrail_provider_map } from "./guardrail_info_helpers"; | |
| import EditGuardrailForm from "./edit_guardrail_form"; | |
| import GuardrailInfoView from "./guardrail_info"; | |
| interface GuardrailItem { | |
| guardrail_id?: string; | |
| guardrail_name: string | null; | |
| litellm_params: { | |
| guardrail: string; | |
| mode: string; | |
| default_on: boolean; | |
| pii_entities_config?: {[key: string]: string}; | |
| [key: string]: any; | |
| }; | |
| guardrail_info: Record<string, any> | null; | |
| created_at?: string; | |
| updated_at?: string; | |
| } | |
| interface GuardrailTableProps { | |
| guardrailsList: GuardrailItem[]; | |
| isLoading: boolean; | |
| onDeleteClick: (guardrailId: string, guardrailName: string) => void; | |
| accessToken: string | null; | |
| onGuardrailUpdated: () => void; | |
| isAdmin?: boolean; | |
| onShowGuardrailInfo?: (isVisible: boolean) => void; | |
| } | |
| const GuardrailTable: React.FC<GuardrailTableProps> = ({ | |
| guardrailsList, | |
| isLoading, | |
| onDeleteClick, | |
| accessToken, | |
| onGuardrailUpdated, | |
| isAdmin = false, | |
| onShowGuardrailInfo, | |
| }) => { | |
| const [sorting, setSorting] = useState<SortingState>([ | |
| { id: "created_at", desc: true } | |
| ]); | |
| const [editModalVisible, setEditModalVisible] = useState(false); | |
| const [selectedGuardrail, setSelectedGuardrail] = useState<GuardrailItem | null>(null); | |
| const [showGuardrailInfo, setShowGuardrailInfo] = useState(false); | |
| const [selectedGuardrailId, setSelectedGuardrailId] = useState<string | null>(null); | |
| // Format date helper function | |
| const formatDate = (dateString?: string) => { | |
| if (!dateString) return "-"; | |
| const date = new Date(dateString); | |
| return date.toLocaleString(); | |
| }; | |
| const handleEditClick = (guardrail: GuardrailItem) => { | |
| setSelectedGuardrail(guardrail); | |
| setEditModalVisible(true); | |
| }; | |
| const handleEditSuccess = () => { | |
| setEditModalVisible(false); | |
| setSelectedGuardrail(null); | |
| onGuardrailUpdated(); | |
| }; | |
| const handleGuardrailIdClick = (guardrailId: string) => { | |
| setSelectedGuardrailId(guardrailId); | |
| setShowGuardrailInfo(true); | |
| onShowGuardrailInfo?.(true); | |
| }; | |
| const handleGuardrailInfoClose = () => { | |
| setShowGuardrailInfo(false); | |
| setSelectedGuardrailId(null); | |
| onShowGuardrailInfo?.(false); | |
| }; | |
| const handleGuardrailDeleted = () => { | |
| setShowGuardrailInfo(false); | |
| setSelectedGuardrailId(null); | |
| onShowGuardrailInfo?.(false); | |
| onGuardrailUpdated(); | |
| }; | |
| const columns: ColumnDef<GuardrailItem>[] = [ | |
| { | |
| header: "Guardrail ID", | |
| accessorKey: "guardrail_id", | |
| cell: (info: any) => ( | |
| <Tooltip title={String(info.getValue() || "")}> | |
| <Button | |
| size="xs" | |
| variant="light" | |
| className="font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left overflow-hidden truncate max-w-[200px]" | |
| onClick={() => info.getValue() && handleGuardrailIdClick(info.getValue())} | |
| > | |
| {info.getValue() ? `${String(info.getValue()).slice(0, 7)}...` : ""} | |
| </Button> | |
| </Tooltip> | |
| ), | |
| }, | |
| { | |
| header: "Name", | |
| accessorKey: "guardrail_name", | |
| cell: ({ row }) => { | |
| const guardrail = row.original; | |
| return ( | |
| <Tooltip title={guardrail.guardrail_name}> | |
| <span className="text-xs font-medium"> | |
| {guardrail.guardrail_name || "-"} | |
| </span> | |
| </Tooltip> | |
| ); | |
| }, | |
| }, | |
| { | |
| header: "Provider", | |
| accessorKey: "litellm_params.guardrail", | |
| cell: ({ row }) => { | |
| const guardrail = row.original; | |
| const { logo, displayName } = getGuardrailLogoAndName(guardrail.litellm_params.guardrail); | |
| return ( | |
| <div className="flex items-center space-x-2"> | |
| {logo && ( | |
| <img | |
| src={logo} | |
| alt={`${displayName} logo`} | |
| className="w-4 h-4" | |
| onError={(e) => { | |
| // Hide broken image | |
| (e.target as HTMLImageElement).style.display = 'none'; | |
| }} | |
| /> | |
| )} | |
| <span className="text-xs">{displayName}</span> | |
| </div> | |
| ); | |
| }, | |
| }, | |
| { | |
| header: "Mode", | |
| accessorKey: "litellm_params.mode", | |
| cell: ({ row }) => { | |
| const guardrail = row.original; | |
| return ( | |
| <span className="text-xs"> | |
| {guardrail.litellm_params.mode} | |
| </span> | |
| ); | |
| }, | |
| }, | |
| { | |
| header: "Created At", | |
| accessorKey: "created_at", | |
| cell: ({ row }) => { | |
| const guardrail = row.original; | |
| return ( | |
| <Tooltip title={guardrail.created_at}> | |
| <span className="text-xs"> | |
| {formatDate(guardrail.created_at)} | |
| </span> | |
| </Tooltip> | |
| ); | |
| }, | |
| }, | |
| { | |
| header: "Updated At", | |
| accessorKey: "updated_at", | |
| cell: ({ row }) => { | |
| const guardrail = row.original; | |
| return ( | |
| <Tooltip title={guardrail.updated_at}> | |
| <span className="text-xs"> | |
| {formatDate(guardrail.updated_at)} | |
| </span> | |
| </Tooltip> | |
| ); | |
| }, | |
| }, | |
| { | |
| id: "actions", | |
| header: "", | |
| cell: ({ row }) => { | |
| const guardrail = row.original; | |
| return ( | |
| <div className="flex space-x-2"> | |
| <Icon | |
| icon={TrashIcon} | |
| size="sm" | |
| onClick={() => guardrail.guardrail_id && onDeleteClick(guardrail.guardrail_id, guardrail.guardrail_name || 'Unnamed Guardrail')} | |
| className="cursor-pointer hover:text-red-500" | |
| tooltip="Delete guardrail" | |
| /> | |
| </div> | |
| ); | |
| }, | |
| }, | |
| ]; | |
| const table = useReactTable({ | |
| data: guardrailsList, | |
| columns, | |
| state: { | |
| sorting, | |
| }, | |
| onSortingChange: setSorting, | |
| getCoreRowModel: getCoreRowModel(), | |
| getSortedRowModel: getSortedRowModel(), | |
| enableSorting: true, | |
| }); | |
| // If showing guardrail info, render the GuardrailInfoView | |
| if (showGuardrailInfo && selectedGuardrailId) { | |
| return ( | |
| <GuardrailInfoView | |
| guardrailId={selectedGuardrailId} | |
| onClose={handleGuardrailInfoClose} | |
| accessToken={accessToken} | |
| isAdmin={isAdmin} | |
| /> | |
| ); | |
| } | |
| return ( | |
| <div className="rounded-lg custom-border relative"> | |
| <div className="overflow-x-auto"> | |
| <Table className="[&_td]:py-0.5 [&_th]:py-1"> | |
| <TableHead> | |
| {table.getHeaderGroups().map((headerGroup) => ( | |
| <TableRow key={headerGroup.id}> | |
| {headerGroup.headers.map((header) => ( | |
| <TableHeaderCell | |
| key={header.id} | |
| className={`py-1 h-8 ${ | |
| header.id === 'actions' | |
| ? 'sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)]' | |
| : '' | |
| }`} | |
| onClick={header.column.getToggleSortingHandler()} | |
| > | |
| <div className="flex items-center justify-between gap-2"> | |
| <div className="flex items-center"> | |
| {header.isPlaceholder ? null : ( | |
| flexRender( | |
| header.column.columnDef.header, | |
| header.getContext() | |
| ) | |
| )} | |
| </div> | |
| {header.id !== 'actions' && ( | |
| <div className="w-4"> | |
| {header.column.getIsSorted() ? ( | |
| { | |
| asc: <ChevronUpIcon className="h-4 w-4 text-blue-500" />, | |
| desc: <ChevronDownIcon className="h-4 w-4 text-blue-500" /> | |
| }[header.column.getIsSorted() as string] | |
| ) : ( | |
| <SwitchVerticalIcon className="h-4 w-4 text-gray-400" /> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| </TableHeaderCell> | |
| ))} | |
| </TableRow> | |
| ))} | |
| </TableHead> | |
| <TableBody> | |
| {isLoading ? ( | |
| <TableRow> | |
| <TableCell colSpan={columns.length} className="h-8 text-center"> | |
| <div className="text-center text-gray-500"> | |
| <p>Loading...</p> | |
| </div> | |
| </TableCell> | |
| </TableRow> | |
| ) : guardrailsList.length > 0 ? ( | |
| table.getRowModel().rows.map((row) => ( | |
| <TableRow key={row.id} className="h-8"> | |
| {row.getVisibleCells().map((cell) => ( | |
| <TableCell | |
| key={cell.id} | |
| className={`py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap ${ | |
| cell.column.id === 'actions' | |
| ? 'sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)]' | |
| : '' | |
| }`} | |
| > | |
| {flexRender(cell.column.columnDef.cell, cell.getContext())} | |
| </TableCell> | |
| ))} | |
| </TableRow> | |
| )) | |
| ) : ( | |
| <TableRow> | |
| <TableCell colSpan={columns.length} className="h-8 text-center"> | |
| <div className="text-center text-gray-500"> | |
| <p>No guardrails found</p> | |
| </div> | |
| </TableCell> | |
| </TableRow> | |
| )} | |
| </TableBody> | |
| </Table> | |
| </div> | |
| {/* Edit Modal */} | |
| {selectedGuardrail && ( | |
| <EditGuardrailForm | |
| visible={editModalVisible} | |
| onClose={() => setEditModalVisible(false)} | |
| accessToken={accessToken} | |
| onSuccess={handleEditSuccess} | |
| guardrailId={selectedGuardrail.guardrail_id || ''} | |
| initialValues={{ | |
| guardrail_name: selectedGuardrail.guardrail_name || '', | |
| provider: Object.keys(guardrail_provider_map).find( | |
| key => guardrail_provider_map[key] === selectedGuardrail?.litellm_params.guardrail | |
| ) || '', | |
| mode: selectedGuardrail.litellm_params.mode, | |
| default_on: selectedGuardrail.litellm_params.default_on, | |
| pii_entities_config: selectedGuardrail.litellm_params.pii_entities_config, | |
| ...selectedGuardrail.guardrail_info | |
| }} | |
| /> | |
| )} | |
| </div> | |
| ); | |
| }; | |
| export default GuardrailTable; |