Commit inicial - upload de todos os arquivos da pasta

This commit is contained in:
2026-01-04 16:14:31 -03:00
commit e3ed1a734b
310 changed files with 62749 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
/*
┌──────────────────────────────────────────────────────────────────────────────┐
│ @author: Davidson Gomes │
│ @file: /app/agents/config/A2AAgentConfig.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 { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
interface A2AAgentConfigProps {
values: {
agent_card_url?: string;
};
onChange: (values: any) => void;
}
export function A2AAgentConfig({ values, onChange }: A2AAgentConfigProps) {
return (
<div className="space-y-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="agent_card_url" className="text-right text-neutral-300">
Agent Card URL
</Label>
<Input
id="agent_card_url"
value={values.agent_card_url || ""}
onChange={(e) =>
onChange({
...values,
agent_card_url: e.target.value,
})
}
placeholder="https://example.com/.well-known/agent-card.json"
className="col-span-3 bg-[#222] border-[#444] text-white"
/>
</div>
<div className="pl-[25%] text-sm text-neutral-400">
<p>
Provide the full URL for the JSON file of the Agent Card that describes
this agent.
</p>
<p className="mt-2">
Agent Cards contain metadata, capabilities descriptions and supported
protocols.
</p>
</div>
</div>
);
}

View File

@@ -0,0 +1,367 @@
/*
┌──────────────────────────────────────────────────────────────────────────────┐
│ @author: Davidson Gomes │
│ @file: /app/agents/config/LLMAgentConfig.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 { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { ApiKey } from "@/services/agentService";
import { Plus, Maximize2, Save } from "lucide-react";
import { useEffect, useState } from "react";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
interface ModelOption {
value: string;
label: string;
provider: string;
}
interface LLMAgentConfigProps {
apiKeys: ApiKey[];
availableModels: ModelOption[];
values: {
model?: string;
api_key_id?: string;
instruction?: string;
role?: string;
goal?: string;
};
onChange: (values: any) => void;
onOpenApiKeysDialog: () => void;
}
export function LLMAgentConfig({
apiKeys,
availableModels,
values,
onChange,
onOpenApiKeysDialog,
}: LLMAgentConfigProps) {
const [instructionText, setInstructionText] = useState(values.instruction || "");
const [isInstructionModalOpen, setIsInstructionModalOpen] = useState(false);
const [expandedInstructionText, setExpandedInstructionText] = useState("");
useEffect(() => {
setInstructionText(values.instruction || "");
}, [values.instruction]);
const handleInstructionChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const newValue = e.target.value;
setInstructionText(newValue);
onChange({
...values,
instruction: newValue,
});
};
const handleExpandInstruction = () => {
setExpandedInstructionText(instructionText);
setIsInstructionModalOpen(true);
};
const handleSaveExpandedInstruction = () => {
setInstructionText(expandedInstructionText);
onChange({
...values,
instruction: expandedInstructionText,
});
setIsInstructionModalOpen(false);
};
return (
<div className="space-y-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="role" className="text-right text-neutral-300">
Role
</Label>
<div className="col-span-3">
<Input
id="role"
value={values.role || ""}
onChange={(e) =>
onChange({
...values,
role: e.target.value,
})
}
placeholder="Ex: Research Assistant, Customer Support, etc."
className="bg-[#222] border-[#444] text-white"
/>
<div className="mt-1 text-xs text-neutral-400">
<span className="inline-block h-3 w-3 mr-1"></span>
<span>Define the role or persona that the agent will assume</span>
</div>
</div>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="goal" className="text-right text-neutral-300">
Goal
</Label>
<div className="col-span-3">
<Input
id="goal"
value={values.goal || ""}
onChange={(e) =>
onChange({
...values,
goal: e.target.value,
})
}
placeholder="Ex: Find and organize information, Assist customers with inquiries, etc."
className="bg-[#222] border-[#444] text-white"
/>
<div className="mt-1 text-xs text-neutral-400">
<span className="inline-block h-3 w-3 mr-1"></span>
<span>Define the main objective or purpose of this agent</span>
</div>
</div>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="api_key" className="text-right text-neutral-300">
API Key
</Label>
<div className="col-span-3 space-y-4">
<div className="flex items-center">
<Select
value={values.api_key_id || ""}
onValueChange={(value) =>
onChange({
...values,
api_key_id: value,
})
}
>
<SelectTrigger className="flex-1 bg-[#222] border-[#444] text-white">
<SelectValue placeholder="Select an API key" />
</SelectTrigger>
<SelectContent className="bg-[#222] border-[#444] text-white">
{apiKeys.length > 0 ? (
apiKeys
.filter((key) => key.is_active !== false)
.map((key) => (
<SelectItem
key={key.id}
value={key.id}
className="data-[selected]:bg-[#333] data-[highlighted]:bg-[#333] !text-white focus:!text-white hover:text-emerald-400 data-[selected]:!text-emerald-400"
>
<div className="flex items-center">
<span>{key.name}</span>
<Badge className="ml-2 bg-[#333] text-emerald-400 text-xs">
{key.provider}
</Badge>
</div>
</SelectItem>
))
) : (
<div className="text-neutral-500 px-2 py-1.5 pl-8">
No API keys available
</div>
)}
</SelectContent>
</Select>
<Button
variant="ghost"
size="sm"
onClick={onOpenApiKeysDialog}
className="ml-2 bg-[#222] text-emerald-400 hover:bg-[#333]"
>
<Plus className="h-4 w-4" />
</Button>
</div>
{apiKeys.length === 0 && (
<div className="flex items-center text-xs text-neutral-400">
<span className="inline-block h-3 w-3 mr-1 text-neutral-400">i</span>
<span>
You need to{" "}
<Button
variant="link"
onClick={onOpenApiKeysDialog}
className="h-auto p-0 text-xs text-emerald-400"
>
register API keys
</Button>{" "}
before creating an agent.
</span>
</div>
)}
</div>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="model" className="text-right text-neutral-300">
Model
</Label>
<Select
value={values.model}
onValueChange={(value) =>
onChange({
...values,
model: value,
})
}
>
<SelectTrigger className="col-span-3 bg-[#222] border-[#444] text-white">
<SelectValue placeholder="Select the model" />
</SelectTrigger>
<SelectContent className="bg-[#222] border-[#444] text-white p-0">
<div className="sticky top-0 z-10 p-2 bg-[#222] border-b border-[#444]">
<Input
placeholder="Search models..."
className="bg-[#333] border-[#444] text-white h-8"
onChange={(e) => {
const searchQuery = e.target.value.toLowerCase();
const items = document.querySelectorAll('[data-model-item="true"]');
items.forEach((item) => {
const text = item.textContent?.toLowerCase() || '';
if (text.includes(searchQuery)) {
(item as HTMLElement).style.display = 'flex';
} else {
(item as HTMLElement).style.display = 'none';
}
});
}}
/>
</div>
<div className="max-h-[200px] overflow-y-auto py-1">
{availableModels
.filter((model) => {
if (!values.api_key_id) return true;
const selectedKey = apiKeys.find(
(key) => key.id === values.api_key_id
);
if (!selectedKey) return true;
return model.provider === selectedKey.provider;
})
.map((model) => (
<SelectItem
key={model.value}
value={model.value}
className="data-[selected]:bg-[#333] data-[highlighted]:bg-[#333] !text-white focus:!text-white hover:text-emerald-400 data-[selected]:!text-emerald-400"
data-model-item="true"
>
{model.label}
</SelectItem>
))}
</div>
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="instruction" className="text-right text-neutral-300">
Instructions
</Label>
<div className="col-span-3">
<div className="relative">
<Textarea
id="instruction"
value={instructionText}
onChange={handleInstructionChange}
className="w-full bg-[#222] border-[#444] text-white pr-10"
rows={4}
onClick={handleExpandInstruction}
/>
<button
type="button"
className="absolute top-3 right-5 text-neutral-400 hover:text-emerald-400 focus:outline-none"
onClick={handleExpandInstruction}
>
<Maximize2 className="h-4 w-4" />
</button>
</div>
<div className="mt-1 text-xs text-neutral-400">
<span className="inline-block h-3 w-3 mr-1"></span>
<span>
Characters like {"{"} and {"}"} or {"{{"} and {"}}"} are automatically escaped to avoid errors in Python.
<span className="ml-2 text-emerald-400">Click to expand editor.</span>
</span>
</div>
</div>
</div>
{/* Expanded Instruction Modal */}
<Dialog open={isInstructionModalOpen} onOpenChange={setIsInstructionModalOpen}>
<DialogContent className="sm:max-w-[1200px] max-h-[90vh] bg-[#1a1a1a] border-[#333] overflow-hidden flex flex-col">
<DialogHeader>
<DialogTitle className="text-white">Agent Instructions</DialogTitle>
</DialogHeader>
<div className="flex-1 overflow-hidden flex flex-col min-h-[60vh]">
<Textarea
value={expandedInstructionText}
onChange={(e) => setExpandedInstructionText(e.target.value)}
className="flex-1 min-h-full bg-[#222] border-[#444] text-white p-4 focus:border-emerald-400 focus:ring-emerald-400 focus:ring-opacity-50 resize-none"
placeholder="Enter detailed instructions for the agent..."
/>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => setIsInstructionModalOpen(false)}
className="bg-[#222] border-[#444] text-neutral-300 hover:bg-[#333] hover:text-white"
>
Cancel
</Button>
<Button
onClick={handleSaveExpandedInstruction}
className="bg-emerald-400 text-black hover:bg-[#00cc7d]"
>
<Save className="h-4 w-4 mr-2" />
Save Instructions
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}

