332 lines
12 KiB
TypeScript
332 lines
12 KiB
TypeScript
/*
|
|
┌──────────────────────────────────────────────────────────────────────────────┐
|
|
│ @author: Davidson Gomes │
|
|
│ @file: /app/agents/forms/AgentForm.tsx │
|
|
│ Developed by: Davidson Gomes │
|
|
│ Creation date: May 13, 2025 │
|
|
│ Contact: contato@evolution-api.com │
|
|
├──────────────────────────────────────────────────────────────────────────────┤
|
|
│ @copyright © Evolution API 2025. All rights reserved. │
|
|
│ Licensed under the Apache License, Version 2.0 │
|
|
│ │
|
|
│ You may not use this file except in compliance with the License. │
|
|
│ You may obtain a copy of the License at │
|
|
│ │
|
|
│ http://www.apache.org/licenses/LICENSE-2.0 │
|
|
│ │
|
|
│ Unless required by applicable law or agreed to in writing, software │
|
|
│ distributed under the License is distributed on an "AS IS" BASIS, │
|
|
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
|
|
│ See the License for the specific language governing permissions and │
|
|
│ limitations under the License. │
|
|
├──────────────────────────────────────────────────────────────────────────────┤
|
|
│ @important │
|
|
│ For any future changes to the code in this file, it is recommended to │
|
|
│ include, together with the modification, the information of the developer │
|
|
│ who changed it and the date of modification. │
|
|
└──────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
"use client";
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog";
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
import { Agent } from "@/types/agent";
|
|
import { ApiKey } from "@/services/agentService";
|
|
import { MCPServer } from "@/types/mcpServer";
|
|
import { useState, useEffect } from "react";
|
|
import { BasicInfoTab } from "./BasicInfoTab";
|
|
import { ConfigurationTab } from "./ConfigurationTab";
|
|
import { SubAgentsTab } from "./SubAgentsTab";
|
|
import { MCPDialog } from "../dialogs/MCPDialog";
|
|
import { CustomMCPDialog } from "../dialogs/CustomMCPDialog";
|
|
|
|
interface ModelOption {
|
|
value: string;
|
|
label: string;
|
|
provider: string;
|
|
}
|
|
|
|
interface AgentFormProps {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
initialValues: Partial<Agent>;
|
|
apiKeys: ApiKey[];
|
|
availableModels: ModelOption[];
|
|
availableMCPs: MCPServer[];
|
|
agents: Agent[];
|
|
onOpenApiKeysDialog: () => void;
|
|
onOpenMCPDialog: (mcp?: any) => void;
|
|
onOpenCustomMCPDialog: (customMCP?: any) => void;
|
|
onSave: (values: Partial<Agent>) => Promise<void>;
|
|
isLoading?: boolean;
|
|
getAgentNameById: (id: string) => string;
|
|
clientId: string;
|
|
}
|
|
|
|
export function AgentForm({
|
|
open,
|
|
onOpenChange,
|
|
initialValues,
|
|
apiKeys,
|
|
availableModels,
|
|
availableMCPs,
|
|
agents,
|
|
onOpenApiKeysDialog,
|
|
onOpenMCPDialog: externalOnOpenMCPDialog,
|
|
onOpenCustomMCPDialog: externalOnOpenCustomMCPDialog,
|
|
onSave,
|
|
isLoading = false,
|
|
getAgentNameById,
|
|
clientId,
|
|
}: AgentFormProps) {
|
|
const [values, setValues] = useState<Partial<Agent>>(initialValues);
|
|
const [activeTab, setActiveTab] = useState("basic");
|
|
|
|
const [mcpDialogOpen, setMcpDialogOpen] = useState(false);
|
|
const [selectedMCP, setSelectedMCP] = useState<any>(null);
|
|
const [customMcpDialogOpen, setCustomMcpDialogOpen] = useState(false);
|
|
const [selectedCustomMCP, setSelectedCustomMCP] = useState<any>(null);
|
|
|
|
useEffect(() => {
|
|
if (open) {
|
|
setValues(initialValues);
|
|
setActiveTab("basic");
|
|
}
|
|
}, [open, initialValues]);
|
|
|
|
const handleOpenMCPDialog = (mcpConfig: any = null) => {
|
|
setSelectedMCP(mcpConfig);
|
|
setMcpDialogOpen(true);
|
|
};
|
|
|
|
const handleOpenCustomMCPDialog = (customMCP: any = null) => {
|
|
setSelectedCustomMCP(customMCP);
|
|
setCustomMcpDialogOpen(true);
|
|
};
|
|
|
|
const handleConfigureMCP = (mcpConfig: any) => {
|
|
handleOpenMCPDialog(mcpConfig);
|
|
};
|
|
|
|
const handleRemoveMCP = (mcpId: string) => {
|
|
setValues({
|
|
...values,
|
|
config: {
|
|
...values.config,
|
|
mcp_servers:
|
|
values.config?.mcp_servers?.filter((mcp) => mcp.id !== mcpId) || [],
|
|
},
|
|
});
|
|
};
|
|
|
|
const handleConfigureCustomMCP = (customMCP: any) => {
|
|
handleOpenCustomMCPDialog(customMCP);
|
|
};
|
|
|
|
const handleRemoveCustomMCP = (url: string) => {
|
|
setValues({
|
|
...values,
|
|
config: {
|
|
...values.config,
|
|
custom_mcp_servers:
|
|
values.config?.custom_mcp_servers?.filter(
|
|
(customMCP) => customMCP.url !== url
|
|
) || [],
|
|
},
|
|
});
|
|
};
|
|
|
|
const handleSaveMCP = (mcpConfig: any) => {
|
|
const updatedMcpServers = [...(values.config?.mcp_servers || [])];
|
|
const existingIndex = updatedMcpServers.findIndex(
|
|
(mcp) => mcp.id === mcpConfig.id
|
|
);
|
|
|
|
if (existingIndex >= 0) {
|
|
updatedMcpServers[existingIndex] = mcpConfig;
|
|
} else {
|
|
updatedMcpServers.push(mcpConfig);
|
|
}
|
|
|
|
setValues({
|
|
...values,
|
|
config: {
|
|
...(values.config || {}),
|
|
mcp_servers: updatedMcpServers,
|
|
},
|
|
});
|
|
};
|
|
|
|
const handleSaveCustomMCP = (customMCP: any) => {
|
|
const updatedCustomMCPs = [...(values.config?.custom_mcp_servers || [])];
|
|
const existingIndex = updatedCustomMCPs.findIndex(
|
|
(mcp) => mcp.url === customMCP.url
|
|
);
|
|
|
|
if (existingIndex >= 0) {
|
|
updatedCustomMCPs[existingIndex] = customMCP;
|
|
} else {
|
|
updatedCustomMCPs.push(customMCP);
|
|
}
|
|
|
|
setValues({
|
|
...values,
|
|
config: {
|
|
...(values.config || {}),
|
|
custom_mcp_servers: updatedCustomMCPs,
|
|
},
|
|
});
|
|
};
|
|
|
|
const handleSave = async () => {
|
|
const finalValues = {
|
|
...values,
|
|
client_id: clientId,
|
|
name: values.name,
|
|
};
|
|
|
|
await onSave(finalValues);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="sm:max-w-[700px] max-h-[90vh] overflow-hidden flex flex-col bg-[#1a1a1a] border-[#333]">
|
|
<DialogHeader>
|
|
<DialogTitle className="text-white">
|
|
{initialValues.id ? "Edit Agent" : "New Agent"}
|
|
</DialogTitle>
|
|
<DialogDescription className="text-neutral-400">
|
|
{initialValues.id
|
|
? "Edit the existing agent information"
|
|
: "Fill in the information to create a new agent"}
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<Tabs
|
|
value={activeTab}
|
|
onValueChange={setActiveTab}
|
|
className="flex-1 overflow-hidden flex flex-col"
|
|
>
|
|
<TabsList className="grid grid-cols-3 bg-[#222]">
|
|
<TabsTrigger
|
|
value="basic"
|
|
className="data-[state=active]:bg-[#333] data-[state=active]:text-emerald-400"
|
|
>
|
|
Basic Information
|
|
</TabsTrigger>
|
|
<TabsTrigger
|
|
value="config"
|
|
className="data-[state=active]:bg-[#333] data-[state=active]:text-emerald-400"
|
|
>
|
|
Configuration
|
|
</TabsTrigger>
|
|
<TabsTrigger
|
|
value="subagents"
|
|
className="data-[state=active]:bg-[#333] data-[state=active]:text-emerald-400"
|
|
>
|
|
Sub-Agents
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<ScrollArea className="flex-1 overflow-auto">
|
|
<TabsContent value="basic" className="p-4 space-y-4">
|
|
<BasicInfoTab
|
|
values={values}
|
|
onChange={setValues}
|
|
apiKeys={apiKeys}
|
|
availableModels={availableModels}
|
|
onOpenApiKeysDialog={onOpenApiKeysDialog}
|
|
clientId={clientId}
|
|
/>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="config" className="p-4 space-y-4">
|
|
<ConfigurationTab
|
|
values={values}
|
|
onChange={setValues}
|
|
agents={agents}
|
|
availableMCPs={availableMCPs}
|
|
apiKeys={apiKeys}
|
|
availableModels={availableModels}
|
|
getAgentNameById={getAgentNameById}
|
|
onOpenApiKeysDialog={onOpenApiKeysDialog}
|
|
onConfigureMCP={handleConfigureMCP}
|
|
onRemoveMCP={handleRemoveMCP}
|
|
onConfigureCustomMCP={handleConfigureCustomMCP}
|
|
onRemoveCustomMCP={handleRemoveCustomMCP}
|
|
onOpenMCPDialog={handleOpenMCPDialog}
|
|
onOpenCustomMCPDialog={handleOpenCustomMCPDialog}
|
|
clientId={clientId}
|
|
/>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="subagents" className="p-4 space-y-4">
|
|
<SubAgentsTab
|
|
values={values}
|
|
onChange={setValues}
|
|
getAgentNameById={getAgentNameById}
|
|
editingAgentId={initialValues.id}
|
|
clientId={clientId}
|
|
/>
|
|
</TabsContent>
|
|
</ScrollArea>
|
|
</Tabs>
|
|
|
|
<DialogFooter>
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => onOpenChange(false)}
|
|
className="bg-[#222] border-[#444] text-neutral-300 hover:bg-[#333] hover:text-white"
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
onClick={handleSave}
|
|
className="bg-emerald-400 text-black hover:bg-[#00cc7d]"
|
|
disabled={!values.name || isLoading}
|
|
>
|
|
{isLoading && (
|
|
<div className="animate-spin h-4 w-4 border-2 border-black border-t-transparent rounded-full mr-2"></div>
|
|
)}
|
|
{initialValues.id ? "Save Changes" : "Add Agent"}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* MCP Dialog */}
|
|
<MCPDialog
|
|
open={mcpDialogOpen}
|
|
onOpenChange={setMcpDialogOpen}
|
|
onSave={handleSaveMCP}
|
|
availableMCPs={availableMCPs}
|
|
selectedMCP={
|
|
availableMCPs.find((m) => selectedMCP?.id === m.id) || null
|
|
}
|
|
initialEnvs={selectedMCP?.envs || {}}
|
|
initialTools={selectedMCP?.tools || []}
|
|
clientId={clientId}
|
|
/>
|
|
|
|
{/* Custom MCP Dialog */}
|
|
<CustomMCPDialog
|
|
open={customMcpDialogOpen}
|
|
onOpenChange={setCustomMcpDialogOpen}
|
|
onSave={handleSaveCustomMCP}
|
|
initialCustomMCP={selectedCustomMCP}
|
|
clientId={clientId}
|
|
/>
|
|
</>
|
|
);
|
|
}
|