Skip to content

Commit

Permalink
feat: first pass at updated agent/user tool workflow
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanhopperlowe committed Nov 1, 2024
1 parent 8773440 commit 60da0eb
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 42 deletions.
5 changes: 3 additions & 2 deletions ui/admin/app/components/agent/ToolEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import useSWR from "swr";
import { z } from "zod";

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

import { TruncatedText } from "../TruncatedText";
import { ToolIcon } from "../tools/ToolIcon";
Expand All @@ -24,9 +23,11 @@ import {
export function ToolEntry({
tool,
onDelete,
actions,
}: {
tool: string;
onDelete: () => void;
actions?: React.ReactNode;
}) {
const { data: toolReference, isLoading } = useSWR(
ToolReferenceService.getToolReferenceById.key(tool),
Expand All @@ -52,7 +53,7 @@ export function ToolEntry({
</div>

<div className="flex items-center gap-2">
<ToolEntryForm onChange={noop} />
{actions}

<Button
type="button"
Expand Down
211 changes: 173 additions & 38 deletions ui/admin/app/components/agent/ToolForm.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { PlusIcon } from "lucide-react";
import { ArrowDownIcon, ArrowUpIcon, PlusIcon } from "lucide-react";
import { useEffect, useMemo } from "react";
import { useFieldArray, useForm } from "react-hook-form";
import { z } from "zod";
Expand All @@ -17,7 +17,16 @@ import {
DialogTitle,
DialogTrigger,
} from "~/components/ui/dialog";
import { Form } from "~/components/ui/form";
import { Form, FormLabel } from "~/components/ui/form";

import { TypographyH4, TypographySmall } from "../Typography";
import { Switch } from "../ui/switch";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "../ui/tooltip";

const ToolVariant = {
FIXED: "fixed",
Expand All @@ -40,12 +49,6 @@ const formSchema = z.object({

export type ToolFormValues = z.infer<typeof formSchema>;

const getVariant = (tool: string, agent: Agent): ToolVariant => {
if (agent.defaultThreadTools?.includes(tool)) return "default";
if (agent.availableThreadTools?.includes(tool)) return "available";
return "fixed";
};

export function ToolForm({
agent,
onSubmit,
Expand All @@ -57,10 +60,20 @@ export function ToolForm({
}) {
const defaultValues = useMemo(() => {
return {
tools: agent.tools?.map((tool) => ({
tool,
variant: getVariant(tool, agent),
})),
tools: [
...(agent.tools ?? []).map((tool) => ({
tool,
variant: ToolVariant.FIXED,
})),
...(agent.defaultThreadTools ?? []).map((tool) => ({
tool,
variant: ToolVariant.DEFAULT,
})),
...(agent.availableThreadTools ?? []).map((tool) => ({
tool,
variant: ToolVariant.AVAILABLE,
})),
],
};
}, [agent]);

Expand All @@ -80,8 +93,6 @@ export function ToolForm({
return form.watch((values) => {
const { data, success } = formSchema.safeParse(values);

console.log(data);

if (!success) return;

onChange?.(data);
Expand All @@ -105,45 +116,169 @@ export function ToolForm({
const addTool = (tool: string) =>
toolFields.append({ tool, variant: ToolVariant.FIXED });

const updateVariant = (tool: string, variant: ToolVariant) =>
toolFields.update(
toolFields.fields.findIndex((t) => t.tool === tool),
{ tool, variant }
);

return (
<Form {...form}>
<form onSubmit={handleSubmit} className="flex flex-col gap-2">
<TypographyH4 className="flex justify-between items-end">
<span className="min-w-fit">Agent Tools</span>
{renderAddButton()}
</TypographyH4>

<TypographySmall>
These tools are essential for the agent&apos;s core
functionality and are always enabled.
</TypographySmall>

<div className="mt-2 w-full overflow-y-auto">
{fixedFields.map((field) => (
<ToolEntry
key={field.id}
tool={field.tool}
onDelete={() => removeTool(field.tool)}
actions={renderFixedActions(field.tool)}
/>
))}
</div>

<div className="flex justify-end w-full my-4">
<Dialog>
<DialogTrigger asChild>
<Button variant="secondary" className="mt-4 mb-4">
<PlusIcon className="w-4 h-4 mr-2" /> Add Tool
</Button>
</DialogTrigger>

<DialogContent className="p-0 max-w-3xl min-h-[350px]">
<DialogTitle hidden>Tool Catalog</DialogTitle>
<DialogDescription hidden>
Add tools to the agent.
</DialogDescription>

<ToolCatalog
className="w-full border-none"
tools={toolFields.fields.map(
(field) => field.tool
)}
onAddTool={addTool}
onRemoveTool={removeTool}
/>
</DialogContent>
</Dialog>
<TypographyH4 className="mt-4">User Tools</TypographyH4>

<TypographySmall>
Optional tools users can turn on or off. Use the toggle to
switch whether they&apos;re active by default by the agent.
</TypographySmall>

<div className="mt-2 w-full overflow-y-auto">
{userFields.map((field) => (
<ToolEntry
key={field.id}
tool={field.tool}
onDelete={() => removeTool(field.tool)}
actions={renderDefaultActions(
field.tool,
field.variant
)}
/>
))}
</div>
</form>
</Form>
);

function renderFixedActions(tool: string) {
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="secondary"
size="icon"
onClick={() =>
updateVariant(tool, ToolVariant.DEFAULT)
}
>
<ArrowDownIcon className="w-4 h-4" />
</Button>
</TooltipTrigger>

<TooltipContent>
Make this tool optional for users
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}

function renderDefaultActions(tool: string, variant: ToolVariant) {
return (
<>
<TooltipProvider>
<Tooltip>
<TooltipTrigger
className="flex items-center gap-2"
asChild
>
<div>
<FormLabel htmlFor="default-switch">
Default
</FormLabel>

<Switch
checked={variant === ToolVariant.DEFAULT}
name="default-switch"
onCheckedChange={(checked) =>
updateVariant(
tool,
checked
? ToolVariant.DEFAULT
: ToolVariant.AVAILABLE
)
}
/>
</div>
</TooltipTrigger>

<TooltipContent>
{variant === ToolVariant.DEFAULT
? "This tool is available by default"
: "This tool can be added by a user"}
</TooltipContent>
</Tooltip>
</TooltipProvider>

<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="secondary"
size="icon"
onClick={() =>
updateVariant(tool, ToolVariant.FIXED)
}
>
<ArrowUpIcon className="w-4 h-4" />
</Button>
</TooltipTrigger>

<TooltipContent>
Make this tool essential for the agent
</TooltipContent>
</Tooltip>
</TooltipProvider>
</>
);
}

function renderAddButton() {
return (
<div className="flex justify-end w-full">
<Dialog>
<DialogTrigger asChild>
<Button variant="secondary">
<PlusIcon className="w-4 h-4 mr-2" /> Add Tool
</Button>
</DialogTrigger>

<DialogContent className="p-0 max-w-3xl min-h-[350px]">
<DialogTitle hidden>Tool Catalog</DialogTitle>
<DialogDescription hidden>
Add tools to the agent.
</DialogDescription>

<ToolCatalog
className="w-full border-none"
tools={toolFields.fields.map((field) => field.tool)}
onAddTool={addTool}
onRemoveTool={removeTool}
/>
</DialogContent>
</Dialog>
</div>
);
}
}
9 changes: 7 additions & 2 deletions ui/admin/app/components/tools/ToolCatalog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
CommandInput,
CommandList,
} from "~/components/ui/command";
import { useLogEffect } from "~/hooks/useLogEffect";

import { ToolCategoryHeader } from "./ToolCategoryHeader";

Expand Down Expand Up @@ -49,19 +50,23 @@ export function ToolCatalog({
[tools, onAddTool]
);

useLogEffect(tools);

const handleSelectBundle = useCallback(
(bundleToolId: string, categoryTools: ToolReference[]) => {
if (tools.includes(bundleToolId)) {
onRemoveTool(bundleToolId);
return;
}

onAddTool(bundleToolId);

// remove all tools in the bundle to remove redundancy
console.log(categoryTools);

categoryTools.forEach((tool) => {
onRemoveTool(tool.id);
});

onAddTool(bundleToolId);
},
[tools, onAddTool, onRemoveTool]
);
Expand Down

0 comments on commit 60da0eb

Please sign in to comment.