Skip to content

Commit

Permalink
feat: add tools table for creating and deleting tools
Browse files Browse the repository at this point in the history
Signed-off-by: tylerslaton <[email protected]>
  • Loading branch information
tylerslaton committed Oct 23, 2024
1 parent e15fd92 commit a9cc9db
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 0 deletions.
5 changes: 5 additions & 0 deletions ui/admin/app/components/header/HeaderNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ function getBreadcrumbs(route: string, params: Readonly<Params<string>>) {
</BreadcrumbItem>
</>
)}
{new RegExp($path("/tools")).test(route) && (
<BreadcrumbItem>
<BreadcrumbPage>Tools</BreadcrumbPage>
</BreadcrumbItem>
)}
{new RegExp($path("/users")).test(route) && (
<BreadcrumbItem>
<BreadcrumbPage>Users</BreadcrumbPage>
Expand Down
6 changes: 6 additions & 0 deletions ui/admin/app/components/sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
MessageSquare,
SettingsIcon,
User,
Wrench,
} from "lucide-react";
import { $path } from "remix-routes";

Expand Down Expand Up @@ -44,6 +45,11 @@ const items = [
url: $path("/threads"),
icon: MessageSquare,
},
{
title: "Tools",
url: $path("/tools"),
icon: Wrench,
},
{
title: "Users",
url: $path("/users"),
Expand Down
56 changes: 56 additions & 0 deletions ui/admin/app/components/tools/CreateTool.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useForm } from "react-hook-form";

import { CreateToolReference } from "~/lib/model/toolReferences";
import { ToolReferenceService } from "~/lib/service/api/toolreferenceService";

import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input";
import { useAsync } from "~/hooks/useAsync";

interface CreateToolProps {
onSuccess: () => void;
}

export function CreateTool({ onSuccess }: CreateToolProps) {
const { register, handleSubmit, reset } = useForm<CreateToolReference>();

const { execute: onSubmit, isLoading } = useAsync(
async (data: CreateToolReference) => {
await ToolReferenceService.createToolReference({
toolReference: { ...data, toolType: "tool" },
});
reset();
onSuccess();
},
{
onError: (error) =>
console.error("Failed to create tool reference:", error),
}
);

return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<div>
<Input
autoComplete="off"
{...register("name", { required: "Name is required" })}
placeholder="Tool Name"
/>
</div>
<div>
<Input
autoComplete="off"
{...register("reference", {
required: "Reference is required",
})}
placeholder="Tool Reference"
/>
</div>
<div>
<Button type="submit" variant="secondary" disabled={isLoading}>
{isLoading ? "Creating..." : "Register Tool"}
</Button>
</div>
</form>
);
}
104 changes: 104 additions & 0 deletions ui/admin/app/components/tools/ToolTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
import { Trash } from "lucide-react";

import { ToolReference } from "~/lib/model/toolReferences";
import { timeSince } from "~/lib/utils";

import { TypographyP } from "~/components/Typography";
import { ConfirmationDialog } from "~/components/composed/ConfirmationDialog";
import { DataTable } from "~/components/composed/DataTable";
import { ToolIcon } from "~/components/tools/ToolIcon";
import { Button } from "~/components/ui/button";

interface ToolTableProps {
tools: ToolReference[];
filter: string;
onDelete: (id: string) => void;
}

export function ToolTable({ tools, filter, onDelete }: ToolTableProps) {
const filteredTools = tools.filter(
(tool) =>
tool.name?.toLowerCase().includes(filter.toLowerCase()) ||
tool.metadata?.category
?.toLowerCase()
.includes(filter.toLowerCase()) ||
tool.description?.toLowerCase().includes(filter.toLowerCase())
);

return (
<DataTable
columns={getColumns(onDelete)}
data={filteredTools}
sort={[{ id: "created", desc: true }]}
/>
);
}

function getColumns(
onDelete: (id: string) => void
): ColumnDef<ToolReference, string>[] {
const columnHelper = createColumnHelper<ToolReference>();

return [
columnHelper.display({
id: "category",
header: "Category",
cell: ({ row }) => (
<TypographyP className="flex items-center gap-2">
<ToolIcon
className="w-5 h-5"
name={row.original.name}
icon={row.original.metadata?.icon}
/>
{row.original.metadata?.category ?? "Uncategorized"}
</TypographyP>
),
}),
columnHelper.display({
id: "name",
header: "Name",
cell: ({ row }) => (
<TypographyP>
{row.original.name}
{row.original.metadata?.bundle ? " Bundle" : ""}
</TypographyP>
),
}),
columnHelper.accessor("reference", {
header: "Reference",
}),
columnHelper.display({
id: "description",
header: "Description",
cell: ({ row }) => (
<TypographyP>{row.original.description}</TypographyP>
),
}),
columnHelper.accessor("created", {
header: "Created",
cell: ({ getValue }) => (
<TypographyP>{timeSince(new Date(getValue()))} ago</TypographyP>
),
sortingFn: "datetime",
}),
columnHelper.display({
id: "actions",
cell: ({ row }) => (
<ConfirmationDialog
title="Delete Tool Reference"
description="Are you sure you want to delete this tool reference? This action cannot be undone."
onConfirm={() => onDelete(row.original.id)}
confirmProps={{
variant: "destructive",
children: "Delete",
}}
>
<Button variant="ghost" size="sm">
<Trash className="h-4 w-4" />
</Button>
</ConfirmationDialog>
),
}),
];
}
91 changes: 91 additions & 0 deletions ui/admin/app/routes/_auth.tools._index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { PlusIcon, SearchIcon } from "lucide-react";
import { useState } from "react";
import useSWR, { preload } from "swr";

import { ToolReferenceService } from "~/lib/service/api/toolreferenceService";

import { TypographyH2 } from "~/components/Typography";
import { CreateTool } from "~/components/tools/CreateTool";
import { ToolTable } from "~/components/tools/ToolTable";
import { Button } from "~/components/ui/button";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "~/components/ui/dialog";
import { Input } from "~/components/ui/input";

export async function clientLoader() {
await Promise.all([
preload(ToolReferenceService.getToolReferences.key("tool"), () =>
ToolReferenceService.getToolReferences("tool")
),
]);
return null;
}

export default function Tools() {
const { data: tools, mutate } = useSWR(
ToolReferenceService.getToolReferences.key("tool"),
() => ToolReferenceService.getToolReferences("tool")
);

const [isDialogOpen, setIsDialogOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState("");

const handleCreateSuccess = () => {
mutate();
setIsDialogOpen(false);
};

const handleDelete = async (id: string) => {
await ToolReferenceService.deleteToolReference(id);
mutate();
};

return (
<div className="h-full p-8 flex flex-col gap-4">
<div className="flex justify-between items-center">
<TypographyH2>Tools</TypographyH2>
<div className="flex items-center space-x-2">
<div className="relative">
<SearchIcon className="w-5 h-5 text-gray-400 absolute left-3 top-1/2 transform -translate-y-1/2" />
<Input
type="text"
placeholder="Search for tools..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 w-64"
/>
</div>
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogTrigger asChild>
<Button variant="outline">
<PlusIcon className="w-4 h-4 mr-2" />
Register New Tool
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>
Create New Tool Reference
</DialogTitle>
</DialogHeader>
<CreateTool onSuccess={handleCreateSuccess} />
</DialogContent>
</Dialog>
</div>
</div>

{tools && (
<ToolTable
tools={tools}
filter={searchQuery}
onDelete={handleDelete}
/>
)}
</div>
);
}

0 comments on commit a9cc9db

Please sign in to comment.