View File

@@ -0,0 +1,133 @@
/*
┌──────────────────────────────────────────────────────────────────────────────┐
│ @author: Davidson Gomes │
│ @file: /app/agents/config/LoopAgentConfig.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 { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { Agent } from "@/types/agent";
import { ArrowRight } from "lucide-react";
interface LoopAgentConfigProps {
values: {
config?: {
sub_agents?: string[];
max_iterations?: number;
};
};
onChange: (values: any) => void;
agents: Agent[];
getAgentNameById: (id: string) => string;
}
export function LoopAgentConfig({
values,
onChange,
agents,
getAgentNameById,
}: LoopAgentConfigProps) {
const handleMaxIterationsChange = (value: string) => {
const maxIterations = parseInt(value);
onChange({
...values,
config: {
...values.config,
max_iterations: isNaN(maxIterations) ? undefined : maxIterations,
},
});
};
return (
<div className="space-y-6">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="max_iterations" className="text-right text-neutral-300">
Max. Iterations
</Label>
<Input
id="max_iterations"
type="number"
min={1}
max={100}
value={values.config?.max_iterations || ""}
onChange={(e) => handleMaxIterationsChange(e.target.value)}
className="col-span-3 bg-[#222] border-[#444] text-white"
/>
</div>
<div className="border border-[#444] rounded-md p-4 bg-[#222]">
<h3 className="text-sm font-medium text-white mb-4">
Execution Order of Agents
</h3>
{values.config?.sub_agents && values.config.sub_agents.length > 0 ? (
<div className="space-y-3">
{values.config.sub_agents.map((agentId, index) => (
<div
key={agentId}
className="flex items-center space-x-2 bg-[#2a2a2a] p-3 rounded-md"
>
<div className="flex-1">
<div className="font-medium text-white">
{getAgentNameById(agentId)}
</div>
<div className="text-sm text-neutral-400">
Executed on{" "}
<Badge className="bg-[#333] text-emerald-400 border-none">
Position {index + 1}
</Badge>
</div>
</div>
</div>
))}
</div>
) : (
<div className="text-center py-6 text-neutral-400">
Add agents in the "Sub-Agents" tab to define the execution order
</div>
)}
<div className="mt-3 text-sm text-neutral-400">
<p>
The agents will be executed sequentially in the order listed above.
The output of each agent will be provided as input to the next
agent in the sequence.
</p>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,125 @@
/*
┌──────────────────────────────────────────────────────────────────────────────┐
│ @author: Davidson Gomes │
│ @file: /app/agents/config/ParallelAgentConfig.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 { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
import { Agent } from "@/types/agent";
import { GitBranch } from "lucide-react";
interface ParallelAgentConfigProps {
values: {
config?: {
sub_agents?: string[];
aggregation_method?: string;
timeout_seconds?: number;
custom_mcp_servers?: any[];
wait_for_all?: boolean;
};
};
onChange: (values: any) => void;
agents: Agent[];
getAgentNameById: (id: string) => string;
}
export function ParallelAgentConfig({
values,
onChange,
agents,
getAgentNameById,
}: ParallelAgentConfigProps) {
const aggregationMethods = [
{ value: "merge", label: "Merge all responses" },
{ value: "first", label: "Use only the first response" },
{ value: "last", label: "Use only the last response" },
{ value: "custom", label: "Custom aggregation" },
];
const handleTimeoutChange = (value: string) => {
const timeout = parseInt(value);
onChange({
...values,
config: {
...values.config,
timeout_seconds: isNaN(timeout) ? undefined : timeout,
},
});
};
return (
<div className="space-y-6">
<div className="border border-[#444] rounded-md p-4 bg-[#222]">
<h3 className="text-sm font-medium text-white mb-4">
Agents in Parallel
</h3>
{values.config?.sub_agents && values.config.sub_agents.length > 0 ? (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{values.config.sub_agents.map((agentId) => (
<div
key={agentId}
className="flex items-center space-x-2 bg-[#2a2a2a] p-3 rounded-md"
>
<GitBranch className="h-5 w-5 text-emerald-400" />
<div className="flex-1">
<div className="font-medium text-white truncate">
{getAgentNameById(agentId)}
</div>
</div>
</div>
))}
</div>
) : (
<div className="text-center py-6 text-neutral-400">
Add agents in the "Sub-Agents" tab to execute in parallel
</div>
)}
<div className="mt-3 text-sm text-neutral-400">
<p>
All listed agents will be executed simultaneously with the same
input. The responses will be aggregated according to the selected
method.
</p>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,120 @@
/*
┌──────────────────────────────────────────────────────────────────────────────┐
│ @author: Davidson Gomes │
│ @file: /app/agents/config/SequentialAgentConfig.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 { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { Agent } from "@/types/agent";
import { ArrowRight } from "lucide-react";
interface SequentialAgentConfigProps {
values: {
config?: {
sub_agents?: string[];
max_iterations?: number;
custom_mcp_servers?: any[];
};
};
onChange: (values: any) => void;
agents: Agent[];
getAgentNameById: (id: string) => string;
}
export function SequentialAgentConfig({
values,
onChange,
agents,
getAgentNameById,
}: SequentialAgentConfigProps) {
const handleMaxIterationsChange = (value: string) => {
const maxIterations = parseInt(value);
onChange({
...values,
config: {
...values.config,
max_iterations: isNaN(maxIterations) ? undefined : maxIterations,
},
});
};
return (
<div className="space-y-6">
<div className="border border-[#444] rounded-md p-4 bg-[#222]">
<h3 className="text-sm font-medium text-white mb-4">
Execution Order of Agents
</h3>
{values.config?.sub_agents && values.config.sub_agents.length > 0 ? (
<div className="space-y-3">
{values.config.sub_agents.map((agentId, index) => (
<div
key={agentId}
className="flex items-center space-x-2 bg-[#2a2a2a] p-3 rounded-md"
>
<div className="flex-1">
<div className="font-medium text-white">
{getAgentNameById(agentId)}
</div>
<div className="text-sm text-neutral-400">
Executed on{" "}
<Badge className="bg-[#333] text-emerald-400 border-none">
Position {index + 1}
</Badge>
</div>
</div>
</div>
))}
</div>
) : (
<div className="text-center py-6 text-neutral-400">
Add agents in the "Sub-Agents" tab to define the execution order
</div>
)}
<div className="mt-3 text-sm text-neutral-400">
<p>
The agents will be executed sequentially in the order listed above.
The output of each agent will be provided as input to the next
agent in the sequence.
</p>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,801 @@
/*
┌──────────────────────────────────────────────────────────────────────────────┐
│ @author: Davidson Gomes │
│ @file: /app/agents/config/TaskAgentConfig.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 { Agent, TaskConfig } from "@/types/agent";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import {
Maximize2,
Save,
X,
ArrowDown,
List,
Search,
Edit,
PenTool,
} from "lucide-react";
import { useState, useEffect } from "react";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import { Checkbox } from "@/components/ui/checkbox";
interface TaskAgentConfigProps {
values: Partial<Agent>;
onChange: (values: Partial<Agent>) => void;
agents: Agent[];
getAgentNameById: (id: string) => string;
singleTask?: boolean;
}
const getAgentTypeLabel = (type: string): string => {
const typeMap: Record<string, string> = {
llm: "LLM",
a2a: "A2A",
sequential: "Sequential",
parallel: "Parallel",
loop: "Loop",
workflow: "Workflow",
task: "Task",
};
return typeMap[type] || type;
};
const getAgentTypeColor = (type: string): string => {
const colorMap: Record<string, string> = {
llm: "bg-blue-800 text-white",
a2a: "bg-purple-800 text-white",
sequential: "bg-orange-800 text-white",
parallel: "bg-green-800 text-white",
loop: "bg-pink-800 text-white",
workflow: "bg-yellow-800 text-black",
task: "bg-green-800 text-white",
};
return colorMap[type] || "bg-neutral-800 text-white";
};
export function TaskAgentConfig({
values,
onChange,
agents,
getAgentNameById,
singleTask = false,
}: TaskAgentConfigProps) {
const [newTask, setNewTask] = useState<TaskConfig>({
agent_id: "",
description: "",
expected_output: "",
enabled_tools: [],
});
const [taskAgentSearchQuery, setTaskAgentSearchQuery] = useState<string>("");
const [filteredTaskAgents, setFilteredTaskAgents] = useState<Agent[]>([]);
const [isDescriptionModalOpen, setIsDescriptionModalOpen] = useState(false);
const [expandedDescription, setExpandedDescription] = useState("");
const [editingTaskIndex, setEditingTaskIndex] = useState<number | null>(null);
const [isEditing, setIsEditing] = useState(false);
const [toolSearchQuery, setToolSearchQuery] = useState<string>("");
const [filteredTools, setFilteredTools] = useState<{id: string, name: string}[]>([]);
const [isToolsModalOpen, setIsToolsModalOpen] = useState(false);
const [tempSelectedTools, setTempSelectedTools] = useState<string[]>([]);
useEffect(() => {
if (isToolsModalOpen) {
if (isEditing && editingTaskIndex !== null && values.config?.tasks) {
setTempSelectedTools(
values.config.tasks[editingTaskIndex]?.enabled_tools || []
);
} else {
setTempSelectedTools([...newTask.enabled_tools || []]);
}
}
}, [isToolsModalOpen]);
const getAvailableTaskAgents = (currentTaskAgentId?: string) =>
agents.filter(
(agent) =>
agent.id !== values.id &&
(!values.config?.tasks?.some((task) => task.agent_id === agent.id) ||
agent.id === currentTaskAgentId)
);
useEffect(() => {
const currentTaskAgentId =
isEditing && editingTaskIndex !== null && values.config?.tasks
? values.config.tasks[editingTaskIndex].agent_id
: undefined;
const availableAgents = getAvailableTaskAgents(currentTaskAgentId);
if (taskAgentSearchQuery.trim() === "") {
setFilteredTaskAgents(availableAgents);
} else {
const query = taskAgentSearchQuery.toLowerCase();
setFilteredTaskAgents(
availableAgents.filter(
(agent) =>
agent.name.toLowerCase().includes(query) ||
(agent.description?.toLowerCase() || "").includes(query)
)
);
}
}, [
taskAgentSearchQuery,
agents,
values.config?.tasks,
isEditing,
editingTaskIndex,
]);
useEffect(() => {
// Reset editing state when values change externally
if (!isEditing) {
const currentTaskAgentId =
editingTaskIndex !== null && values.config?.tasks
? values.config.tasks[editingTaskIndex]?.agent_id
: undefined;
setFilteredTaskAgents(getAvailableTaskAgents(currentTaskAgentId));
}
}, [agents, values.config?.tasks]);
const getAvailableTools = () => {
if (!values.config?.tasks || values.config.tasks.length === 0) {
return [];
}
const taskAgentIds = values.config.tasks.map(task => task.agent_id);
const toolsList: {id: string, name: string}[] = [];
const toolsMap: Record<string, boolean> = {};
taskAgentIds.forEach(agentId => {
const agent = agents.find(a => a.id === agentId);
if (agent?.type === "llm" && agent.config?.tools) {
agent.config.tools.forEach(tool => {
if (!toolsMap[tool.id]) {
toolsList.push({ id: tool.id, name: tool.id });
toolsMap[tool.id] = true;
}
});
}
if (agent?.type === "llm" && agent.config?.mcp_servers) {
agent.config.mcp_servers.forEach(mcp => {
if (mcp.tools) {
mcp.tools.forEach(toolId => {
if (!toolsMap[toolId]) {
toolsList.push({ id: toolId, name: toolId });
toolsMap[toolId] = true;
}
});
}
});
}
});
return toolsList;
};
useEffect(() => {
const availableTools = getAvailableTools();
if (toolSearchQuery.trim() === "") {
setFilteredTools(availableTools);
} else {
const query = toolSearchQuery.toLowerCase();
setFilteredTools(
availableTools.filter(
(tool) =>
tool.name.toLowerCase().includes(query) ||
tool.id.toLowerCase().includes(query)
)
);
}
}, [toolSearchQuery, values.config?.tasks, agents]);
const handleAddTask = () => {
if (!newTask.agent_id || !newTask.description) {
return;
}
if (isEditing && editingTaskIndex !== null) {
const tasks = [...(values.config?.tasks || [])];
tasks[editingTaskIndex] = { ...newTask };
onChange({
...values,
config: {
...(values.config || {}),
tasks,
},
});
setIsEditing(false);
setEditingTaskIndex(null);
} else {
const tasks = [...(values.config?.tasks || [])];
if (singleTask) {
tasks.splice(0, tasks.length, newTask);
} else {
tasks.push(newTask);
}
onChange({
...values,
config: {
...(values.config || {}),
tasks,
},
});
}
setNewTask({
agent_id: "",
description: "",
expected_output: "",
enabled_tools: [],
});
};
const handleEditTask = (index: number) => {
const task = values.config?.tasks?.[index];
if (task) {
setNewTask({ ...task });
setIsEditing(true);
setEditingTaskIndex(index);
}
};
const handleCancelEdit = () => {
setNewTask({
agent_id: "",
description: "",
expected_output: "",
enabled_tools: [],
});
setIsEditing(false);
setEditingTaskIndex(null);
};
const handleRemoveTask = (index: number) => {
if (editingTaskIndex === index) {
handleCancelEdit();
}
const tasks = [...(values.config?.tasks || [])];
tasks.splice(index, 1);
onChange({
...values,
config: {
...(values.config || {}),
tasks,
},
});
};
const handleDescriptionChange = (
e: React.ChangeEvent<HTMLTextAreaElement>
) => {
const newValue = e.target.value;
setNewTask({
...newTask,
description: newValue,
});
};
const handleExpandDescription = () => {
setExpandedDescription(newTask.description);
setIsDescriptionModalOpen(true);
};
const handleSaveExpandedDescription = () => {
setNewTask({
...newTask,
description: expandedDescription,
});
setIsDescriptionModalOpen(false);
};
const handleToggleTool = (toolId: string) => {
const index = tempSelectedTools.indexOf(toolId);
if (index > -1) {
setTempSelectedTools(tempSelectedTools.filter(id => id !== toolId));
} else {
setTempSelectedTools([...tempSelectedTools, toolId]);
}
};
const isToolEnabled = (toolId: string) => {
return tempSelectedTools.includes(toolId);
};
const handleSaveTools = () => {
if (isEditing && editingTaskIndex !== null && values.config?.tasks) {
const tasks = [...(values.config?.tasks || [])];
const updatedTask = {
...tasks[editingTaskIndex],
enabled_tools: [...tempSelectedTools]
};
tasks[editingTaskIndex] = updatedTask;
const newConfig = {
...(values.config || {}),
tasks: tasks
};
onChange({
...values,
config: newConfig
});
} else if (newTask.agent_id) {
const updatedNewTask = {
...newTask,
enabled_tools: [...tempSelectedTools]
};
setNewTask(updatedNewTask);
}
setIsToolsModalOpen(false);
};
const renderAgentTypeBadge = (agentId: string) => {
const agent = agents.find((a) => a.id === agentId);
if (!agent) {
return null;
}
return (
<Badge className={`ml-2 ${getAgentTypeColor(agent.type)} text-xs`}>
{getAgentTypeLabel(agent.type)}
</Badge>
);
};
return (
<div className="space-y-8">
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium text-white flex items-center">
<List className="mr-2 h-5 w-5 text-emerald-400" />
{singleTask ? "Task" : "Tasks"}
</h3>
</div>
<div className="border border-[#444] rounded-md p-4 bg-[#222]">
<p className="text-sm text-neutral-400 mb-4">
{singleTask
? "Configure the task that will be executed by the agent."
: "Configure the sequential tasks that will be executed by the team of agents."}
</p>
{values.config?.tasks && values.config.tasks.length > 0 ? (
<div className="space-y-4 mb-4">
{values.config.tasks.map((task, index) => (
<div
key={index}
className={`border border-[#333] rounded-md p-3 ${
editingTaskIndex === index ? "bg-[#1e3a3a]" : "bg-[#2a2a2a]"
}`}
>
<div className="flex items-start justify-between mb-2">
<div className="flex-1">
<div className="flex items-center">
<span className="inline-flex items-center justify-center rounded-full bg-[#333] px-2 py-1 text-xs text-white mr-2">
{index + 1}
</span>
<h4 className="font-medium text-white flex items-center">
{getAgentNameById(task.agent_id)}
{renderAgentTypeBadge(task.agent_id)}
</h4>
</div>
<p className="text-sm text-neutral-300 mt-1">
{task.description}
</p>
{task.expected_output && (
<div className="mt-2">
<span className="text-xs text-neutral-400">
Expected output:
</span>
<Badge
variant="outline"
className="ml-2 bg-[#333] text-emerald-400 border-emerald-400/30"
>
{task.expected_output}
</Badge>
</div>
)}
{task.enabled_tools && task.enabled_tools.length > 0 && (
<div className="mt-2">
<span className="text-xs text-neutral-400">
Enabled tools:
</span>
<div className="flex flex-wrap gap-1 mt-1">
{task.enabled_tools.map((toolId) => (
<Badge
key={toolId}
className="bg-[#333] text-emerald-400 border border-emerald-400/30 text-xs"
>
{toolId}
</Badge>
))}
</div>
</div>
)}
</div>
<div className="flex">
<Button
variant="ghost"
size="sm"
onClick={() => handleEditTask(index)}
className="text-neutral-400 hover:text-emerald-400 hover:bg-[#333] mr-1"
>
<Edit className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleRemoveTask(index)}
className="text-red-500 hover:text-red-400 hover:bg-[#333]"
>
<X className="h-4 w-4" />
</Button>
</div>
</div>
{!singleTask &&
index < (values.config?.tasks?.length || 0) - 1 && (
<div className="flex justify-center my-2">
<ArrowDown className="h-4 w-4 text-neutral-400" />
</div>
)}
</div>
))}
</div>
) : (
<div className="text-center py-4 mb-4 bg-[#2a2a2a] rounded-md">
<p className="text-neutral-400">No tasks configured</p>
<p className="text-xs text-neutral-500">
{singleTask
? "Add a task to define the agent's behavior"
: "Add tasks to define the workflow of the team"}
</p>
</div>
)}
{(!singleTask ||
!values.config?.tasks ||
values.config.tasks.length === 0 ||
isEditing) && (
<div className="space-y-3 border-t border-[#333] pt-4">
<h4 className="text-sm font-medium text-white flex items-center justify-between">
<span>
{isEditing
? "Edit task"
: `Add ${singleTask ? "one" : "new"} task`}
</span>
{isEditing && (
<Button
variant="ghost"
size="sm"
onClick={handleCancelEdit}
className="text-neutral-400 hover:text-emerald-400 hover:bg-[#333]"
>
<X className="h-4 w-4 mr-1" /> Cancel
</Button>
)}
</h4>
<div className="grid grid-cols-3 gap-3">
<div>
<Label
htmlFor="agent_id"
className="text-xs text-neutral-400 mb-1 block"
>
Agent
</Label>
<Select
value={newTask.agent_id}
onValueChange={(value) =>
setNewTask({ ...newTask, agent_id: value })
}
>
<SelectTrigger className="bg-[#2a2a2a] border-[#444] text-white">
<SelectValue placeholder="Select agent" />
</SelectTrigger>
<SelectContent className="bg-[#2a2a2a] border-[#444] text-white p-0">
<div className="sticky top-0 z-10 p-2 bg-[#2a2a2a] border-b border-[#444]">
<div className="relative">
<Search className="absolute left-2 top-1/2 transform -translate-y-1/2 h-4 w-4 text-neutral-400" />
<Input
placeholder="Search agents..."
className="bg-[#333] border-[#444] text-white h-8 pl-8"
value={taskAgentSearchQuery}
onChange={(e) =>
setTaskAgentSearchQuery(e.target.value)
}
/>
</div>
</div>
<div className="max-h-[200px] overflow-y-auto py-1">
{filteredTaskAgents.length > 0 ? (
filteredTaskAgents.map((agent) => (
<SelectItem
key={agent.id}
value={agent.id}
className="hover:bg-[#333] focus:bg-[#333] flex items-center justify-between px-2"
data-agent-item="true"
>
<div className="flex items-center">
<span className="mr-2">{agent.name}</span>
<Badge
className={`${getAgentTypeColor(
agent.type
)} text-xs`}
>
{getAgentTypeLabel(agent.type)}
</Badge>
</div>
</SelectItem>
))
) : (
<div className="text-neutral-500 px-4 py-2 text-center">
No agents found
</div>
)}
</div>
</SelectContent>
</Select>
</div>
<div className="col-span-2">
<Label
htmlFor="description"
className="text-xs text-neutral-400 mb-1 block"
>
Task description
</Label>
<div className="relative">
<Textarea
id="description"
value={newTask.description}
onChange={handleDescriptionChange}
className="w-full bg-[#2a2a2a] border-[#444] text-white pr-10"
rows={3}
onClick={handleExpandDescription}
/>
<button
type="button"
className="absolute top-3 right-5 text-neutral-400 hover:text-emerald-400 focus:outline-none"
onClick={handleExpandDescription}
>
<Maximize2 className="h-4 w-4" />
</button>
</div>
<div className="mt-1 text-xs text-neutral-400">
<span className="inline-block h-3 w-3 mr-1"></span>
<span>
Use {"{"}content{"}"} to insert the user's input.
<span className="ml-2 text-emerald-400">
Click to expand editor.
</span>
</span>
</div>
</div>
</div>
<div>
<Label
htmlFor="expected_output"
className="text-xs text-neutral-400 mb-1 block"
>
Expected output (optional)
</Label>
<Input
id="expected_output"
placeholder="Ex: JSON report, List of recommendations, etc."
value={newTask.expected_output}
onChange={(e) =>
setNewTask({ ...newTask, expected_output: e.target.value })
}
className="bg-[#2a2a2a] border-[#444] text-white"
/>
</div>
{newTask.enabled_tools && newTask.enabled_tools.length > 0 && (
<div className="mt-3">
<Label className="text-xs text-neutral-400 mb-1 block">
Selected tools:
</Label>
<div className="flex flex-wrap gap-1">
{newTask.enabled_tools.map((toolId) => (
<Badge
key={toolId}
className="bg-[#333] text-emerald-400 border border-emerald-400/30"
>
{toolId}
</Badge>
))}
</div>
</div>
)}
<div className="flex items-center justify-between mt-3">
<Button
type="button"
variant="outline"
size="sm"
onClick={() => {
if (newTask.agent_id) setIsToolsModalOpen(true);
}}
disabled={!newTask.agent_id}
className="border-emerald-400 text-emerald-400 hover:bg-emerald-400/10 px-3"
>
<PenTool className="h-4 w-4 mr-2" />
Configure tools
</Button>
<Button
onClick={handleAddTask}
disabled={!newTask.agent_id || !newTask.description}
className="bg-[#222] text-emerald-400 border border-emerald-400 hover:bg-emerald-400/10"
>
<Save className="h-4 w-4 mr-1" />{" "}
{isEditing ? "Update task" : "Add task"}
</Button>
</div>
</div>
)}
</div>
</div>
<Dialog
open={isDescriptionModalOpen}
onOpenChange={setIsDescriptionModalOpen}
>
<DialogContent className="sm:max-w-[1200px] max-h-[90vh] bg-[#1a1a1a] border-[#333] overflow-hidden flex flex-col">
<DialogHeader>
<DialogTitle className="text-white">Task Description</DialogTitle>
</DialogHeader>
<div className="flex-1 overflow-hidden flex flex-col min-h-[60vh]">
<Textarea
value={expandedDescription}
onChange={(e) => setExpandedDescription(e.target.value)}
className="flex-1 min-h-full bg-[#222] border-[#444] text-white p-4 focus:border-emerald-400 focus:ring-emerald-400 focus:ring-opacity-50 resize-none"
placeholder="Enter detailed description for the task..."
/>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => setIsDescriptionModalOpen(false)}
className="bg-[#222] border-[#444] text-neutral-300 hover:bg-[#333] hover:text-white"
>
Cancel
</Button>
<Button
onClick={handleSaveExpandedDescription}
className="bg-emerald-400 text-black hover:bg-[#00cc7d]"
>
<Save className="h-4 w-4 mr-2" />
Save description
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<Dialog open={isToolsModalOpen} onOpenChange={setIsToolsModalOpen}>
<DialogContent className="sm:max-w-[600px] max-h-[90vh] bg-[#1a1a1a] border-[#333] overflow-hidden flex flex-col">
<DialogHeader>
<DialogTitle className="text-white">
Available tools
</DialogTitle>
</DialogHeader>
<div className="relative mb-4">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-neutral-400" />
<Input
placeholder="Search tools..."
className="bg-[#222] border-[#444] text-white pl-9"
value={toolSearchQuery}
onChange={(e) => setToolSearchQuery(e.target.value)}
/>
</div>
<div className="flex-1 overflow-y-auto space-y-2 pr-1">
{filteredTools.length > 0 ? (
filteredTools.map((tool) => (
<div
key={tool.id}
className="flex items-center space-x-2 p-2 rounded-md hover:bg-[#333] transition duration-150"
>
<Checkbox
id={tool.id}
checked={isToolEnabled(tool.id)}
onCheckedChange={() => handleToggleTool(tool.id)}
className="border-[#444] data-[state=checked]:bg-emerald-400 data-[state=checked]:text-black"
/>
<Label
htmlFor={tool.id}
className="cursor-pointer text-white flex-1"
>
{tool.name}
</Label>
<Badge className="bg-[#333] text-emerald-400">{tool.id}</Badge>
</div>
))
) : (
<div className="text-center py-8">
<p className="text-neutral-400">No tools available</p>
<p className="text-xs text-neutral-500">
The tools are obtained from the selected agents in the tasks.
</p>
</div>
)}
</div>
<DialogFooter>
<Button
onClick={handleSaveTools}
className="bg-emerald-400 text-black hover:bg-[#00cc7d]"
>
<Save className="h-4 w-4 mr-2" />
Save settings
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}