mirror of
https://github.com/langchain-ai/open-agent-platform.git
synced 2026-07-01 20:24:10 -04:00
feat: avoid unnecessary rerenders
- use `react-hook-form` to re-render as little as possible - switch to uncontrolled state to avoid rerendering whole thread while typing input
This commit is contained in:
+165
-150
@@ -21,6 +21,7 @@ import {
|
||||
} from "@/types/configurable";
|
||||
import _ from "lodash";
|
||||
import { useFetchPreselectedTools } from "@/hooks/use-fetch-preselected-tools";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
|
||||
export function AgentFieldsFormLoading() {
|
||||
return (
|
||||
@@ -39,32 +40,26 @@ export function AgentFieldsFormLoading() {
|
||||
}
|
||||
|
||||
interface AgentFieldsFormProps {
|
||||
name: string;
|
||||
setName: (name: string) => void;
|
||||
description: string;
|
||||
setDescription: (description: string) => void;
|
||||
configurations: ConfigurableFieldUIMetadata[];
|
||||
toolConfigurations: ConfigurableFieldMCPMetadata[];
|
||||
config: Record<string, any>;
|
||||
setConfig: (config: Record<string, any>) => void;
|
||||
agentId: string;
|
||||
ragConfigurations: ConfigurableFieldRAGMetadata[];
|
||||
agentsConfigurations: ConfigurableFieldAgentsMetadata[];
|
||||
}
|
||||
|
||||
export function AgentFieldsForm({
|
||||
name,
|
||||
setName,
|
||||
description,
|
||||
setDescription,
|
||||
configurations,
|
||||
toolConfigurations,
|
||||
config,
|
||||
setConfig,
|
||||
agentId,
|
||||
ragConfigurations,
|
||||
agentsConfigurations,
|
||||
}: AgentFieldsFormProps) {
|
||||
const form = useFormContext<{
|
||||
name: string;
|
||||
description: string;
|
||||
config: Record<string, any>;
|
||||
}>();
|
||||
|
||||
const { tools, setTools, getTools, cursor, loading } = useMCPContext();
|
||||
const { toolSearchTerm, debouncedSetSearchTerm, displayTools } =
|
||||
useSearchTools(tools, {
|
||||
@@ -81,7 +76,7 @@ export function AgentFieldsForm({
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-8 overflow-y-auto py-4">
|
||||
<div className="flex flex-col gap-8 py-4">
|
||||
<div className="flex w-full flex-col items-start justify-start gap-2 space-y-2">
|
||||
<p className="text-lg font-semibold tracking-tight">Agent Details</p>
|
||||
<div className="flex w-full flex-col items-start justify-start gap-2">
|
||||
@@ -90,8 +85,7 @@ export function AgentFieldsForm({
|
||||
</Label>
|
||||
<Input
|
||||
id="oap_name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
{...form.register("name")}
|
||||
placeholder="Emails Agent"
|
||||
/>
|
||||
</div>
|
||||
@@ -101,149 +95,170 @@ export function AgentFieldsForm({
|
||||
</Label>
|
||||
<Textarea
|
||||
id="oap_description"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
{...form.register("description")}
|
||||
placeholder="Agent that handles emails"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{configurations.length > 0 && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="flex w-full flex-col items-start justify-start gap-2 space-y-2">
|
||||
<p className="text-lg font-semibold tracking-tight">
|
||||
Agent Configuration
|
||||
</p>
|
||||
{configurations.map((c, index) => (
|
||||
<ConfigField
|
||||
key={`${c.label}-${index}`}
|
||||
className="w-full"
|
||||
id={c.label}
|
||||
label={c.label}
|
||||
type={c.type === "boolean" ? "switch" : (c.type ?? "text")}
|
||||
description={c.description}
|
||||
placeholder={c.placeholder}
|
||||
options={c.options}
|
||||
min={c.min}
|
||||
max={c.max}
|
||||
step={c.step}
|
||||
value={config[c.label]}
|
||||
setValue={(v) => setConfig({ ...config, [c.label]: v })}
|
||||
agentId={agentId}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{toolConfigurations.length > 0 && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="flex w-full flex-col items-start justify-start gap-2 space-y-2">
|
||||
<p className="text-lg font-semibold tracking-tight">Agent Tools</p>
|
||||
<Search
|
||||
onSearchChange={debouncedSetSearchTerm}
|
||||
placeholder="Search tools..."
|
||||
className="w-full"
|
||||
/>
|
||||
<div className="max-h-[500px] w-full flex-1 overflow-y-auto rounded-md border-[1px] border-slate-200 px-4">
|
||||
{toolConfigurations[0]?.label
|
||||
? displayTools.map((c) => (
|
||||
<ConfigFieldTool
|
||||
key={`tool-${c.name}`}
|
||||
id={c.name}
|
||||
label={c.name}
|
||||
|
||||
<>
|
||||
{configurations.length > 0 && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="flex w-full flex-col items-start justify-start gap-2 space-y-2">
|
||||
<p className="text-lg font-semibold tracking-tight">
|
||||
Agent Configuration
|
||||
</p>
|
||||
{configurations.map((c, index) => (
|
||||
<Controller
|
||||
key={`${c.label}-${index}`}
|
||||
control={form.control}
|
||||
name={`config.${c.label}`}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<ConfigField
|
||||
className="w-full"
|
||||
id={c.label}
|
||||
label={c.label}
|
||||
type={
|
||||
c.type === "boolean" ? "switch" : (c.type ?? "text")
|
||||
}
|
||||
description={c.description}
|
||||
placeholder={c.placeholder}
|
||||
options={c.options}
|
||||
min={c.min}
|
||||
max={c.max}
|
||||
step={c.step}
|
||||
value={value}
|
||||
setValue={onChange}
|
||||
agentId={agentId}
|
||||
toolId={toolConfigurations[0].label}
|
||||
className="border-b-[1px] py-4"
|
||||
value={config[toolConfigurations[0].label]}
|
||||
setValue={(v) =>
|
||||
setConfig({
|
||||
...config,
|
||||
[toolConfigurations[0].label]: v,
|
||||
})
|
||||
}
|
||||
/>
|
||||
))
|
||||
: null}
|
||||
{displayTools.length === 0 && toolSearchTerm && (
|
||||
<p className="my-4 w-full text-center text-sm text-slate-500">
|
||||
No tools found matching "{toolSearchTerm}".
|
||||
</p>
|
||||
)}
|
||||
{tools.length === 0 && !toolSearchTerm && (
|
||||
<p className="my-4 w-full text-center text-sm text-slate-500">
|
||||
No tools available for this agent.
|
||||
</p>
|
||||
)}
|
||||
{cursor && !toolSearchTerm && (
|
||||
<div className="flex justify-center py-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={async () => {
|
||||
try {
|
||||
setLoadingMore(true);
|
||||
const moreTool = await getTools(cursor);
|
||||
setTools((prevTools) => [...prevTools, ...moreTool]);
|
||||
} catch (error) {
|
||||
console.error("Failed to load more tools:", error);
|
||||
} finally {
|
||||
setLoadingMore(false);
|
||||
}
|
||||
}}
|
||||
disabled={loadingMore || loading}
|
||||
>
|
||||
{loadingMore ? "Loading..." : "Load More Tools"}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{ragConfigurations.length > 0 && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="flex w-full flex-col items-start justify-start gap-2">
|
||||
<p className="text-lg font-semibold tracking-tight">Agent RAG</p>
|
||||
<ConfigFieldRAG
|
||||
id={ragConfigurations[0].label}
|
||||
label={ragConfigurations[0].label}
|
||||
agentId={agentId}
|
||||
value={config[ragConfigurations[0].label]}
|
||||
setValue={(v) =>
|
||||
setConfig({
|
||||
...config,
|
||||
[ragConfigurations[0].label]: v,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{agentsConfigurations.length > 0 && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="flex w-full flex-col items-start justify-start gap-2">
|
||||
<p className="text-lg font-semibold tracking-tight">
|
||||
Supervisor Agents
|
||||
</p>
|
||||
<ConfigFieldAgents
|
||||
id={agentsConfigurations[0].label}
|
||||
label={agentsConfigurations[0].label}
|
||||
agentId={agentId}
|
||||
value={config[agentsConfigurations[0].label]}
|
||||
setValue={(v) =>
|
||||
setConfig({
|
||||
...config,
|
||||
[agentsConfigurations[0].label]: v,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{toolConfigurations.length > 0 && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="flex w-full flex-col items-start justify-start gap-4">
|
||||
<p className="text-lg font-semibold tracking-tight">
|
||||
Agent Tools
|
||||
</p>
|
||||
<Search
|
||||
onSearchChange={debouncedSetSearchTerm}
|
||||
placeholder="Search tools..."
|
||||
className="w-full"
|
||||
/>
|
||||
<div className="relative w-full flex-1 basis-[500px] rounded-md border-[1px] border-slate-200 px-4">
|
||||
<div className="absolute inset-0 overflow-y-auto px-4">
|
||||
{toolConfigurations[0]?.label
|
||||
? displayTools.map((c) => (
|
||||
<Controller
|
||||
key={`tool-${c.name}`}
|
||||
control={form.control}
|
||||
name={`config.${toolConfigurations[0].label}`}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<ConfigFieldTool
|
||||
key={`tool-${c.name}`}
|
||||
id={c.name}
|
||||
label={c.name}
|
||||
description={c.description}
|
||||
agentId={agentId}
|
||||
toolId={toolConfigurations[0].label}
|
||||
className="border-b-[1px] py-4"
|
||||
value={value}
|
||||
setValue={onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
))
|
||||
: null}
|
||||
{displayTools.length === 0 && toolSearchTerm && (
|
||||
<p className="my-4 w-full text-center text-sm text-slate-500">
|
||||
No tools found matching "{toolSearchTerm}".
|
||||
</p>
|
||||
)}
|
||||
{tools.length === 0 && !toolSearchTerm && (
|
||||
<p className="my-4 w-full text-center text-sm text-slate-500">
|
||||
No tools available for this agent.
|
||||
</p>
|
||||
)}
|
||||
{cursor && !toolSearchTerm && (
|
||||
<div className="flex justify-center py-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={async () => {
|
||||
try {
|
||||
setLoadingMore(true);
|
||||
const moreTool = await getTools(cursor);
|
||||
setTools((prevTools) => [
|
||||
...prevTools,
|
||||
...moreTool,
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error("Failed to load more tools:", error);
|
||||
} finally {
|
||||
setLoadingMore(false);
|
||||
}
|
||||
}}
|
||||
disabled={loadingMore || loading}
|
||||
>
|
||||
{loadingMore ? "Loading..." : "Load More Tools"}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{ragConfigurations.length > 0 && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="flex w-full flex-col items-start justify-start gap-2">
|
||||
<p className="text-lg font-semibold tracking-tight">Agent RAG</p>
|
||||
<Controller
|
||||
control={form.control}
|
||||
name={`config.${ragConfigurations[0].label}`}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<ConfigFieldRAG
|
||||
id={ragConfigurations[0].label}
|
||||
label={ragConfigurations[0].label}
|
||||
agentId={agentId}
|
||||
value={value}
|
||||
setValue={onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{agentsConfigurations.length > 0 && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="flex w-full flex-col items-start justify-start gap-2">
|
||||
<p className="text-lg font-semibold tracking-tight">
|
||||
Supervisor Agents
|
||||
</p>
|
||||
<Controller
|
||||
control={form.control}
|
||||
name={`config.${agentsConfigurations[0].label}`}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<ConfigFieldAgents
|
||||
id={agentsConfigurations[0].label}
|
||||
label={agentsConfigurations[0].label}
|
||||
agentId={agentId}
|
||||
value={value}
|
||||
setValue={onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+144
-125
@@ -1,6 +1,7 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
@@ -8,8 +9,8 @@ import {
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { useAgents } from "@/hooks/use-agents";
|
||||
import { Bot, LoaderCircle } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Bot, LoaderCircle, X } from "lucide-react";
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { useAgentsContext } from "@/providers/Agents";
|
||||
import { AgentFieldsForm, AgentFieldsFormLoading } from "./agent-form";
|
||||
@@ -18,6 +19,7 @@ import { Agent } from "@/types/agent";
|
||||
import { getDeployments } from "@/lib/environment/deployments";
|
||||
import { GraphSelect } from "./graph-select";
|
||||
import { useAgentConfig } from "@/hooks/use-agent-config";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
|
||||
interface CreateAgentDialogProps {
|
||||
agentId?: string;
|
||||
@@ -27,93 +29,51 @@ interface CreateAgentDialogProps {
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export function CreateAgentDialog({
|
||||
agentId,
|
||||
deploymentId,
|
||||
graphId,
|
||||
open,
|
||||
onOpenChange,
|
||||
}: CreateAgentDialogProps) {
|
||||
const deployments = getDeployments();
|
||||
function CreateAgentFormContent(props: {
|
||||
selectedGraph: Agent;
|
||||
selectedDeployment: Deployment;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const form = useForm<{
|
||||
name: string;
|
||||
description: string;
|
||||
config: Record<string, any>;
|
||||
}>({
|
||||
defaultValues: async () => {
|
||||
const values = await getSchemaAndUpdateConfig(props.selectedGraph);
|
||||
return { name: "", description: "", config: values.config };
|
||||
},
|
||||
});
|
||||
|
||||
const { createAgent } = useAgents();
|
||||
const { refreshAgents, agents } = useAgentsContext();
|
||||
const { refreshAgents } = useAgentsContext();
|
||||
const {
|
||||
getSchemaAndUpdateConfig,
|
||||
loading,
|
||||
configurations,
|
||||
toolConfigurations,
|
||||
ragConfigurations,
|
||||
agentsConfigurations,
|
||||
config,
|
||||
setConfig,
|
||||
loading,
|
||||
name,
|
||||
setName,
|
||||
description,
|
||||
setDescription,
|
||||
clearState: clearAgentConfigState,
|
||||
} = useAgentConfig();
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [selectedDeployment, setSelectedDeployment] = useState<Deployment>();
|
||||
// Use the default agent as the selected graph.
|
||||
const [selectedGraph, setSelectedGraph] = useState<Agent>();
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedDeployment || selectedGraph) return;
|
||||
if (agentId && deploymentId && graphId) {
|
||||
// Find the deployment & default agent, then set them
|
||||
const deployment = deployments.find((d) => d.id === deploymentId);
|
||||
const defaultAgent = agents.find(
|
||||
(a) => a.assistant_id === agentId && a.deploymentId === deploymentId,
|
||||
);
|
||||
if (!deployment || !defaultAgent) {
|
||||
toast.error("Something went wrong. Please try again.", {
|
||||
richColors: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedDeployment(deployment);
|
||||
setSelectedGraph(defaultAgent);
|
||||
}
|
||||
}, [agentId, deploymentId, graphId, agents, deployments]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
typeof window === "undefined" ||
|
||||
loading ||
|
||||
!open ||
|
||||
!selectedGraph ||
|
||||
!selectedDeployment
|
||||
)
|
||||
return;
|
||||
|
||||
getSchemaAndUpdateConfig(selectedGraph, {
|
||||
isCreate: true,
|
||||
});
|
||||
}, [selectedGraph, selectedDeployment, open]);
|
||||
|
||||
const handleSubmit = async (
|
||||
e: React.MouseEvent<HTMLButtonElement, MouseEvent>,
|
||||
) => {
|
||||
e.preventDefault();
|
||||
const handleSubmit = async (data: {
|
||||
name: string;
|
||||
description: string;
|
||||
config: Record<string, any>;
|
||||
}) => {
|
||||
const { name, description, config } = data;
|
||||
if (!name || !description) {
|
||||
toast.warning("Name and description are required", {
|
||||
richColors: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!selectedGraph || !selectedDeployment) {
|
||||
toast.error("Failed to create agent", {
|
||||
description: "Please try again",
|
||||
richColors: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmitting(true);
|
||||
const newAgent = await createAgent(
|
||||
selectedDeployment.id,
|
||||
selectedGraph.graph_id,
|
||||
props.selectedDeployment.id,
|
||||
props.selectedGraph.graph_id,
|
||||
{
|
||||
name,
|
||||
description,
|
||||
@@ -134,37 +94,125 @@ export function CreateAgentDialog({
|
||||
richColors: true,
|
||||
});
|
||||
|
||||
onOpenChange(false);
|
||||
clearState();
|
||||
props.onClose();
|
||||
// Do not await so that the refresh is non-blocking
|
||||
refreshAgents();
|
||||
};
|
||||
|
||||
const clearState = () => {
|
||||
clearAgentConfigState();
|
||||
setSelectedDeployment(undefined);
|
||||
setSelectedGraph(undefined);
|
||||
};
|
||||
return (
|
||||
<form onSubmit={form.handleSubmit(handleSubmit)}>
|
||||
{loading ? (
|
||||
<AgentFieldsFormLoading />
|
||||
) : (
|
||||
<FormProvider {...form}>
|
||||
<AgentFieldsForm
|
||||
agentId={props.selectedGraph.assistant_id}
|
||||
configurations={configurations}
|
||||
toolConfigurations={toolConfigurations}
|
||||
ragConfigurations={ragConfigurations}
|
||||
agentsConfigurations={agentsConfigurations}
|
||||
/>
|
||||
</FormProvider>
|
||||
)}
|
||||
<AlertDialogFooter>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
props.onClose();
|
||||
}}
|
||||
variant="outline"
|
||||
disabled={loading || submitting}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
className="flex w-full items-center justify-center gap-1"
|
||||
disabled={loading || submitting}
|
||||
>
|
||||
{submitting ? <LoaderCircle className="animate-spin" /> : <Bot />}
|
||||
<span>{submitting ? "Creating..." : "Create Agent"}</span>
|
||||
</Button>
|
||||
</AlertDialogFooter>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
export function CreateAgentDialog({
|
||||
agentId,
|
||||
deploymentId,
|
||||
graphId,
|
||||
open,
|
||||
onOpenChange,
|
||||
}: CreateAgentDialogProps) {
|
||||
const deployments = getDeployments();
|
||||
const { agents } = useAgentsContext();
|
||||
|
||||
const [selectedDeployment, setSelectedDeployment] = useState<
|
||||
Deployment | undefined
|
||||
>();
|
||||
const [selectedGraph, setSelectedGraph] = useState<Agent | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedDeployment || selectedGraph) return;
|
||||
if (agentId && deploymentId && graphId) {
|
||||
// Find the deployment & default agent, then set them
|
||||
const deployment = deployments.find((d) => d.id === deploymentId);
|
||||
const defaultAgent = agents.find(
|
||||
(a) => a.assistant_id === agentId && a.deploymentId === deploymentId,
|
||||
);
|
||||
if (!deployment || !defaultAgent) {
|
||||
toast.error("Something went wrong. Please try again.", {
|
||||
richColors: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedDeployment(deployment);
|
||||
setSelectedGraph(defaultAgent);
|
||||
}
|
||||
}, [
|
||||
agentId,
|
||||
deploymentId,
|
||||
graphId,
|
||||
agents,
|
||||
deployments,
|
||||
selectedDeployment,
|
||||
selectedGraph,
|
||||
]);
|
||||
|
||||
const [openCounter, setOpenCounter] = useState(0);
|
||||
|
||||
const lastOpen = useRef(open);
|
||||
useLayoutEffect(() => {
|
||||
if (lastOpen.current !== open && open) {
|
||||
setOpenCounter((c) => c + 1);
|
||||
}
|
||||
lastOpen.current = open;
|
||||
}, [open, setOpenCounter]);
|
||||
|
||||
return (
|
||||
<AlertDialog
|
||||
open={open}
|
||||
onOpenChange={(c) => {
|
||||
onOpenChange(c);
|
||||
if (!c) {
|
||||
clearState();
|
||||
}
|
||||
}}
|
||||
onOpenChange={onOpenChange}
|
||||
>
|
||||
<AlertDialogContent className="h-auto max-h-[90vh] overflow-auto sm:max-w-lg md:max-w-2xl lg:max-w-3xl">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Create Agent</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Create a new agent for '
|
||||
<span className="font-medium">{selectedGraph?.graph_id}</span>'
|
||||
graph.
|
||||
</AlertDialogDescription>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<AlertDialogTitle>Create Agent</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Create a new agent for '
|
||||
<span className="font-medium">{selectedGraph?.graph_id}</span>
|
||||
' graph.
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogCancel size="icon">
|
||||
<X className="size-4" />
|
||||
</AlertDialogCancel>
|
||||
</div>
|
||||
</AlertDialogHeader>
|
||||
|
||||
{!agentId && !graphId && !deploymentId && (
|
||||
<div className="flex flex-col items-start justify-start gap-2">
|
||||
<p>Please select a graph to create an agent for.</p>
|
||||
@@ -178,44 +226,15 @@ export function CreateAgentDialog({
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{loading ? (
|
||||
<AgentFieldsFormLoading />
|
||||
) : selectedGraph && selectedDeployment ? (
|
||||
<AgentFieldsForm
|
||||
name={name}
|
||||
setName={setName}
|
||||
description={description}
|
||||
setDescription={setDescription}
|
||||
configurations={configurations}
|
||||
toolConfigurations={toolConfigurations}
|
||||
config={config}
|
||||
setConfig={setConfig}
|
||||
agentId={selectedGraph.assistant_id}
|
||||
ragConfigurations={ragConfigurations}
|
||||
agentsConfigurations={agentsConfigurations}
|
||||
|
||||
{selectedGraph && selectedDeployment ? (
|
||||
<CreateAgentFormContent
|
||||
key={openCounter}
|
||||
selectedGraph={selectedGraph}
|
||||
selectedDeployment={selectedDeployment}
|
||||
onClose={() => onOpenChange(false)}
|
||||
/>
|
||||
) : null}
|
||||
<AlertDialogFooter>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
clearState();
|
||||
onOpenChange(false);
|
||||
}}
|
||||
variant="outline"
|
||||
disabled={loading || submitting}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={(e) => handleSubmit(e)}
|
||||
className="flex w-full items-center justify-center gap-1"
|
||||
disabled={loading || submitting}
|
||||
>
|
||||
{submitting ? <LoaderCircle className="animate-spin" /> : <Bot />}
|
||||
<span>{submitting ? "Creating..." : "Create Agent"}</span>
|
||||
</Button>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
|
||||
+95
-90
@@ -11,11 +11,12 @@ import {
|
||||
import { useAgents } from "@/hooks/use-agents";
|
||||
import { useAgentConfig } from "@/hooks/use-agent-config";
|
||||
import { Bot, LoaderCircle, Trash, X } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useLayoutEffect, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { useAgentsContext } from "@/providers/Agents";
|
||||
import { AgentFieldsForm, AgentFieldsFormLoading } from "./agent-form";
|
||||
import { Agent } from "@/types/agent";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
|
||||
interface EditAgentDialogProps {
|
||||
agent: Agent;
|
||||
@@ -23,63 +24,47 @@ interface EditAgentDialogProps {
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export function EditAgentDialog({
|
||||
function EditAgentDialogContent({
|
||||
agent,
|
||||
open,
|
||||
onOpenChange,
|
||||
}: EditAgentDialogProps) {
|
||||
onClose,
|
||||
}: {
|
||||
agent: Agent;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const { updateAgent, deleteAgent } = useAgents();
|
||||
const { refreshAgents } = useAgentsContext();
|
||||
const {
|
||||
getSchemaAndUpdateConfig,
|
||||
|
||||
loading,
|
||||
configurations,
|
||||
toolConfigurations,
|
||||
ragConfigurations,
|
||||
agentsConfigurations,
|
||||
config,
|
||||
setConfig,
|
||||
loading,
|
||||
setLoading,
|
||||
name,
|
||||
setName,
|
||||
description,
|
||||
setDescription,
|
||||
clearState: clearAgentConfigState,
|
||||
} = useAgentConfig();
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [deleteSubmitting, setDeleteSubmitting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
typeof window === "undefined" ||
|
||||
loading ||
|
||||
configurations.length > 0 ||
|
||||
!open
|
||||
)
|
||||
return;
|
||||
const form = useForm<{
|
||||
name: string;
|
||||
description: string;
|
||||
config: Record<string, any>;
|
||||
}>({ defaultValues: async () => getSchemaAndUpdateConfig(agent) });
|
||||
|
||||
getSchemaAndUpdateConfig(agent);
|
||||
}, [agent, open]);
|
||||
|
||||
const handleSubmit = async (
|
||||
e: React.MouseEvent<HTMLButtonElement, MouseEvent>,
|
||||
) => {
|
||||
e.preventDefault();
|
||||
if (!name || !description) {
|
||||
const handleSubmit = async (data: {
|
||||
name: string;
|
||||
description: string;
|
||||
config: Record<string, any>;
|
||||
}) => {
|
||||
if (!data.name || !data.description) {
|
||||
toast.warning("Name and description are required");
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmitting(true);
|
||||
const updatedAgent = await updateAgent(
|
||||
agent.assistant_id,
|
||||
agent.deploymentId,
|
||||
{
|
||||
name,
|
||||
description,
|
||||
config,
|
||||
},
|
||||
data,
|
||||
);
|
||||
setSubmitting(false);
|
||||
|
||||
if (!updatedAgent) {
|
||||
toast.error("Failed to update agent", {
|
||||
@@ -90,16 +75,14 @@ export function EditAgentDialog({
|
||||
|
||||
toast.success("Agent updated successfully!");
|
||||
|
||||
onOpenChange(false);
|
||||
clearState();
|
||||
// Do not await so that the refresh is non-blocking
|
||||
onClose();
|
||||
refreshAgents();
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
setSubmitting(true);
|
||||
setDeleteSubmitting(true);
|
||||
const deleted = await deleteAgent(agent.deploymentId, agent.assistant_id);
|
||||
setSubmitting(false);
|
||||
setDeleteSubmitting(false);
|
||||
|
||||
if (!deleted) {
|
||||
toast.error("Failed to delete agent", {
|
||||
@@ -110,78 +93,100 @@ export function EditAgentDialog({
|
||||
|
||||
toast.success("Agent deleted successfully!");
|
||||
|
||||
onOpenChange(false);
|
||||
clearState();
|
||||
// Do not await so that the refresh is non-blocking
|
||||
onClose();
|
||||
refreshAgents();
|
||||
};
|
||||
|
||||
const clearState = () => {
|
||||
clearAgentConfigState();
|
||||
setLoading(false);
|
||||
setSubmitting(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<AlertDialog
|
||||
open={open}
|
||||
onOpenChange={(c) => {
|
||||
onOpenChange(c);
|
||||
if (!c) {
|
||||
clearState();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<AlertDialogContent className="h-auto max-h-[90vh] overflow-auto sm:max-w-lg md:max-w-2xl lg:max-w-3xl">
|
||||
<AlertDialogContent className="h-auto max-h-[90vh] overflow-auto sm:max-w-lg md:max-w-2xl lg:max-w-3xl">
|
||||
<form onSubmit={form.handleSubmit(handleSubmit)}>
|
||||
<AlertDialogHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<AlertDialogTitle>Edit Agent</AlertDialogTitle>
|
||||
<AlertDialogCancel>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<AlertDialogTitle>Edit Agent</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Edit the agent for '
|
||||
<span className="font-medium">{agent.graph_id}</span>'
|
||||
graph.
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogCancel size="icon">
|
||||
<X className="size-4" />
|
||||
</AlertDialogCancel>
|
||||
</div>
|
||||
<AlertDialogDescription>
|
||||
Edit the agent for '
|
||||
<span className="font-medium">{agent.graph_id}</span>' graph.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
{loading ? (
|
||||
<AgentFieldsFormLoading />
|
||||
) : (
|
||||
<AgentFieldsForm
|
||||
name={name}
|
||||
setName={setName}
|
||||
description={description}
|
||||
setDescription={setDescription}
|
||||
configurations={configurations}
|
||||
toolConfigurations={toolConfigurations}
|
||||
config={config}
|
||||
setConfig={setConfig}
|
||||
agentId={agent.assistant_id}
|
||||
ragConfigurations={ragConfigurations}
|
||||
agentsConfigurations={agentsConfigurations}
|
||||
/>
|
||||
<FormProvider {...form}>
|
||||
<AgentFieldsForm
|
||||
configurations={configurations}
|
||||
toolConfigurations={toolConfigurations}
|
||||
agentId={agent.assistant_id}
|
||||
ragConfigurations={ragConfigurations}
|
||||
agentsConfigurations={agentsConfigurations}
|
||||
/>
|
||||
</FormProvider>
|
||||
)}
|
||||
<AlertDialogFooter>
|
||||
<Button
|
||||
onClick={handleDelete}
|
||||
className="flex w-full items-center justify-center gap-1"
|
||||
disabled={loading || submitting}
|
||||
disabled={loading || deleteSubmitting}
|
||||
variant="destructive"
|
||||
>
|
||||
{submitting ? <LoaderCircle className="animate-spin" /> : <Trash />}
|
||||
<span>{submitting ? "Deleting..." : "Delete Agent"}</span>
|
||||
{deleteSubmitting ? (
|
||||
<LoaderCircle className="animate-spin" />
|
||||
) : (
|
||||
<Trash />
|
||||
)}
|
||||
<span>{deleteSubmitting ? "Deleting..." : "Delete Agent"}</span>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
type="submit"
|
||||
className="flex w-full items-center justify-center gap-1"
|
||||
disabled={loading || submitting}
|
||||
disabled={loading || form.formState.isSubmitting}
|
||||
>
|
||||
{submitting ? <LoaderCircle className="animate-spin" /> : <Bot />}
|
||||
<span>{submitting ? "Saving..." : "Save Changes"}</span>
|
||||
{form.formState.isSubmitting ? (
|
||||
<LoaderCircle className="animate-spin" />
|
||||
) : (
|
||||
<Bot />
|
||||
)}
|
||||
<span>
|
||||
{form.formState.isSubmitting ? "Saving..." : "Save Changes"}
|
||||
</span>
|
||||
</Button>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</form>
|
||||
</AlertDialogContent>
|
||||
);
|
||||
}
|
||||
|
||||
export function EditAgentDialog({
|
||||
agent,
|
||||
open,
|
||||
onOpenChange,
|
||||
}: EditAgentDialogProps) {
|
||||
const [openCounter, setOpenCounter] = useState(0);
|
||||
|
||||
const lastOpen = useRef(open);
|
||||
useLayoutEffect(() => {
|
||||
if (lastOpen.current !== open && open) {
|
||||
setOpenCounter((c) => c + 1);
|
||||
}
|
||||
lastOpen.current = open;
|
||||
}, [open, setOpenCounter]);
|
||||
|
||||
return (
|
||||
<AlertDialog
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
>
|
||||
<EditAgentDialogContent
|
||||
key={openCounter}
|
||||
agent={agent}
|
||||
onClose={() => onOpenChange(false)}
|
||||
/>
|
||||
</AlertDialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -114,12 +114,11 @@ function NewThreadButton() {
|
||||
|
||||
export function Thread() {
|
||||
const [agentId] = useQueryState("agentId");
|
||||
const { getAgentConfig } = useConfigStore();
|
||||
const [hideToolCalls, setHideToolCalls] = useQueryState(
|
||||
"hideToolCalls",
|
||||
parseAsBoolean.withDefault(false),
|
||||
);
|
||||
const [input, setInput] = useState("");
|
||||
const [hasInput, setHasInput] = useState(false);
|
||||
const [firstTokenReceived, setFirstTokenReceived] = useState(false);
|
||||
|
||||
const { session } = useAuthContext();
|
||||
@@ -174,17 +173,26 @@ export function Thread() {
|
||||
|
||||
const handleSubmit = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!input.trim() || isLoading) return;
|
||||
|
||||
const form = e.currentTarget as HTMLFormElement;
|
||||
const formData = new FormData(form);
|
||||
const content = (formData.get("input") as string | undefined)?.trim() ?? "";
|
||||
|
||||
setHasInput(false);
|
||||
|
||||
if (!content || isLoading) return;
|
||||
if (!agentId) return;
|
||||
setFirstTokenReceived(false);
|
||||
|
||||
const newHumanMessage: Message = {
|
||||
id: uuidv4(),
|
||||
type: "human",
|
||||
content: input,
|
||||
content,
|
||||
};
|
||||
|
||||
const toolMessages = ensureToolCallsHaveResponses(stream.messages);
|
||||
const { getAgentConfig } = useConfigStore.getState();
|
||||
|
||||
stream.submit(
|
||||
{ messages: [...toolMessages, newHumanMessage] },
|
||||
{
|
||||
@@ -206,13 +214,14 @@ export function Thread() {
|
||||
},
|
||||
);
|
||||
|
||||
setInput("");
|
||||
form.reset();
|
||||
};
|
||||
|
||||
const handleRegenerate = (
|
||||
parentCheckpoint: Checkpoint | null | undefined,
|
||||
) => {
|
||||
if (!agentId) return;
|
||||
const { getAgentConfig } = useConfigStore.getState();
|
||||
|
||||
// Do this so the loading state is correct
|
||||
prevMessageLength.current = prevMessageLength.current - 1;
|
||||
@@ -296,8 +305,8 @@ export function Thread() {
|
||||
className="mx-auto grid max-w-3xl grid-rows-[1fr_auto] gap-2"
|
||||
>
|
||||
<textarea
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
name="input"
|
||||
onChange={(e) => setHasInput(!!e.target.value.trim())}
|
||||
onKeyDown={(e) => {
|
||||
if (
|
||||
e.key === "Enter" &&
|
||||
@@ -345,7 +354,7 @@ export function Thread() {
|
||||
<Button
|
||||
type="submit"
|
||||
className="shadow-md transition-all"
|
||||
disabled={isLoading || !input.trim()}
|
||||
disabled={isLoading || !hasInput}
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
|
||||
@@ -43,9 +43,7 @@ export function HumanMessage({
|
||||
isLoading: boolean;
|
||||
}) {
|
||||
const { session } = useAuthContext();
|
||||
|
||||
const [agentId] = useQueryState("agentId");
|
||||
const { getAgentConfig } = useConfigStore();
|
||||
|
||||
const thread = useStreamContext();
|
||||
const meta = thread.getMessagesMetadata(message);
|
||||
@@ -61,6 +59,8 @@ export function HumanMessage({
|
||||
setIsEditing(false);
|
||||
|
||||
const newMessage: Message = { type: "human", content: value };
|
||||
const { getAgentConfig } = useConfigStore.getState();
|
||||
|
||||
thread.submit(
|
||||
{ messages: [newMessage] },
|
||||
{
|
||||
|
||||
@@ -4,13 +4,9 @@ import {
|
||||
ConfigurableFieldRAGMetadata,
|
||||
ConfigurableFieldUIMetadata,
|
||||
} from "@/types/configurable";
|
||||
import { useState } from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useAgents } from "./use-agents";
|
||||
import {
|
||||
configSchemaToAgentsConfig,
|
||||
configSchemaToConfigurableFields,
|
||||
configSchemaToConfigurableTools,
|
||||
configSchemaToRagConfig,
|
||||
extractConfigurationsFromAgent,
|
||||
getConfigurableDefaults,
|
||||
} from "@/lib/ui-config";
|
||||
@@ -38,155 +34,103 @@ export function useAgentConfig() {
|
||||
>([]);
|
||||
|
||||
const [supportedConfigs, setSupportedConfigs] = useState<string[]>([]);
|
||||
|
||||
// The raw configurable fields. Only contains key value pairs, and nothing
|
||||
// around the UI config.
|
||||
const [config, setConfig] = useState<Record<string, any>>({});
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [name, setName] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
|
||||
const clearState = () => {
|
||||
const clearState = useCallback(() => {
|
||||
setConfigurations([]);
|
||||
setToolConfigurations([]);
|
||||
setRagConfigurations([]);
|
||||
setAgentsConfigurations([]);
|
||||
setConfig({});
|
||||
setName("");
|
||||
setDescription("");
|
||||
setLoading(false);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const getSchemaAndUpdateConfig = async (
|
||||
agent: Agent,
|
||||
args?: {
|
||||
isCreate?: boolean;
|
||||
},
|
||||
) => {
|
||||
clearState();
|
||||
const getSchemaAndUpdateConfig = useCallback(
|
||||
async (
|
||||
agent: Agent,
|
||||
): Promise<{
|
||||
name: string;
|
||||
description: string;
|
||||
config: Record<string, any>;
|
||||
}> => {
|
||||
clearState();
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const schema = await getAgentConfigSchema(
|
||||
agent.assistant_id,
|
||||
agent.deploymentId,
|
||||
);
|
||||
if (!schema) return;
|
||||
const { configFields, toolConfig, ragConfig, agentsConfig } =
|
||||
extractConfigurationsFromAgent({
|
||||
agent,
|
||||
schema,
|
||||
});
|
||||
setLoading(true);
|
||||
try {
|
||||
const schema = await getAgentConfigSchema(
|
||||
agent.assistant_id,
|
||||
agent.deploymentId,
|
||||
);
|
||||
if (!schema)
|
||||
return {
|
||||
name: agent.name,
|
||||
description:
|
||||
(agent.metadata?.description as string | undefined) ?? "",
|
||||
config: {},
|
||||
};
|
||||
const { configFields, toolConfig, ragConfig, agentsConfig } =
|
||||
extractConfigurationsFromAgent({
|
||||
agent,
|
||||
schema,
|
||||
});
|
||||
|
||||
const agentId = agent.assistant_id;
|
||||
const agentId = agent.assistant_id;
|
||||
|
||||
setConfigurations(configFields);
|
||||
setToolConfigurations(toolConfig);
|
||||
|
||||
// Set default config values based on configuration fields
|
||||
const { setDefaultConfig } = useConfigStore.getState();
|
||||
setDefaultConfig(agentId, configFields);
|
||||
|
||||
const supportedConfigs: string[] = [];
|
||||
|
||||
if (toolConfig.length) {
|
||||
setDefaultConfig(`${agentId}:selected-tools`, toolConfig);
|
||||
setConfigurations(configFields);
|
||||
setToolConfigurations(toolConfig);
|
||||
supportedConfigs.push("tools");
|
||||
|
||||
// Set default config values based on configuration fields
|
||||
const { setDefaultConfig } = useConfigStore.getState();
|
||||
setDefaultConfig(agentId, configFields);
|
||||
|
||||
const supportedConfigs: string[] = [];
|
||||
|
||||
if (toolConfig.length) {
|
||||
setDefaultConfig(`${agentId}:selected-tools`, toolConfig);
|
||||
setToolConfigurations(toolConfig);
|
||||
supportedConfigs.push("tools");
|
||||
}
|
||||
if (ragConfig.length) {
|
||||
setDefaultConfig(`${agentId}:rag`, ragConfig);
|
||||
setRagConfigurations(ragConfig);
|
||||
supportedConfigs.push("rag");
|
||||
}
|
||||
if (agentsConfig.length) {
|
||||
setDefaultConfig(`${agentId}:agents`, agentsConfig);
|
||||
setAgentsConfigurations(agentsConfig);
|
||||
supportedConfigs.push("supervisor");
|
||||
}
|
||||
setSupportedConfigs(supportedConfigs);
|
||||
|
||||
const configurableDefaults = getConfigurableDefaults(
|
||||
configFields,
|
||||
toolConfig,
|
||||
ragConfig,
|
||||
agentsConfig,
|
||||
);
|
||||
|
||||
return {
|
||||
name: agent.name,
|
||||
description:
|
||||
(agent.metadata?.description as string | undefined) ?? "",
|
||||
config: configurableDefaults,
|
||||
};
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
if (ragConfig.length) {
|
||||
setDefaultConfig(`${agentId}:rag`, ragConfig);
|
||||
setRagConfigurations(ragConfig);
|
||||
supportedConfigs.push("rag");
|
||||
}
|
||||
if (agentsConfig.length) {
|
||||
setDefaultConfig(`${agentId}:agents`, agentsConfig);
|
||||
setAgentsConfigurations(agentsConfig);
|
||||
supportedConfigs.push("supervisor");
|
||||
}
|
||||
|
||||
if (!args?.isCreate) {
|
||||
// Don't set name/description for create agents, since these are user specified fields.
|
||||
setName(agent.name);
|
||||
setDescription((agent.metadata?.description ?? "") as string);
|
||||
}
|
||||
|
||||
const configurableDefaults = getConfigurableDefaults(
|
||||
configFields,
|
||||
toolConfig,
|
||||
ragConfig,
|
||||
agentsConfig,
|
||||
);
|
||||
setConfig(configurableDefaults);
|
||||
setSupportedConfigs(supportedConfigs);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const resetToDefaultConfig = async (agent: Agent) => {
|
||||
const schema = await getAgentConfigSchema(
|
||||
agent.assistant_id,
|
||||
agent.deploymentId,
|
||||
);
|
||||
if (!schema) return;
|
||||
|
||||
const agentId = agent.assistant_id;
|
||||
|
||||
const configFields = configSchemaToConfigurableFields(schema);
|
||||
const toolConfig = configSchemaToConfigurableTools(schema);
|
||||
const ragConfig = configSchemaToRagConfig(schema);
|
||||
const agentsConfig = configSchemaToAgentsConfig(schema);
|
||||
const { setDefaultConfig } = useConfigStore.getState();
|
||||
setDefaultConfig(agentId, configFields);
|
||||
|
||||
const supportedConfigs: string[] = [];
|
||||
|
||||
if (toolConfig.length) {
|
||||
setDefaultConfig(`${agentId}:selected-tools`, toolConfig);
|
||||
setToolConfigurations(toolConfig);
|
||||
supportedConfigs.push("tools");
|
||||
}
|
||||
if (ragConfig) {
|
||||
setDefaultConfig(`${agentId}:rag`, [ragConfig]);
|
||||
setRagConfigurations([ragConfig]);
|
||||
supportedConfigs.push("rag");
|
||||
}
|
||||
if (agentsConfig) {
|
||||
setDefaultConfig(`${agentId}:agents`, [agentsConfig]);
|
||||
setAgentsConfigurations([agentsConfig]);
|
||||
}
|
||||
const configurableDefaults = getConfigurableDefaults(
|
||||
configFields,
|
||||
toolConfig,
|
||||
ragConfig ? [ragConfig] : [],
|
||||
agentsConfig ? [agentsConfig] : [],
|
||||
);
|
||||
setConfig(configurableDefaults);
|
||||
};
|
||||
},
|
||||
[clearState, getAgentConfigSchema],
|
||||
);
|
||||
|
||||
return {
|
||||
clearState,
|
||||
resetToDefaultConfig,
|
||||
getSchemaAndUpdateConfig,
|
||||
|
||||
configurations,
|
||||
setConfigurations,
|
||||
toolConfigurations,
|
||||
setToolConfigurations,
|
||||
ragConfigurations,
|
||||
setRagConfigurations,
|
||||
agentsConfigurations,
|
||||
setAgentsConfigurations,
|
||||
config,
|
||||
setConfig,
|
||||
loading,
|
||||
setLoading,
|
||||
name,
|
||||
setName,
|
||||
description,
|
||||
setDescription,
|
||||
supportedConfigs,
|
||||
setSupportedConfigs,
|
||||
|
||||
loading,
|
||||
};
|
||||
}
|
||||
|
||||
+151
-144
@@ -3,158 +3,165 @@ import { Agent } from "@/types/agent";
|
||||
import { Assistant } from "@langchain/langgraph-sdk";
|
||||
import { toast } from "sonner";
|
||||
import { useAuthContext } from "@/providers/Auth";
|
||||
import { useCallback } from "react";
|
||||
|
||||
export function useAgents() {
|
||||
const { session } = useAuthContext();
|
||||
|
||||
const getAgent = async (
|
||||
agentId: string,
|
||||
deploymentId: string,
|
||||
): Promise<Agent | undefined> => {
|
||||
if (!session?.accessToken) {
|
||||
toast.error("No access token found", {
|
||||
richColors: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const client = createClient(deploymentId, session.accessToken);
|
||||
const agent = await client.assistants.get(agentId);
|
||||
return {
|
||||
...agent,
|
||||
deploymentId,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Failed to get agent", e);
|
||||
toast.error("Failed to get agent");
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const getAgentConfigSchema = async (
|
||||
agentId: string,
|
||||
deploymentId: string,
|
||||
) => {
|
||||
if (!session?.accessToken) {
|
||||
toast.error("No access token found", {
|
||||
richColors: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const client = createClient(deploymentId, session.accessToken);
|
||||
const schemas = await client.assistants.getSchemas(agentId);
|
||||
|
||||
return schemas.config_schema ?? undefined;
|
||||
} catch (e) {
|
||||
console.error("Failed to get agent config schema", e);
|
||||
toast.error("Failed to get agent config schema", {
|
||||
description: (
|
||||
<div className="flex flex-col items-start gap-2">
|
||||
<p>
|
||||
Agent ID:{" "}
|
||||
<span className="font-mono font-semibold">{agentId}</span>
|
||||
</p>
|
||||
<p>
|
||||
Deployment ID:{" "}
|
||||
<span className="font-mono font-semibold">{deploymentId}</span>
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
richColors: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const createAgent = async (
|
||||
deploymentId: string,
|
||||
graphId: string,
|
||||
args: {
|
||||
name: string;
|
||||
description: string;
|
||||
config: Record<string, any>;
|
||||
const getAgent = useCallback(
|
||||
async (
|
||||
agentId: string,
|
||||
deploymentId: string,
|
||||
): Promise<Agent | undefined> => {
|
||||
if (!session?.accessToken) {
|
||||
toast.error("No access token found", {
|
||||
richColors: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const client = createClient(deploymentId, session.accessToken);
|
||||
const agent = await client.assistants.get(agentId);
|
||||
return { ...agent, deploymentId };
|
||||
} catch (e) {
|
||||
console.error("Failed to get agent", e);
|
||||
toast.error("Failed to get agent");
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
): Promise<Assistant | undefined> => {
|
||||
if (!session?.accessToken) {
|
||||
toast.error("No access token found", {
|
||||
richColors: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const client = createClient(deploymentId, session.accessToken);
|
||||
const agent = await client.assistants.create({
|
||||
graphId,
|
||||
metadata: {
|
||||
description: args.description,
|
||||
},
|
||||
name: args.name,
|
||||
config: {
|
||||
configurable: {
|
||||
...args.config,
|
||||
[session?.accessToken],
|
||||
);
|
||||
|
||||
const getAgentConfigSchema = useCallback(
|
||||
async (agentId: string, deploymentId: string) => {
|
||||
if (!session?.accessToken) {
|
||||
toast.error("No access token found", {
|
||||
richColors: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const client = createClient(deploymentId, session.accessToken);
|
||||
const schemas = await client.assistants.getSchemas(agentId);
|
||||
|
||||
return schemas.config_schema ?? undefined;
|
||||
} catch (e) {
|
||||
console.error("Failed to get agent config schema", e);
|
||||
toast.error("Failed to get agent config schema", {
|
||||
description: (
|
||||
<div className="flex flex-col items-start gap-2">
|
||||
<p>
|
||||
Agent ID:{" "}
|
||||
<span className="font-mono font-semibold">{agentId}</span>
|
||||
</p>
|
||||
<p>
|
||||
Deployment ID:{" "}
|
||||
<span className="font-mono font-semibold">{deploymentId}</span>
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
richColors: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
[session?.accessToken],
|
||||
);
|
||||
|
||||
const createAgent = useCallback(
|
||||
async (
|
||||
deploymentId: string,
|
||||
graphId: string,
|
||||
args: {
|
||||
name: string;
|
||||
description: string;
|
||||
config: Record<string, any>;
|
||||
},
|
||||
): Promise<Assistant | undefined> => {
|
||||
if (!session?.accessToken) {
|
||||
toast.error("No access token found", {
|
||||
richColors: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const client = createClient(deploymentId, session.accessToken);
|
||||
const agent = await client.assistants.create({
|
||||
graphId,
|
||||
metadata: {
|
||||
description: args.description,
|
||||
},
|
||||
},
|
||||
});
|
||||
return agent;
|
||||
} catch (e) {
|
||||
console.error("Failed to create agent", e);
|
||||
toast.error("Failed to create agent");
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const updateAgent = async (
|
||||
agentId: string,
|
||||
deploymentId: string,
|
||||
args: {
|
||||
name?: string;
|
||||
description?: string;
|
||||
config?: Record<string, any>;
|
||||
name: args.name,
|
||||
config: {
|
||||
configurable: {
|
||||
...args.config,
|
||||
},
|
||||
},
|
||||
});
|
||||
return agent;
|
||||
} catch (e) {
|
||||
console.error("Failed to create agent", e);
|
||||
toast.error("Failed to create agent");
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
): Promise<Assistant | undefined> => {
|
||||
if (!session?.accessToken) {
|
||||
toast.error("No access token found", {
|
||||
richColors: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const client = createClient(deploymentId, session.accessToken);
|
||||
const agent = await client.assistants.update(agentId, {
|
||||
metadata: {
|
||||
...(args.description && { description: args.description }),
|
||||
},
|
||||
...(args.name && { name: args.name }),
|
||||
...(args.config && { config: { configurable: args.config } }),
|
||||
});
|
||||
return agent;
|
||||
} catch (e) {
|
||||
console.error("Failed to update agent", e);
|
||||
toast.error("Failed to update agent");
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
[session?.accessToken],
|
||||
);
|
||||
|
||||
const deleteAgent = async (
|
||||
deploymentId: string,
|
||||
agentId: string,
|
||||
): Promise<boolean> => {
|
||||
if (!session?.accessToken) {
|
||||
toast.error("No access token found", {
|
||||
richColors: true,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const client = createClient(deploymentId, session.accessToken);
|
||||
await client.assistants.delete(agentId);
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error("Failed to delete agent", e);
|
||||
toast.error("Failed to delete agent");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const updateAgent = useCallback(
|
||||
async (
|
||||
agentId: string,
|
||||
deploymentId: string,
|
||||
args: {
|
||||
name?: string;
|
||||
description?: string;
|
||||
config?: Record<string, any>;
|
||||
},
|
||||
): Promise<Assistant | undefined> => {
|
||||
if (!session?.accessToken) {
|
||||
toast.error("No access token found", {
|
||||
richColors: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const client = createClient(deploymentId, session.accessToken);
|
||||
const agent = await client.assistants.update(agentId, {
|
||||
metadata: {
|
||||
...(args.description && { description: args.description }),
|
||||
},
|
||||
...(args.name && { name: args.name }),
|
||||
...(args.config && { config: { configurable: args.config } }),
|
||||
});
|
||||
return agent;
|
||||
} catch (e) {
|
||||
console.error("Failed to update agent", e);
|
||||
toast.error("Failed to update agent");
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
[session?.accessToken],
|
||||
);
|
||||
|
||||
const deleteAgent = useCallback(
|
||||
async (deploymentId: string, agentId: string): Promise<boolean> => {
|
||||
if (!session?.accessToken) {
|
||||
toast.error("No access token found", {
|
||||
richColors: true,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const client = createClient(deploymentId, session.accessToken);
|
||||
await client.assistants.delete(agentId);
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error("Failed to delete agent", e);
|
||||
toast.error("Failed to delete agent");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[session?.accessToken],
|
||||
);
|
||||
|
||||
return {
|
||||
getAgent,
|
||||
|
||||
+4
-1
@@ -17,5 +17,8 @@
|
||||
"turbo": "^2.5.0",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"packageManager": "yarn@3.5.1"
|
||||
"packageManager": "yarn@3.5.1",
|
||||
"dependencies": {
|
||||
"react-hook-form": "^7.56.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6419,6 +6419,7 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "open-agent-platform@workspace:."
|
||||
dependencies:
|
||||
react-hook-form: ^7.56.3
|
||||
turbo: ^2.5.0
|
||||
typescript: ^5
|
||||
languageName: unknown
|
||||
@@ -6834,6 +6835,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-hook-form@npm:^7.56.3":
|
||||
version: 7.56.3
|
||||
resolution: "react-hook-form@npm:7.56.3"
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17 || ^18 || ^19
|
||||
checksum: 5b5ebf1b61754a5c2e5785a6f3039eac89028d0613e9f7da23c132129e9685c166077b338db05430292d16b9532a21ebd2abd8785de497c54cb70fbccf774572
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-is@npm:^16.13.1":
|
||||
version: 16.13.1
|
||||
resolution: "react-is@npm:16.13.1"
|
||||
|
||||
Reference in New Issue
Block a user