onOpenChange(false)}
+ />
+
+ {/* Side panel */}
+
+ {/* Header */}
+
+
+
+
+
+ {getAgentTypeIcon(agent.type)}
+
+
+
{agent.name}
+
+
+ {agent.type.toUpperCase()} Agent
+
+ {agent.model && (
+
+ {agent.model}
+
+ )}
+
+
+
+
+
+
+
+
+ Restart chat
+
+
+ onOpenChange(false)}
+ className="p-1.5 rounded-full hover:bg-neutral-700/50 text-neutral-400 hover:text-white transition-colors group relative"
+ >
+
+
+ Close
+
+
+
+
+
+ {agent.description && (
+
+ {agent.description}
+
+ )}
+
+
+ {/* Chat content */}
+
+ {isInitializing ? (
+
+
+
+
+
Initializing connection...
+
+
+
+
+
+
+ ) : messages.length === 0 ? (
+
+
+
+
+
Start the conversation
+
+ Type a message below to begin chatting with {agent.name}
+
+
+ ) : (
+
+ {messages.map((message) => {
+ const messageContent = getMessageText(message);
+ const agentColor = message.author === "user" ? "bg-emerald-500" : "bg-gradient-to-br from-neutral-800 to-neutral-900";
+ const isExpanded = expandedFunctions[message.id] || false;
+
+ return (
+
+ );
+ })}
+
+ {isSending && (
+
+ )}
+
+ )}
+
+
+ {/* Message input */}
+
+
+
+
+ >
+ );
+
+ // Use createPortal to render the modal directly to the document body
+ return typeof document !== 'undefined'
+ ? createPortal(modalContent, document.body)
+ : null;
+}
\ No newline at end of file
diff --git a/frontend/app/agents/workflows/nodes/components/agent/styles.css b/frontend/app/agents/workflows/nodes/components/agent/styles.css
new file mode 100644
index 00000000..19031bc1
--- /dev/null
+++ b/frontend/app/agents/workflows/nodes/components/agent/styles.css
@@ -0,0 +1,68 @@
+/*
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β @author: Davidson Gomes β
+β @file: /app/agents/workflows/nodes/components/agent/styles.css β
+β 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. β
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+*/
+
+.markdown-content {
+ max-width: 100%;
+ overflow-wrap: break-word;
+ word-wrap: break-word;
+ word-break: break-word;
+ hyphens: auto;
+}
+
+.markdown-content pre {
+ white-space: pre-wrap;
+ overflow-x: auto;
+ max-width: 100%;
+}
+
+.markdown-content code {
+ white-space: pre-wrap;
+ word-break: break-all;
+}
+
+.markdown-content p {
+ max-width: 100%;
+ overflow-wrap: break-word;
+}
+
+.markdown-content table {
+ display: block;
+ overflow-x: auto;
+ max-width: 100%;
+ border-collapse: collapse;
+}
+
+.markdown-content img {
+ max-width: 100%;
+ height: auto;
+}
+
+.overflow-wrap-anywhere {
+ overflow-wrap: anywhere;
+}
\ No newline at end of file
diff --git a/frontend/app/agents/workflows/nodes/components/condition/ConditionDialog.tsx b/frontend/app/agents/workflows/nodes/components/condition/ConditionDialog.tsx
new file mode 100644
index 00000000..5fdd172f
--- /dev/null
+++ b/frontend/app/agents/workflows/nodes/components/condition/ConditionDialog.tsx
@@ -0,0 +1,283 @@
+/*
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β @author: Davidson Gomes β
+β @file: /app/agents/workflows/nodes/components/condition/ConditionDialog.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. β
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+*/
+/* eslint-disable no-unused-vars */
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { useState } from "react";
+import { v4 as uuidv4 } from "uuid";
+
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogFooter
+} from "@/components/ui/dialog";
+
+import { ConditionType, ConditionTypeEnum } from "../../nodeFunctions";
+import { Button } from "@/components/ui/button";
+import { Filter, ArrowRight } from "lucide-react";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue
+} from "@/components/ui/select";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Badge } from "@/components/ui/badge";
+
+const conditionTypes = [
+ {
+ id: "previous-output",
+ name: "Previous output",
+ description: "Validate the result returned by the previous node",
+ icon:
,
+ color: "bg-blue-900/30 border-blue-700/50",
+ },
+];
+
+const operators = [
+ { value: "is_defined", label: "is defined" },
+ { value: "is_not_defined", label: "is not defined" },
+ { value: "equals", label: "is equal to" },
+ { value: "not_equals", label: "is not equal to" },
+ { value: "contains", label: "contains" },
+ { value: "not_contains", label: "does not contain" },
+ { value: "starts_with", label: "starts with" },
+ { value: "ends_with", label: "ends with" },
+ { value: "greater_than", label: "is greater than" },
+ { value: "greater_than_or_equal", label: "is greater than or equal to" },
+ { value: "less_than", label: "is less than" },
+ { value: "less_than_or_equal", label: "is less than or equal to" },
+ { value: "matches", label: "matches the regex" },
+ { value: "not_matches", label: "does not match the regex" },
+];
+
+const outputFields = [
+ { value: "content", label: "Content" },
+ { value: "status", label: "Status" },
+];
+
+function ConditionDialog({
+ open,
+ onOpenChange,
+ selectedNode,
+ handleUpdateNode,
+}: {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ selectedNode: any;
+ handleUpdateNode: any;
+}) {
+ const [selectedType, setSelectedType] = useState("previous-output");
+ const [selectedField, setSelectedField] = useState(outputFields[0].value);
+ const [selectedOperator, setSelectedOperator] = useState(operators[0].value);
+ const [comparisonValue, setComparisonValue] = useState("");
+
+ const handleConditionSave = (condition: ConditionType) => {
+ const newConditions = selectedNode.data.conditions
+ ? [...selectedNode.data.conditions]
+ : [];
+ newConditions.push(condition);
+
+ const updatedNode = {
+ ...selectedNode,
+ data: {
+ ...selectedNode.data,
+ conditions: newConditions,
+ },
+ };
+
+ handleUpdateNode(updatedNode);
+ onOpenChange(false);
+ };
+
+ const getOperatorLabel = (value: string) => {
+ return operators.find(op => op.value === value)?.label || value;
+ };
+
+ const getFieldLabel = (value: string) => {
+ return outputFields.find(field => field.value === value)?.label || value;
+ };
+
+ return (
+
+
+
+ Add New Condition
+
+
+
+
+
Condition Type
+
+ {conditionTypes.map((type) => (
+
setSelectedType(type.id)}
+ >
+
+ {type.icon}
+
+
+
{type.name}
+
{type.description}
+
+ {selectedType === type.id && (
+
Selected
+ )}
+
+ ))}
+
+
+
+
+
+
Configuration
+ {selectedType === "previous-output" && (
+
+
Output field
+
+
Operator
+ {!["is_defined", "is_not_defined"].includes(selectedOperator) && (
+ <>
+
+
Value
+ >
+ )}
+
+ )}
+
+
+ {selectedType === "previous-output" && (
+
+
+ Output Field
+
+
+
+
+
+ {outputFields.map((field) => (
+
+ {field.label}
+
+ ))}
+
+
+
+
+
+ Operator
+
+
+
+
+
+ {operators.map((op) => (
+
+ {op.label}
+
+ ))}
+
+
+
+
+ {!["is_defined", "is_not_defined"].includes(selectedOperator) && (
+
+ Comparison Value
+ setComparisonValue(e.target.value)}
+ className="bg-neutral-700 border-neutral-600"
+ />
+
+ )}
+
+
+
Preview
+
+ {getFieldLabel(selectedField)}
+ {" "}
+ {getOperatorLabel(selectedOperator)}
+ {" "}
+ {!["is_defined", "is_not_defined"].includes(selectedOperator) && (
+ "{comparisonValue || "(empty)"}"
+ )}
+
+
+
+ )}
+
+
+
+
+ onOpenChange(false)}
+ className="border-neutral-600 text-neutral-200 hover:bg-neutral-700"
+ >
+ Cancel
+
+ {
+ handleConditionSave({
+ id: uuidv4(),
+ type: ConditionTypeEnum.PREVIOUS_OUTPUT,
+ data: {
+ field: selectedField,
+ operator: selectedOperator,
+ value: comparisonValue,
+ },
+ });
+ }}
+ className="bg-blue-700 hover:bg-blue-600 text-white"
+ >
+ Add Condition
+
+
+
+
+ );
+}
+
+export { ConditionDialog };
diff --git a/frontend/app/agents/workflows/nodes/components/condition/ConditionForm.tsx b/frontend/app/agents/workflows/nodes/components/condition/ConditionForm.tsx
new file mode 100644
index 00000000..ff224f3c
--- /dev/null
+++ b/frontend/app/agents/workflows/nodes/components/condition/ConditionForm.tsx
@@ -0,0 +1,290 @@
+/*
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β @author: Davidson Gomes β
+β @file: /app/agents/workflows/nodes/components/condition/ConditionForm.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. β
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+*/
+import { useEffect, useState } from "react";
+import {
+ Dialog,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+
+import { ConditionType, ConditionTypeEnum } from "../../nodeFunctions";
+import { ConditionDialog } from "./ConditionDialog";
+import { Button } from "@/components/ui/button";
+import { Filter, Trash2, Plus } from "lucide-react";
+import { Badge } from "@/components/ui/badge";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
+
+/* eslint-disable @typescript-eslint/no-explicit-any */
+function ConditionForm({
+ selectedNode,
+ handleUpdateNode,
+}: {
+ selectedNode: any;
+ handleUpdateNode: any;
+ setEdges: any;
+ setIsOpen: any;
+ setSelectedNode: any;
+}) {
+ const [node, setNode] = useState(selectedNode);
+
+ const [conditions, setConditions] = useState
(
+ selectedNode.data.conditions || []
+ );
+
+ const [open, setOpen] = useState(false);
+ const [deleteDialog, setDeleteDialog] = useState(false);
+ const [conditionToDelete, setConditionToDelete] =
+ useState(null);
+
+ useEffect(() => {
+ if (selectedNode) {
+ setNode(selectedNode);
+ setConditions(selectedNode.data.conditions || []);
+ }
+ }, [selectedNode]);
+
+ const handleDelete = (condition: ConditionType) => {
+ setConditionToDelete(condition);
+ setDeleteDialog(true);
+ };
+
+ const confirmDelete = () => {
+ if (!conditionToDelete) return;
+
+ const newConditions = conditions.filter(
+ (c) => c.id !== conditionToDelete.id
+ );
+ setConditions(newConditions);
+ handleUpdateNode({
+ ...node,
+ data: { ...node.data, conditions: newConditions },
+ });
+ setDeleteDialog(false);
+ setConditionToDelete(null);
+ };
+
+ const renderCondition = (condition: ConditionType) => {
+ if (condition.type === ConditionTypeEnum.PREVIOUS_OUTPUT) {
+ type OperatorType =
+ | "is_defined"
+ | "is_not_defined"
+ | "equals"
+ | "not_equals"
+ | "contains"
+ | "not_contains"
+ | "starts_with"
+ | "ends_with"
+ | "greater_than"
+ | "greater_than_or_equal"
+ | "less_than"
+ | "less_than_or_equal"
+ | "matches"
+ | "not_matches";
+
+ const operatorText: Record = {
+ is_defined: "is defined",
+ is_not_defined: "is not defined",
+ equals: "is equal to",
+ not_equals: "is not equal to",
+ contains: "contains",
+ not_contains: "does not contain",
+ starts_with: "starts with",
+ ends_with: "ends with",
+ greater_than: "is greater than",
+ greater_than_or_equal: "is greater than or equal to",
+ less_than: "is less than",
+ less_than_or_equal: "is less than or equal to",
+ matches: "matches the regex",
+ not_matches: "does not match the regex",
+ };
+
+ return (
+
+
+
+
+
+
+
+
Condition
+ handleDelete(condition)}
+ className="h-7 w-7 text-neutral-400 opacity-0 group-hover:opacity-100 hover:text-red-500 hover:bg-red-900/20"
+ >
+
+
+
+
+
+ Field
+
+ {condition.data.field}
+
+
+ {operatorText[condition.data.operator as OperatorType]}
+ {!["is_defined", "is_not_defined"].includes(condition.data.operator) && (
+
+ "{condition.data.value}"
+
+ )}
+
+
+
+
+ );
+ }
+ return null;
+ };
+
+ return (
+
+
+
+
+
Logic Type
+
+ {node.data.type === "or" ? "ANY" : "ALL"}
+
+
+
{
+ const updatedNode = {
+ ...node,
+ data: {
+ ...node.data,
+ type: value,
+ },
+ };
+ setNode(updatedNode);
+ handleUpdateNode(updatedNode);
+ }}
+ >
+
+
+
+
+ ALL (AND)
+ ANY (OR)
+
+
+
+
+ {node.data.type === "or"
+ ? "Any of the following conditions must be true to proceed."
+ : "All of the following conditions must be true to proceed."}
+
+
+
+
+
+
+
Conditions
+
setOpen(true)}
+ >
+
+ Add Condition
+
+
+
+ {conditions.length > 0 ? (
+
+ {conditions.map((condition) => renderCondition(condition))}
+
+ ) : (
+
setOpen(true)}
+ className="flex flex-col items-center justify-center p-6 rounded-lg border-2 border-dashed border-neutral-700 hover:border-blue-600/50 hover:bg-neutral-800/50 transition-colors cursor-pointer text-center"
+ >
+
+
No conditions yet
+
Click to add a condition
+
+ )}
+
+
+
+
+
+
+
+
+ Confirm Delete
+
+
+
Are you sure you want to delete this condition?
+
+
+ {
+ setDeleteDialog(false);
+ setConditionToDelete(null);
+ }}
+ >
+ Cancel
+
+
+ Delete
+
+
+
+
+
+ );
+}
+
+export { ConditionForm };
diff --git a/frontend/app/agents/workflows/nodes/components/condition/ConditionNode.tsx b/frontend/app/agents/workflows/nodes/components/condition/ConditionNode.tsx
new file mode 100644
index 00000000..ca8235ae
--- /dev/null
+++ b/frontend/app/agents/workflows/nodes/components/condition/ConditionNode.tsx
@@ -0,0 +1,204 @@
+/*
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β @author: Davidson Gomes β
+β @file: /app/agents/workflows/nodes/components/condition/ConditionNode.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. β
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+*/
+/* eslint-disable no-unused-vars */
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { Handle, Node, NodeProps, Position, useEdges } from "@xyflow/react";
+import { FilterIcon, ArrowRight } from "lucide-react";
+import { cn } from "@/lib/utils";
+
+import { BaseNode } from "../../BaseNode";
+import { ConditionType, ConditionTypeEnum } from "../../nodeFunctions";
+
+export type ConditionNodeType = Node<
+ {
+ label?: string;
+ type?: "and" | "or";
+ conditions?: ConditionType[];
+ },
+ "condition-node"
+>;
+
+export type OperatorType =
+ | "is_defined"
+ | "is_not_defined"
+ | "equals"
+ | "not_equals"
+ | "contains"
+ | "not_contains"
+ | "starts_with"
+ | "ends_with"
+ | "greater_than"
+ | "greater_than_or_equal"
+ | "less_than"
+ | "less_than_or_equal"
+ | "matches"
+ | "not_matches";
+
+const operatorText: Record = {
+ equals: "is equal to",
+ not_equals: "is not equal to",
+ contains: "contains",
+ not_contains: "does not contain",
+ starts_with: "starts with",
+ ends_with: "ends with",
+ greater_than: "is greater than",
+ greater_than_or_equal: "is greater than or equal to",
+ less_than: "is less than",
+ less_than_or_equal: "is less than or equal to",
+ matches: "matches the pattern",
+ not_matches: "does not match the pattern",
+ is_defined: "is defined",
+ is_not_defined: "is not defined",
+};
+
+export function ConditionNode(props: NodeProps) {
+ const { selected, data } = props;
+ const edges = useEdges();
+ const isExecuting = data.isExecuting as boolean | undefined;
+
+ const typeText = {
+ and: "all of the following conditions",
+ or: "any of the following conditions",
+ };
+
+ const isHandleConnected = (handleId: string) => {
+ return edges.some(
+ (edge) => edge.source === props.id && edge.sourceHandle === handleId,
+ );
+ };
+
+ const isBottomHandleConnected = isHandleConnected("bottom-handle");
+
+ const conditions: ConditionType[] = data.conditions as ConditionType[];
+ // const statistics: StatisticType = data.statistics as StatisticType;
+
+ const renderCondition = (condition: ConditionType) => {
+ const isConnected = isHandleConnected(condition.id);
+
+ if (condition.type === ConditionTypeEnum.PREVIOUS_OUTPUT) {
+ return (
+
+
+
+
+ O campo{" "}
+
+ {condition.data.field}
+ {" "}
+
+ {operatorText[condition.data.operator as OperatorType]}
+ {" "}
+ {!["is_defined", "is_not_defined"].includes(
+ condition.data.operator,
+ ) && (
+
+ "{condition.data.value}"
+
+ )}
+
+
+
+
+
+ );
+ }
+ return null;
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ {data.label as string}
+
+
+ Matches {typeText[(data.type as "and" | "or") || "and"]}
+
+
+
+
+
+ {conditions && conditions.length > 0 && Array.isArray(conditions) ? (
+ conditions.map((condition) => (
+ {renderCondition(condition)}
+ ))
+ ) : (
+
+
+
No conditions configured
+
Click to add a condition
+
+ )}
+
+
+
+ );
+}
diff --git a/frontend/app/agents/workflows/nodes/components/delay/DelayForm.tsx b/frontend/app/agents/workflows/nodes/components/delay/DelayForm.tsx
new file mode 100644
index 00000000..e5ce6b65
--- /dev/null
+++ b/frontend/app/agents/workflows/nodes/components/delay/DelayForm.tsx
@@ -0,0 +1,256 @@
+/*
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β @author: Davidson Gomes β
+β @author: Victor Calazans - Implementation of Delay node form β
+β @file: /app/agents/workflows/nodes/components/delay/DelayForm.tsx β
+β Developed by: Davidson Gomes β
+β Delay form developed by: Victor Calazans β
+β Creation date: May 13, 2025 β
+β Delay form implementation date: May 17, 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. β
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+*/
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { useState, useEffect } from "react";
+import { Dispatch, SetStateAction } from "react";
+import { Clock, Trash2, Save, AlertCircle, HourglassIcon } from "lucide-react";
+
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Textarea } from "@/components/ui/textarea";
+import { Badge } from "@/components/ui/badge";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+
+import { DelayType, DelayUnitEnum } from "../../nodeFunctions";
+import { cn } from "@/lib/utils";
+
+export function DelayForm({
+ selectedNode,
+ handleUpdateNode,
+ setEdges,
+ setIsOpen,
+ setSelectedNode,
+}: {
+ selectedNode: any;
+ handleUpdateNode: (node: any) => void;
+ setEdges: any;
+ setIsOpen: Dispatch>;
+ setSelectedNode: Dispatch>;
+}) {
+ const [delay, setDelay] = useState({
+ value: 1,
+ unit: DelayUnitEnum.SECONDS,
+ description: "",
+ });
+
+ useEffect(() => {
+ if (selectedNode?.data?.delay) {
+ setDelay(selectedNode.data.delay);
+ }
+ }, [selectedNode]);
+
+ const handleSave = () => {
+ handleUpdateNode({
+ ...selectedNode,
+ data: {
+ ...selectedNode.data,
+ delay,
+ },
+ });
+ };
+
+ const handleDelete = () => {
+ setEdges((edges: any) => {
+ return edges.filter(
+ (edge: any) => edge.source !== selectedNode.id && edge.target !== selectedNode.id
+ );
+ });
+
+ setIsOpen(false);
+ setSelectedNode(null);
+ };
+
+ const getUnitLabel = (unit: DelayUnitEnum) => {
+ const units = {
+ [DelayUnitEnum.SECONDS]: "Seconds",
+ [DelayUnitEnum.MINUTES]: "Minutes",
+ [DelayUnitEnum.HOURS]: "Hours",
+ [DelayUnitEnum.DAYS]: "Days",
+ };
+ return units[unit] || unit;
+ };
+
+ const getTimeDescription = () => {
+ const value = delay.value || 0;
+
+ if (value <= 0) return "Invalid time";
+
+ if (value === 1) {
+ return `1 ${getUnitLabel(delay.unit).slice(0, -1)}`;
+ }
+
+ return `${value} ${getUnitLabel(delay.unit)}`;
+ };
+
+ return (
+
+
+
+
+
Delay Duration
+
+ {getTimeDescription().toUpperCase()}
+
+
+
+ setDelay({
+ ...delay,
+ unit: value as DelayUnitEnum,
+ })
+ }
+ >
+
+
+
+
+ Seconds
+ Minutes
+ Hours
+ Days
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Time Delay
+
+ Pause workflow execution for a specified amount of time
+
+
+
+
+
+
+ Delay Value
+
+ setDelay({
+ ...delay,
+ value: parseInt(e.target.value) || 1,
+ })
+ }
+ />
+
+
+
+ Description (optional)
+
+
+ {delay.value > 0 ? (
+
+
Preview
+
+
+
+
+
+
+
+ {getTimeDescription()} delay
+
+ {delay.description && (
+
+ {delay.description}
+
+ )}
+
+
+
+
+ ) : (
+
+
+
Please set a valid delay time
+
+ )}
+
+
+
+
+
+
+
+ Delete Node
+
+
+
+ Save Delay
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/app/agents/workflows/nodes/components/delay/DelayNode.tsx b/frontend/app/agents/workflows/nodes/components/delay/DelayNode.tsx
new file mode 100644
index 00000000..9e095bd2
--- /dev/null
+++ b/frontend/app/agents/workflows/nodes/components/delay/DelayNode.tsx
@@ -0,0 +1,146 @@
+/*
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β @author: Davidson Gomes β
+β @author: Victor Calazans - Implementation of Delay node functionality β
+β @file: /app/agents/workflows/nodes/components/delay/DelayNode.tsx β
+β Developed by: Davidson Gomes β
+β Delay node developed by: Victor Calazans β
+β Creation date: May 13, 2025 β
+β Delay node implementation date: May 17, 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. β
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+*/
+/* eslint-disable no-unused-vars */
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { Handle, NodeProps, Position, useEdges } from "@xyflow/react";
+import { Clock, ArrowRight, Timer } from "lucide-react";
+import { DelayType } from "../../nodeFunctions";
+import { Badge } from "@/components/ui/badge";
+import { cn } from "@/lib/utils";
+
+import { BaseNode } from "../../BaseNode";
+
+export function DelayNode(props: NodeProps) {
+ const { selected, data } = props;
+
+ const edges = useEdges();
+ const isExecuting = data.isExecuting as boolean | undefined;
+
+ const isHandleConnected = (handleId: string) => {
+ return edges.some(
+ (edge) => edge.source === props.id && edge.sourceHandle === handleId
+ );
+ };
+
+ const isBottomHandleConnected = isHandleConnected("bottom-handle");
+
+ const delay = data.delay as DelayType | undefined;
+
+ const getUnitLabel = (unit: string) => {
+ switch (unit) {
+ case 'seconds':
+ return 'Seconds';
+ case 'minutes':
+ return 'Minutes';
+ case 'hours':
+ return 'Hours';
+ case 'days':
+ return 'Days';
+ default:
+ return unit;
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ {data.label as string}
+
+
+
+
+
+ {delay ? (
+
+
+
+
+
+ Delay
+
+
+ {getUnitLabel(delay.unit)}
+
+
+
+
+ {delay.value}
+ {delay.unit}
+
+
+ {delay.description && (
+
+ {delay.description.slice(0, 80)} {delay.description.length > 80 && '...'}
+
+ )}
+
+
+ ) : (
+
+
+
No delay configured
+
Click to configure
+
+ )}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/app/agents/workflows/nodes/components/message/MessageForm.tsx b/frontend/app/agents/workflows/nodes/components/message/MessageForm.tsx
new file mode 100644
index 00000000..ffdbef6f
--- /dev/null
+++ b/frontend/app/agents/workflows/nodes/components/message/MessageForm.tsx
@@ -0,0 +1,292 @@
+/*
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β @author: Davidson Gomes β
+β @file: /app/agents/workflows/nodes/components/message/MessageForm.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. β
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+*/
+/* eslint-disable jsx-a11y/alt-text */
+import { useEdges, useNodes } from "@xyflow/react";
+import { useCallback, useEffect, useMemo, useState } from "react";
+import { Agent } from "@/types/agent";
+import { listAgents } from "@/services/agentService";
+import { Button } from "@/components/ui/button";
+import { ScrollArea } from "@/components/ui/scroll-area";
+import { Badge } from "@/components/ui/badge";
+import {
+ MessageSquare,
+ Save,
+ Text,
+ Image,
+ File,
+ Video,
+ AlertCircle
+} from "lucide-react";
+import { Input } from "@/components/ui/input";
+import { Textarea } from "@/components/ui/textarea";
+import { Label } from "@/components/ui/label";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue
+} from "@/components/ui/select";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { cn } from "@/lib/utils";
+
+/* eslint-disable @typescript-eslint/no-explicit-any */
+function MessageForm({
+ selectedNode,
+ handleUpdateNode,
+ setEdges,
+ setIsOpen,
+ setSelectedNode,
+}: {
+ selectedNode: any;
+ handleUpdateNode: any;
+ setEdges: any;
+ setIsOpen: any;
+ setSelectedNode: any;
+}) {
+ const [node, setNode] = useState(selectedNode);
+ const [messageType, setMessageType] = useState("text");
+ const [content, setContent] = useState("");
+ const [loading, setLoading] = useState(false);
+ const [agents, setAgents] = useState([]);
+ const [allAgents, setAllAgents] = useState([]);
+ const [searchTerm, setSearchTerm] = useState("");
+ const edges = useEdges();
+ const nodes = useNodes();
+
+ const connectedNode = useMemo(() => {
+ const edge = edges.find((edge) => edge.source === selectedNode.id);
+ if (!edge) return null;
+ const node = nodes.find((node) => node.id === edge.target);
+ return node || null;
+ }, [edges, nodes, selectedNode.id]);
+
+ const user = typeof window !== "undefined" ? JSON.parse(localStorage.getItem("user") || '{}') : {};
+ const clientId = user?.client_id || "";
+
+ const currentAgent = typeof window !== "undefined" ?
+ JSON.parse(localStorage.getItem("current_workflow_agent") || '{}') : {};
+ const currentAgentId = currentAgent?.id;
+
+ useEffect(() => {
+ if (selectedNode) {
+ setNode(selectedNode);
+ setMessageType(selectedNode.data.message?.type || "text");
+ setContent(selectedNode.data.message?.content || "");
+ }
+ }, [selectedNode]);
+
+ useEffect(() => {
+ if (!clientId) return;
+ setLoading(true);
+ listAgents(clientId)
+ .then((res) => {
+ const filteredAgents = res.data.filter((agent: Agent) => agent.id !== currentAgentId);
+ setAllAgents(filteredAgents);
+ setAgents(filteredAgents);
+ })
+ .catch((error) => console.error("Error loading agents:", error))
+ .finally(() => setLoading(false));
+ }, [clientId, currentAgentId]);
+
+ useEffect(() => {
+ if (searchTerm.trim() === "") {
+ setAgents(allAgents);
+ } else {
+ const filtered = allAgents.filter((agent) =>
+ agent.name.toLowerCase().includes(searchTerm.toLowerCase())
+ );
+ setAgents(filtered);
+ }
+ }, [searchTerm, allAgents]);
+
+ const handleDeleteEdge = useCallback(() => {
+ const id = edges.find((edge: any) => edge.source === selectedNode.id)?.id;
+ setEdges((edges: any) => {
+ const left = edges.filter((edge: any) => edge.id !== id);
+ return left;
+ });
+ }, [nodes, edges, selectedNode, setEdges]);
+
+ const handleSelectAgent = (agent: Agent) => {
+ const updatedNode = {
+ ...node,
+ data: {
+ ...node.data,
+ agent,
+ },
+ };
+ setNode(updatedNode);
+ handleUpdateNode(updatedNode);
+ };
+
+ const getAgentTypeName = (type: string) => {
+ const agentTypes: Record = {
+ llm: "LLM Agent",
+ a2a: "A2A Agent",
+ sequential: "Sequential Agent",
+ parallel: "Parallel Agent",
+ loop: "Loop Agent",
+ workflow: "Workflow Agent",
+ task: "Task Agent",
+ };
+ return agentTypes[type] || type;
+ };
+
+ const handleSave = () => {
+ const updatedNode = {
+ ...node,
+ data: {
+ ...node.data,
+ message: {
+ type: messageType,
+ content,
+ },
+ },
+ };
+ setNode(updatedNode);
+ handleUpdateNode(updatedNode);
+ };
+
+ const messageTypeInfo = {
+ text: {
+ icon: ,
+ name: "Text Message",
+ description: "Simple text message",
+ color: "bg-orange-900/30 border-orange-700/50",
+ },
+ image: {
+ icon: ,
+ name: "Image",
+ description: "Image URL or base64",
+ color: "bg-blue-900/30 border-blue-700/50",
+ },
+ file: {
+ icon: ,
+ name: "File",
+ description: "File URL or base64",
+ color: "bg-emerald-900/30 border-emerald-700/50",
+ },
+ video: {
+ icon: ,
+ name: "Video",
+ description: "Video URL or base64",
+ color: "bg-purple-900/30 border-purple-700/50",
+ },
+ };
+
+ return (
+
+
+
+
+
Message Type
+
+ {messageType === "text" ? "TEXT" : messageType.toUpperCase()}
+
+
+
+
+
+
+
+ Text
+ {/* Other options can be enabled in the future */}
+ {/* Image
+ File
+ Video */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{messageTypeInfo.text.name}
+
{messageTypeInfo.text.description}
+
+
+
+
+
+ Message Content
+
+
+ {content.trim() !== "" ? (
+
+ ) : (
+
+
+
Your message will appear here
+
+ )}
+
+
+
+
+
+
+ Save Message
+
+
+
+ );
+}
+
+export { MessageForm };
diff --git a/frontend/app/agents/workflows/nodes/components/message/MessageNode.tsx b/frontend/app/agents/workflows/nodes/components/message/MessageNode.tsx
new file mode 100644
index 00000000..200b527c
--- /dev/null
+++ b/frontend/app/agents/workflows/nodes/components/message/MessageNode.tsx
@@ -0,0 +1,162 @@
+/*
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β @author: Davidson Gomes β
+β @file: /app/agents/workflows/nodes/components/message/MessageNode.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. β
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+*/
+/* eslint-disable no-unused-vars */
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { Handle, NodeProps, Position, useEdges } from "@xyflow/react";
+import { MessageCircle, Text, Image, File, Video, ArrowRight } from "lucide-react";
+import { MessageType, MessageTypeEnum } from "../../nodeFunctions";
+import { Badge } from "@/components/ui/badge";
+import { cn } from "@/lib/utils";
+
+import { BaseNode } from "../../BaseNode";
+
+export function MessageNode(props: NodeProps) {
+ const { selected, data } = props;
+ const edges = useEdges();
+ const isExecuting = data.isExecuting as boolean | undefined;
+
+ const isHandleConnected = (handleId: string) => {
+ return edges.some(
+ (edge) => edge.source === props.id && edge.sourceHandle === handleId
+ );
+ };
+
+ const isBottomHandleConnected = isHandleConnected("bottom-handle");
+
+ const message = data.message as MessageType | undefined;
+
+ const getMessageTypeIcon = (type: string) => {
+ switch (type) {
+ case MessageTypeEnum.TEXT:
+ return ;
+ case "image":
+ return ;
+ case "file":
+ return ;
+ case "video":
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ const getMessageTypeColor = (type: string) => {
+ switch (type) {
+ case MessageTypeEnum.TEXT:
+ return 'bg-orange-900/30 text-orange-400 border-orange-700/40';
+ case "image":
+ return 'bg-blue-900/30 text-blue-400 border-blue-700/40';
+ case "file":
+ return 'bg-emerald-900/30 text-emerald-400 border-emerald-700/40';
+ case "video":
+ return 'bg-purple-900/30 text-purple-400 border-purple-700/40';
+ default:
+ return 'bg-orange-900/30 text-orange-400 border-orange-700/40';
+ }
+ };
+
+ const getMessageTypeName = (type: string) => {
+ const messageTypes: Record = {
+ text: "Text Message",
+ image: "Image",
+ file: "File",
+ video: "Video",
+ };
+ return messageTypes[type] || type;
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ {data.label as string}
+
+
+
+
+
+ {message ? (
+
+
+
+
+ {getMessageTypeIcon(message.type)}
+ {getMessageTypeName(message.type)}
+
+
+ {message.type}
+
+
+
+ {message.content && (
+
+ {message.content.slice(0, 80)} {message.content.length > 80 && '...'}
+
+ )}
+
+
+ ) : (
+
+
+
No message configured
+
Click to configure
+
+ )}
+
+
+
+ );
+}
diff --git a/frontend/app/agents/workflows/nodes/components/start/StartNode.tsx b/frontend/app/agents/workflows/nodes/components/start/StartNode.tsx
new file mode 100644
index 00000000..5a9c2889
--- /dev/null
+++ b/frontend/app/agents/workflows/nodes/components/start/StartNode.tsx
@@ -0,0 +1,100 @@
+/*
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β @author: Davidson Gomes β
+β @file: /app/agents/workflows/nodes/components/start/StartNode.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. β
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+*/
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { Handle, Node, NodeProps, Position, useEdges } from "@xyflow/react";
+import { Zap, ArrowRight } from "lucide-react";
+import { cn } from "@/lib/utils";
+
+import { BaseNode } from "../../BaseNode";
+
+export type StartNodeType = Node<
+ {
+ label?: string;
+ },
+ "start-node"
+>;
+
+export function StartNode(props: NodeProps) {
+ const { selected, data } = props;
+ const edges = useEdges();
+ const isExecuting = data.isExecuting as boolean | undefined;
+
+ const isSourceHandleConnected = edges.some(
+ (edge) => edge.source === props.id
+ );
+
+ return (
+
+
+
+
+
+
+
+ Input: User content
+
+
+
+
+ The workflow begins when a user sends a message to the agent
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/app/agents/workflows/nodes/index.ts b/frontend/app/agents/workflows/nodes/index.ts
new file mode 100644
index 00000000..47bc3267
--- /dev/null
+++ b/frontend/app/agents/workflows/nodes/index.ts
@@ -0,0 +1,109 @@
+/*
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β @author: Davidson Gomes β
+β @author: Victor Calazans - Implementation of Delay node type β
+β @file: /app/agents/workflows/nodes/index.ts β
+β Developed by: Davidson Gomes β
+β Delay node functionality developed by: Victor Calazans β
+β Creation date: May 13, 2025 β
+β Delay implementation date: May 17, 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. β
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+*/
+import type { NodeTypes, BuiltInNode, Node } from "@xyflow/react";
+
+import { ConditionNode } from "./components/condition/ConditionNode";
+import { AgentNode } from "./components/agent/AgentNode";
+import { StartNode, StartNodeType } from "./components/start/StartNode";
+import { MessageNode } from "./components/message/MessageNode";
+import { DelayNode } from "./components/delay/DelayNode";
+
+import "./style.css";
+import {
+ ConditionType,
+ MessageType,
+ DelayType,
+} from "./nodeFunctions";
+import { Agent } from "@/types/agent";
+
+type AgentNodeType = Node<
+ {
+ label?: string;
+ agent?: Agent;
+ },
+ "agent-node"
+>;
+
+type MessageNodeType = Node<
+ {
+ label?: string;
+ message?: MessageType;
+ },
+ "message-node"
+>;
+
+type DelayNodeType = Node<
+ {
+ label?: string;
+ delay?: DelayType;
+ },
+ "delay-node"
+>;
+
+type ConditionNodeType = Node<
+ {
+ label?: string;
+ integration?: string;
+ icon?: string;
+ conditions?: ConditionType[];
+ },
+ "condition-node"
+>;
+
+export type AppNode =
+ | BuiltInNode
+ | StartNodeType
+ | AgentNodeType
+ | ConditionNodeType
+ | MessageNodeType
+ | DelayNodeType;
+
+export type NodeType = AppNode["type"];
+
+export const initialNodes: AppNode[] = [
+ {
+ id: "start-node",
+ type: "start-node",
+ position: { x: -100, y: 100 },
+ data: {
+ label: "Start",
+ },
+ },
+];
+
+export const nodeTypes = {
+ "start-node": StartNode,
+ "agent-node": AgentNode,
+ "message-node": MessageNode,
+ "condition-node": ConditionNode,
+ "delay-node": DelayNode,
+} satisfies NodeTypes;
diff --git a/frontend/app/agents/workflows/nodes/nodeFunctions.ts b/frontend/app/agents/workflows/nodes/nodeFunctions.ts
new file mode 100644
index 00000000..48006aff
--- /dev/null
+++ b/frontend/app/agents/workflows/nodes/nodeFunctions.ts
@@ -0,0 +1,65 @@
+/*
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β @author: Davidson Gomes β
+β @author: Victor Calazans - Implementation of Delay types β
+β @file: /app/agents/workflows/nodes/nodeFunctions.ts β
+β Developed by: Davidson Gomes β
+β Delay node functionality developed by: Victor Calazans β
+β Creation date: May 13, 2025 β
+β Delay implementation date: May 17, 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. β
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+*/
+/* eslint-disable @typescript-eslint/no-explicit-any */
+/* eslint-disable no-unused-vars */
+export enum ConditionTypeEnum {
+ PREVIOUS_OUTPUT = "previous-output",
+}
+
+export enum MessageTypeEnum {
+ TEXT = "text",
+}
+
+export enum DelayUnitEnum {
+ SECONDS = "seconds",
+ MINUTES = "minutes",
+ HOURS = "hours",
+ DAYS = "days",
+}
+
+export type MessageType = {
+ type: MessageTypeEnum;
+ content: string;
+};
+
+export type DelayType = {
+ value: number;
+ unit: DelayUnitEnum;
+ description?: string;
+};
+
+export type ConditionType = {
+ id: string;
+ type: ConditionTypeEnum;
+ data?: any;
+};
+
diff --git a/frontend/app/agents/workflows/nodes/style.css b/frontend/app/agents/workflows/nodes/style.css
new file mode 100644
index 00000000..beaf80fa
--- /dev/null
+++ b/frontend/app/agents/workflows/nodes/style.css
@@ -0,0 +1,54 @@
+/*
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β @author: Davidson Gomes β
+β @file: /app/agents/workflows/nodes/style.css β
+β 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. β
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+*/
+/* .react-flow__handle {
+ background-color: #8492A6;
+ border-radius: 50%;
+ height: 16px;
+ position: absolute;
+ width: 16px;
+} */
+
+/* .react-flow__handle-right {
+ right: -6px;
+ top: 88%;
+ transform: translateY(-75%);
+ background-color: #f5f5f5 !important;
+ border: 3px solid #8492A6 !important;
+} */
+
+/* .react-flow__handle-left {
+ left: 0px;
+ top: 40%;
+ width: 60px;
+ border-radius: 0;
+ height: 50%;
+ transform: translateY(-75%);
+ background-color: transparent;
+ border: none;
+} */
\ No newline at end of file
diff --git a/frontend/app/agents/workflows/page.tsx b/frontend/app/agents/workflows/page.tsx
new file mode 100644
index 00000000..f5bcaf08
--- /dev/null
+++ b/frontend/app/agents/workflows/page.tsx
@@ -0,0 +1,218 @@
+/*
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β @author: Davidson Gomes β
+β @file: /app/agents/workflows/page.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 { useEffect, useState, useRef, Suspense } from "react";
+import { useSearchParams } from "next/navigation";
+import Canva from "./Canva";
+import { Agent } from "@/types/agent";
+import { getAgent, updateAgent } from "@/services/agentService";
+import { Button } from "@/components/ui/button";
+import { ArrowLeft, Save, Download, PlayIcon } from "lucide-react";
+import Link from "next/link";
+import { ReactFlowProvider } from "@xyflow/react";
+import { DnDProvider } from "@/contexts/DnDContext";
+import { NodeDataProvider } from "@/contexts/NodeDataContext";
+import { SourceClickProvider } from "@/contexts/SourceClickContext";
+import { useToast } from "@/hooks/use-toast";
+import { AgentTestChatModal } from "./nodes/components/agent/AgentTestChatModal";
+
+function WorkflowsContent() {
+ const searchParams = useSearchParams();
+ const agentId = searchParams.get("agentId");
+ const [agent, setAgent] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const canvaRef = useRef(null);
+ const { toast } = useToast();
+ const [isTestModalOpen, setIsTestModalOpen] = useState(false);
+
+ const user =
+ typeof window !== "undefined"
+ ? JSON.parse(localStorage.getItem("user") || "{}")
+ : {};
+ const clientId = user?.client_id || "";
+
+ useEffect(() => {
+ if (agentId && clientId) {
+ setLoading(true);
+ getAgent(agentId, clientId)
+ .then((res) => {
+ setAgent(res.data);
+ if (typeof window !== "undefined") {
+ localStorage.setItem(
+ "current_workflow_agent",
+ JSON.stringify(res.data)
+ );
+ }
+ })
+ .catch((err) => {
+ console.error("Error loading agent:", err);
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ }
+ }, [agentId, clientId]);
+
+ useEffect(() => {
+ return () => {
+ if (typeof window !== "undefined") {
+ localStorage.removeItem("current_workflow_agent");
+ }
+ };
+ }, []);
+
+ const handleSaveWorkflow = async () => {
+ if (!agent || !canvaRef.current) return;
+
+ try {
+ const { nodes, edges } = canvaRef.current.getFlowData();
+
+ const workflow = {
+ nodes,
+ edges,
+ };
+
+ await updateAgent(agent.id, {
+ ...agent,
+ config: {
+ ...agent.config,
+ workflow,
+ },
+ });
+
+ toast({
+ title: "Workflow saved",
+ description: "The changes were saved successfully",
+ });
+
+ canvaRef.current.setHasChanges(false);
+ } catch (error) {
+ console.error("Error saving workflow:", error);
+ toast({
+ title: "Error saving workflow",
+ description: "Unable to save the changes",
+ variant: "destructive",
+ });
+ }
+ };
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ return (
+
+ {/* Header with controls */}
+
+
+
+
+
+ Back to Agents
+
+
+
+ {agent && (
+
+
+ {agent.name} -{" "}
+ {agent.type}
+
+
+ )}
+
+
+
+
+
+ Save
+
+ {agent && (
+
setIsTestModalOpen(true)}
+ >
+
+ Test Workflow
+
+ )}
+
+
+
+ {/* Main content area */}
+
+ {agent && isTestModalOpen && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default function WorkflowsPage() {
+ return (
+
+ Loading...
+
+ }
+ >
+