diff --git a/ui/admin/app/components/agent/AgentDropdownActions.tsx b/ui/admin/app/components/agent/AgentDropdownActions.tsx
new file mode 100644
index 000000000..6ba23dcdb
--- /dev/null
+++ b/ui/admin/app/components/agent/AgentDropdownActions.tsx
@@ -0,0 +1,73 @@
+import { EllipsisVerticalIcon } from "lucide-react";
+import { useNavigate } from "react-router";
+import { $path } from "safe-routes";
+import { toast } from "sonner";
+import { mutate } from "swr";
+
+import { Agent } from "~/lib/model/agents";
+import { AgentService } from "~/lib/service/api/agentService";
+
+import { ConfirmationDialog } from "~/components/composed/ConfirmationDialog";
+import { Button } from "~/components/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "~/components/ui/dropdown-menu";
+import { useConfirmationDialog } from "~/hooks/component-helpers/useConfirmationDropdown";
+import { useAsync } from "~/hooks/useAsync";
+
+export function AgentDropdownActions({ agent }: { agent: Agent }) {
+ const navigate = useNavigate();
+
+ const deleteAgent = useAsync(AgentService.deleteAgent, {
+ onSuccess: () => {
+ mutate(AgentService.getAgents.key());
+ toast.success("Agent deleted");
+ navigate($path("/agents"));
+ },
+ onError: (error) => {
+ if (error instanceof Error) return toast.error(error.message);
+
+ toast.error("Something went wrong");
+ },
+ });
+
+ const { dialogProps, interceptAsync } = useConfirmationDialog();
+
+ const handleDelete = () =>
+ interceptAsync(() => deleteAgent.executeAsync(agent.id));
+
+ return (
+ <>
+
+
+
+
+
+
+
+ Delete Agent
+
+
+
+
+
+ >
+ );
+}
diff --git a/ui/admin/app/components/agent/AgentPublishStatus.tsx b/ui/admin/app/components/agent/AgentPublishStatus.tsx
index fae0d9310..7a03d971a 100644
--- a/ui/admin/app/components/agent/AgentPublishStatus.tsx
+++ b/ui/admin/app/components/agent/AgentPublishStatus.tsx
@@ -8,6 +8,7 @@ import { ConsumptionUrl } from "~/lib/routers/baseRouter";
import { AssistantApiService } from "~/lib/service/api/assistantApiService";
import { TypographySmall } from "~/components/Typography";
+import { AgentDropdownActions } from "~/components/agent/AgentDropdownActions";
import { Publish } from "~/components/agent/Publish";
import { Unpublish } from "~/components/agent/Unpublish";
import { CopyText } from "~/components/composed/CopyText";
@@ -40,14 +41,18 @@ export function AgentPublishStatus({
{renderAgentRef()}
- {agent.alias ? (
-
onChange({ alias: "" })} />
- ) : (
- onChange({ alias })}
- />
- )}
+
+ {agent.alias ? (
+
onChange({ alias: "" })} />
+ ) : (
+ onChange({ alias })}
+ />
+ )}
+
+
+
);
diff --git a/ui/admin/app/components/ui/dropdown-menu.tsx b/ui/admin/app/components/ui/dropdown-menu.tsx
index b837c5600..d6633a82e 100644
--- a/ui/admin/app/components/ui/dropdown-menu.tsx
+++ b/ui/admin/app/components/ui/dropdown-menu.tsx
@@ -4,6 +4,7 @@ import {
ChevronRightIcon,
DotFilledIcon,
} from "@radix-ui/react-icons";
+import { VariantProps, cva } from "class-variance-authority";
import * as React from "react";
import { cn } from "~/lib/utils";
@@ -77,19 +78,33 @@ const DropdownMenuContent = React.forwardRef<
));
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
+const dropdownMenuItemVariants = cva(
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+ {
+ variants: {
+ variant: {
+ default: "focus:bg-accent focus:text-accent-foreground",
+ destructive:
+ "text-destructive focus:text-destructive-foreground focus:bg-destructive",
+ },
+ inset: { true: "pl-8", false: "" },
+ },
+ defaultVariants: { variant: "default", inset: false },
+ }
+);
+
+type DropdownMenuItemProps = React.ComponentPropsWithoutRef<
+ typeof DropdownMenuPrimitive.Item
+> &
+ VariantProps;
+
const DropdownMenuItem = React.forwardRef<
React.ElementRef,
- React.ComponentPropsWithoutRef & {
- inset?: boolean;
- }
->(({ className, inset, ...props }, ref) => (
+ DropdownMenuItemProps
+>(({ className, inset, variant, ...props }, ref) => (
));
diff --git a/ui/admin/app/hooks/component-helpers/useConfirmationDropdown.tsx b/ui/admin/app/hooks/component-helpers/useConfirmationDropdown.tsx
new file mode 100644
index 000000000..6fbc051bc
--- /dev/null
+++ b/ui/admin/app/hooks/component-helpers/useConfirmationDropdown.tsx
@@ -0,0 +1,54 @@
+import { ComponentProps, useCallback, useState } from "react";
+
+import { noop } from "~/lib/utils";
+
+import { ConfirmationDialog } from "~/components/composed/ConfirmationDialog";
+
+type ConfirmationDialogProps = ComponentProps;
+type Handler = (e: React.MouseEvent) => void;
+type AsyncHandler = (
+ e: React.MouseEvent
+) => Promise;
+
+export function useConfirmationDialog(
+ baseProps?: Partial
+) {
+ const [props, setProps] = useState({
+ title: "Are you sure?",
+ onConfirm: noop,
+ open: false,
+ ...baseProps,
+ });
+
+ const updateProps = useCallback(
+ (props: Partial) =>
+ setProps((prev) => ({ ...prev, ...props })),
+ []
+ );
+
+ const intercept = useCallback(
+ (handler: Handler, props?: Partial) =>
+ updateProps({
+ onConfirm: handler,
+ onCancel: () => updateProps({ open: false }),
+ open: true,
+ onOpenChange: (open) => updateProps({ open }),
+ ...props,
+ }),
+ [updateProps]
+ );
+
+ const interceptAsync = useCallback(
+ (handler: AsyncHandler, props?: Partial) =>
+ intercept(
+ async (e) => {
+ await handler(e);
+ updateProps({ open: false });
+ },
+ { closeOnConfirm: false, ...props }
+ ),
+ [intercept, updateProps]
+ );
+
+ return { dialogProps: props, intercept, interceptAsync };
+}