Skip to content

Commit

Permalink
feat: add ability to delete agents from agent edit page
Browse files Browse the repository at this point in the history
Signed-off-by: Ryan Hopper-Lowe <[email protected]>
  • Loading branch information
ryanhopperlowe committed Dec 30, 2024
1 parent a39f77d commit 44289ca
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 17 deletions.
76 changes: 76 additions & 0 deletions ui/admin/app/components/agent/AgentDropdownActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
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 (
<>
<DropdownMenu modal>
<DropdownMenuTrigger>
<Button size="icon" variant="ghost">
<EllipsisVerticalIcon className="w-4 h-4" />
</Button>
</DropdownMenuTrigger>

<DropdownMenuContent align="end">
<DropdownMenuItem
variant="destructive"
onClick={(e) => {
e.preventDefault();
handleDelete();
}}
>
Delete Agent
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>

<ConfirmationDialog
{...dialogProps}
title="Are you sure you want to delete this agent?"
description="This action cannot be undone."
confirmProps={{
variant: "destructive",
loading: deleteAgent.isLoading,
disabled: deleteAgent.isLoading,
}}
/>
</>
);
}
21 changes: 13 additions & 8 deletions ui/admin/app/components/agent/AgentPublishStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -40,14 +41,18 @@ export function AgentPublishStatus({
<div className="flex w-full justify-between px-8 pt-4 items-center gap-4">
{renderAgentRef()}

{agent.alias ? (
<Unpublish onUnpublish={() => onChange({ alias: "" })} />
) : (
<Publish
alias={agent.alias}
onPublish={(alias) => onChange({ alias })}
/>
)}
<div className="flex items-center gap-2">
{agent.alias ? (
<Unpublish onUnpublish={() => onChange({ alias: "" })} />
) : (
<Publish
alias={agent.alias}
onPublish={(alias) => onChange({ alias })}
/>
)}

<AgentDropdownActions agent={agent} />
</div>
</div>
);

Expand Down
33 changes: 24 additions & 9 deletions ui/admin/app/components/ui/dropdown-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<typeof dropdownMenuItemVariants>;

const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
DropdownMenuItemProps
>(({ className, inset, variant, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
className={dropdownMenuItemVariants({ variant, inset, className })}
{...props}
/>
));
Expand Down
54 changes: 54 additions & 0 deletions ui/admin/app/hooks/component-helpers/useConfirmationDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { ComponentProps, useCallback, useState } from "react";

import { noop } from "~/lib/utils";

import { ConfirmationDialog } from "~/components/composed/ConfirmationDialog";

type ConfirmationDialogProps = ComponentProps<typeof ConfirmationDialog>;
type Handler = (e: React.MouseEvent<HTMLButtonElement>) => void;
type AsyncHandler = (
e: React.MouseEvent<HTMLButtonElement>
) => Promise<unknown>;

export function useConfirmationDialog(
baseProps?: Partial<ConfirmationDialogProps>
) {
const [props, setProps] = useState<ConfirmationDialogProps>({
title: "Are you sure?",
onConfirm: noop,
open: false,
...baseProps,
});

const updateProps = useCallback(
(props: Partial<ConfirmationDialogProps>) =>
setProps((prev) => ({ ...prev, ...props })),
[]
);

const intercept = useCallback(
(handler: Handler, props?: Partial<ConfirmationDialogProps>) =>
updateProps({
onConfirm: handler,
onCancel: () => updateProps({ open: false }),
open: true,
onOpenChange: (open) => updateProps({ open }),
...props,
}),
[updateProps]
);

const interceptAsync = useCallback(
(handler: AsyncHandler, props?: Partial<ConfirmationDialogProps>) =>
intercept(
async (e) => {
await handler(e);
updateProps({ open: false });
},
{ closeOnConfirm: false, ...props }
),
[intercept, updateProps]
);

return { dialogProps: props, intercept, interceptAsync };
}

0 comments on commit 44289ca

Please sign in to comment.