migrate to antd table

This commit is contained in:
yuneng-jiang 2026-02-04 12:22:57 -08:00
parent 208cf5e86d
commit 609c423314
3 changed files with 151 additions and 102 deletions

View file

@ -1,78 +1,114 @@
import { ColumnDef } from "@tanstack/react-table";
import { ColumnsType } from "antd/es/table";
import { SearchTool } from "./types";
import { Icon } from "@tremor/react";
import { PencilAltIcon, TrashIcon } from "@heroicons/react/outline";
import { Tag } from "antd";
import TableIconActionButton from "../common_components/IconActionButton/TableIconActionButtons/TableIconActionButton";
export const searchToolColumns = (
onView: (searchToolId: string) => void,
onEdit: (searchToolId: string) => void,
onDelete: (searchToolId: string) => void,
availableProviders: Array<{ provider_name: string; ui_friendly_name: string }>,
): ColumnDef<SearchTool>[] => [
{
accessorKey: "search_tool_id",
header: "Search Tool ID",
cell: ({ row }) => (
<button
onClick={() => onView(row.original.search_tool_id!)}
className="font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left w-full truncate whitespace-nowrap cursor-pointer max-w-[15ch]"
>
{row.original.search_tool_id?.slice(0, 7)}...
</button>
),
},
{
accessorKey: "search_tool_name",
header: "Name",
cell: ({ getValue }) => <span className="font-medium">{getValue() as string}</span>,
},
{
id: "provider",
header: "Provider",
cell: ({ row }) => {
const provider = row.original.litellm_params.search_provider;
const providerInfo = availableProviders.find((p) => p.provider_name === provider);
const displayName = providerInfo?.ui_friendly_name || provider;
): ColumnsType<SearchTool> => [
{
title: "Search Tool ID",
dataIndex: "search_tool_id",
key: "search_tool_id",
render: (_, tool) => {
const isFromConfig = tool.is_from_config;
return <span className="text-sm">{displayName}</span>;
if (isFromConfig) {
return <span className="text-xs">-</span>;
}
return (
<button
onClick={() => onView(tool.search_tool_id!)}
className="font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left cursor-pointer max-w-40"
>
<span className="truncate block">{tool.search_tool_id}</span>
</button>
);
},
},
},
{
header: "Created At",
accessorKey: "created_at",
sortingFn: "datetime",
cell: ({ row }) => {
const tool = row.original;
return <span className="text-xs">{tool.created_at ? new Date(tool.created_at).toLocaleDateString() : "-"}</span>;
{
title: "Name",
dataIndex: "search_tool_name",
key: "search_tool_name",
render: (name: string) => <span className="font-medium">{name}</span>,
},
},
{
header: "Updated At",
accessorKey: "updated_at",
sortingFn: "datetime",
cell: ({ row }) => {
const tool = row.original;
return <span className="text-xs">{tool.updated_at ? new Date(tool.updated_at).toLocaleDateString() : "-"}</span>;
{
title: "Provider",
key: "provider",
render: (_, tool) => {
const provider = tool.litellm_params.search_provider;
const providerInfo = availableProviders.find((p) => p.provider_name === provider);
const displayName = providerInfo?.ui_friendly_name || provider;
return <span className="text-sm">{displayName}</span>;
},
},
},
{
id: "actions",
header: "Actions",
cell: ({ row }) => (
<div className="flex items-center gap-2">
<Icon
icon={PencilAltIcon}
size="sm"
onClick={() => onEdit(row.original.search_tool_id!)}
className="cursor-pointer"
/>
<Icon
icon={TrashIcon}
size="sm"
onClick={() => onDelete(row.original.search_tool_id!)}
className="cursor-pointer"
/>
</div>
),
},
];
{
title: "Created At",
dataIndex: "created_at",
key: "created_at",
render: (_, tool) => {
return <span className="text-xs">{tool.created_at ? new Date(tool.created_at).toLocaleDateString() : "-"}</span>;
},
},
{
title: "Updated At",
dataIndex: "updated_at",
key: "updated_at",
render: (_, tool) => {
return <span className="text-xs">{tool.updated_at ? new Date(tool.updated_at).toLocaleDateString() : "-"}</span>;
},
},
{
title: "Source",
key: "source",
render: (_, tool) => {
const isFromConfig = tool.is_from_config ?? false;
return (
<Tag color={isFromConfig ? "default" : "blue"}>
{isFromConfig ? "Config" : "DB"}
</Tag>
);
},
},
{
title: "Actions",
key: "actions",
render: (_, tool) => {
const toolId = tool.search_tool_id;
const isFromConfig = tool.is_from_config ?? false;
return (
<div className="flex items-center gap-2">
<TableIconActionButton
variant="Edit"
tooltipText="Edit search tool"
disabled={isFromConfig}
disabledTooltipText="Config search tool cannot be edited on the dashboard. Please edit it from the config file."
onClick={() => {
if (toolId && !isFromConfig) {
onEdit(toolId);
}
}}
/>
<TableIconActionButton
variant="Delete"
tooltipText="Delete search tool"
disabled={isFromConfig}
disabledTooltipText="Config search tool cannot be deleted on the dashboard. Please delete it from the config file."
onClick={() => {
if (toolId && !isFromConfig) {
onDelete(toolId);
}
}}
/>
</div>
);
},
},
];

View file

@ -1,8 +1,8 @@
import React, { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { Modal, Form, Input, Select } from "antd";
import { Button, Title, Text, Grid, Col } from "@tremor/react";
import { DataTable } from "../view_logs/table";
import { Modal, Form, Input, Select, Table, Spin } from "antd";
import { LoadingOutlined } from "@ant-design/icons";
import { Button, Title, Text } from "@tremor/react";
import { searchToolColumns } from "./SearchToolColumn";
import {
fetchSearchTools,
@ -15,6 +15,7 @@ import { isAdminRole } from "@/utils/roles";
import NotificationsManager from "../molecules/notifications_manager";
import { SearchToolView } from "./SearchToolView";
import CreateSearchTool from "./CreateSearchTools";
import DeleteResourceModal from "../common_components/DeleteResourceModal";
interface SearchToolsProps {
accessToken: string | null;
@ -22,24 +23,6 @@ interface SearchToolsProps {
userID: string | null;
}
const DeleteModal: React.FC<{
isModalOpen: boolean;
title: string;
confirmDelete: () => void;
cancelDelete: () => void;
}> = ({ isModalOpen, title, confirmDelete, cancelDelete }) => {
if (!isModalOpen) return null;
return (
<Modal open={isModalOpen} onOk={confirmDelete} okType="danger" onCancel={cancelDelete}>
<Grid numItems={1} className="gap-2 w-full">
<Title>{title}</Title>
<Col numColSpan={1}>
<p>Are you sure you want to delete this search tool?</p>
</Col>
</Grid>
</Modal>
);
};
const SearchTools: React.FC<SearchToolsProps> = ({ accessToken, userRole, userID }) => {
const {
@ -72,6 +55,7 @@ const SearchTools: React.FC<SearchToolsProps> = ({ accessToken, userRole, userID
// State
const [toolIdToDelete, setToolToDelete] = useState<string | null>(null);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
const [selectedToolId, setSelectedToolId] = useState<string | null>(null);
const [editTool, setEditTool] = useState(false);
const [isCreateModalVisible, setCreateModalVisible] = useState(false);
@ -116,16 +100,19 @@ const SearchTools: React.FC<SearchToolsProps> = ({ accessToken, userRole, userID
if (toolIdToDelete == null || accessToken == null) {
return;
}
setIsDeleting(true);
try {
await deleteSearchTool(accessToken, toolIdToDelete);
NotificationsManager.success("Deleted search tool successfully");
setIsDeleteModalOpen(false);
setToolToDelete(null);
refetch();
} catch (error) {
console.error("Error deleting the search tool:", error);
NotificationsManager.error("Failed to delete search tool");
} finally {
setIsDeleting(false);
}
setIsDeleteModalOpen(false);
setToolToDelete(null);
};
const cancelDelete = () => {
@ -133,6 +120,11 @@ const SearchTools: React.FC<SearchToolsProps> = ({ accessToken, userRole, userID
setToolToDelete(null);
};
const toolToDelete = searchTools?.find((t) => t.search_tool_id === toolIdToDelete);
const providerInfo = toolToDelete
? availableProviders.find((p) => p.provider_name === toolToDelete.litellm_params.search_provider)
: null;
const handleCreateSuccess = (newSearchTool: SearchTool) => {
setCreateModalVisible(false);
refetch();
@ -231,26 +223,46 @@ const SearchTools: React.FC<SearchToolsProps> = ({ accessToken, userRole, userID
/>
) : (
<div className="w-full h-full">
<div className="w-full px-6 mt-6">
<DataTable
data={searchTools || []}
<Spin spinning={isLoadingTools} indicator={<LoadingOutlined spin />} size="large">
<Table
bordered
dataSource={searchTools || []}
columns={columns}
renderSubComponent={() => <div></div>}
getRowCanExpand={() => false}
isLoading={isLoadingTools}
noDataMessage="No search tools configured"
rowKey={(record) => record.search_tool_id || record.search_tool_name}
pagination={false}
locale={{
emptyText: "No search tools configured",
}}
size="small"
/>
</div>
</Spin>
</div>
);
return (
<div className="w-full h-full p-6">
<DeleteModal
isModalOpen={isDeleteModalOpen}
<DeleteResourceModal
isOpen={isDeleteModalOpen}
title="Delete Search Tool"
confirmDelete={confirmDelete}
cancelDelete={cancelDelete}
message="Are you sure you want to delete this search tool? This action cannot be undone."
resourceInformationTitle="Search Tool Information"
resourceInformation={
toolToDelete
? [
{ label: "Name", value: toolToDelete.search_tool_name },
{ label: "ID", value: toolToDelete.search_tool_id, code: true },
{
label: "Provider",
value: providerInfo?.ui_friendly_name || toolToDelete.litellm_params.search_provider,
},
{ label: "Description", value: toolToDelete.search_tool_info?.description || "-" },
]
: []
}
onCancel={cancelDelete}
onOk={confirmDelete}
confirmLoading={isDeleting}
/>
<CreateSearchTool

View file

@ -19,6 +19,7 @@ export interface SearchTool {
search_tool_info?: SearchToolInfo;
created_at?: string;
updated_at?: string;
is_from_config?: boolean;
}
export interface SearchToolsResponse {