// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. ========= // Licensed under the Apache License, Version 2.0 (the "License"); // 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. // ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. ========= import { Handle, Position, useReactFlow, NodeResizer } from "@xyflow/react"; import { useEffect, useRef, useState, useCallback } from "react"; import { FileText, Globe, Bird, CodeXml, Image, Bot, SquareCode, Ellipsis, LoaderCircle, CircleCheckBig, Circle, CircleSlash, TriangleAlert, Trash2, SquareChevronLeft, CircleSlash2, } from "lucide-react"; import { Button } from "@/components/ui/button"; import Folder from "../Folder"; import Terminal from "../Terminal"; import { useAuthStore, useWorkerList } from "@/store/authStore"; import ShinyText from "../ui/ShinyText/ShinyText"; import { MarkDown } from "./MarkDown"; import { Tooltip, TooltipTrigger } from "../ui/tooltip"; import { TooltipContent } from "@radix-ui/react-tooltip"; import { TaskState, TaskStateType } from "../TaskState"; import { Popover, PopoverClose, PopoverContent, PopoverTrigger, } from "../ui/popover"; import { AddWorker } from "@/components/AddWorker"; import useChatStoreAdapter from "@/hooks/useChatStoreAdapter"; import { TaskStatus, ChatTaskStatus, AgentStatusValue } from "@/types/constants"; interface NodeProps { id: string; data: { img: ActiveWebView[]; agent?: Agent; type: AgentNameType; isExpanded: boolean; onExpandChange: (nodeId: string, isExpanded: boolean) => void; isEditMode: boolean; workerInfo: { name: string; description: string; tools: any; mcp_tools: any; selectedTools: any; }; }; } export function Node({ id, data }: NodeProps) { const [isExpanded, setIsExpanded] = useState(data.isExpanded); const [selectedTask, setSelectedTask] = useState(null); const [selectedState, setSelectedState] = useState("all"); const [filterTasks, setFilterTasks] = useState([]); useEffect(() => { const tasks = data.agent?.tasks || []; if (selectedState === "all") { setFilterTasks(tasks); } else { const newFiltered = tasks.filter((task) => { switch (selectedState) { case "done": return task.status === TaskStatus.COMPLETED && !task.reAssignTo; case "reassigned": return !!task.reAssignTo; case "ongoing": return ( task.status !== TaskStatus.FAILED && task.status !== TaskStatus.COMPLETED && task.status !== TaskStatus.SKIPPED && task.status !== TaskStatus.WAITING && task.status !== TaskStatus.EMPTY && !task.reAssignTo ); case "pending": return ( (task.status === TaskStatus.SKIPPED || task.status === TaskStatus.WAITING || task.status === TaskStatus.EMPTY) && !task.reAssignTo ); case "failed": return task.status === TaskStatus.FAILED && !task.reAssignTo; default: return false; } }); setFilterTasks(newFiltered); } }, [selectedState, data.agent?.tasks]); //Get Chatstore for the active project's task const { chatStore } = useChatStoreAdapter(); if (!chatStore) { return
Loading...
; } const { setCenter, getNode, setViewport, setNodes } = useReactFlow(); const workerList = useWorkerList(); const { setWorkerList } = useAuthStore(); const nodeRef = useRef(null); const lastAutoExpandedTaskIdRef = useRef(null); useEffect(() => { setIsExpanded(data.isExpanded); }, [data.isExpanded]); // Auto-expand when a task is running with toolkits useEffect(() => { const tasks = data.agent?.tasks || []; // Find running task with active toolkits const runningTaskWithToolkits = tasks.find( (task) => task.status === TaskStatus.RUNNING && task.toolkits && task.toolkits.length > 0 ); // Reset tracking when no tasks are running const hasRunningTasks = tasks.some((task) => task.status === TaskStatus.RUNNING); if (!hasRunningTasks && lastAutoExpandedTaskIdRef.current) { lastAutoExpandedTaskIdRef.current = null; } // Auto-expand for new running task if (runningTaskWithToolkits && runningTaskWithToolkits.id !== lastAutoExpandedTaskIdRef.current) { // Always select the new task setSelectedTask(runningTaskWithToolkits); // Expand if not already expanded if (!isExpanded) { setIsExpanded(true); data.onExpandChange(id, true); } lastAutoExpandedTaskIdRef.current = runningTaskWithToolkits.id; } }, [ data.agent?.tasks, // Add specific dependencies that actually change data.agent?.tasks?.length, data.agent?.tasks?.find((t) => t.status === TaskStatus.RUNNING)?.id, data.agent?.tasks?.find((t) => t.status === TaskStatus.RUNNING)?.toolkits?.length, id, data.onExpandChange, isExpanded, ]); // manually control node size useEffect(() => { if (data.isEditMode) { const targetWidth = isExpanded ? 684 : 342; const targetHeight = 600; setNodes((nodes) => nodes.map((node) => { if (node.id === id) { return { ...node, style: { ...node.style, width: targetWidth, height: targetHeight, }, }; } return node; }) ); } }, [isExpanded, data.isEditMode, id, setNodes]); const handleShowLog = () => { if (!isExpanded) { setSelectedTask( data.agent?.tasks.find((task) => task.status === TaskStatus.RUNNING) || data.agent?.tasks[0] ); } setIsExpanded(!isExpanded); data.onExpandChange(id, !isExpanded); }; useEffect(() => { if (chatStore.tasks[chatStore.activeTaskId as string]?.activeAgent === id) { const node = getNode(id); if (node) { setTimeout(() => { setViewport( { x: -node.position.x, y: 0, zoom: 1 }, { duration: 500, } ); }, 100); } } }, [ chatStore.tasks[chatStore.activeTaskId as string]?.activeAgent, id, setCenter, getNode, ]); const wrapperRef = useRef(null); const toolsRef = useRef(null); const [shouldScroll, setShouldScroll] = useState(false); const [toolsHeight, setToolsHeight] = useState(0); useEffect(() => { if (wrapperRef.current) { const { scrollHeight, clientHeight } = wrapperRef.current; setShouldScroll(scrollHeight > clientHeight); } }, [data.agent?.tasks, toolsHeight]); // dynamically calculate tool label height useEffect(() => { if (toolsRef.current) { const height = toolsRef.current.offsetHeight; setToolsHeight(height); } }, [data.agent?.tools]); const logRef = useRef(null); const rePortRef = useRef(null); const wheelHandler = useCallback((e: WheelEvent) => { e.stopPropagation(); }, []); useEffect(() => { const wrapper = wrapperRef.current; const log = logRef.current; if (wrapper) { wrapper.addEventListener("wheel", wheelHandler, { passive: false }); } if (log) { log.addEventListener("wheel", wheelHandler, { passive: false }); } return () => { if (wrapper) { wrapper.removeEventListener("wheel", wheelHandler); } if (log) { log.removeEventListener("wheel", wheelHandler); } }; }, [ wheelHandler, isExpanded, selectedTask, selectedTask?.report?.rePort?.content, ]); const agentMap = { developer_agent: { name: "Developer Agent", icon: , textColor: "text-text-developer", bgColor: "bg-bg-fill-coding-active", shapeColor: "bg-bg-fill-coding-default", borderColor: "border-bg-fill-coding-active", bgColorLight: "bg-emerald-200", }, browser_agent: { name: "Browser Agent", icon: , textColor: "text-blue-700", bgColor: "bg-bg-fill-browser-active", shapeColor: "bg-bg-fill-browser-default", borderColor: "border-bg-fill-browser-active", bgColorLight: "bg-blue-200", }, document_agent: { name: "Document Agent", icon: , textColor: "text-yellow-700", bgColor: "bg-bg-fill-writing-active", shapeColor: "bg-bg-fill-writing-default", borderColor: "border-bg-fill-writing-active", bgColorLight: "bg-yellow-200", }, multi_modal_agent: { name: "Multi Modal Agent", icon: , textColor: "text-fuchsia-700", bgColor: "bg-bg-fill-multimodal-active", shapeColor: "bg-bg-fill-multimodal-default", borderColor: "border-bg-fill-multimodal-active", bgColorLight: "bg-fuchsia-200", }, social_media_agent: { name: "Social Media Agent", icon: , textColor: "text-purple-700", bgColor: "bg-violet-700", shapeColor: "bg-violet-300", borderColor: "border-violet-700", bgColorLight: "bg-purple-50", }, }; const agentToolkits = { developer_agent: [ "# Terminal & Shell ", "# Web Deployment ", "# Screen Capture ", ], browser_agent: ["# Web Browser ", "# Search Engines "], multi_modal_agent: [ "# Image Analysis ", "# Video Processing ", "# Audio Processing ", "# Image Generation ", ], document_agent: [ "# File Management ", "# Data Processing ", "# Document Creation ", ], }; const getTaskId = (taskId: string) => { const list = taskId.split("."); let idStr = ""; list.shift(); list.map((i: string, index: number) => { idStr += Number(i) + (index === list.length - 1 ? "" : "."); }); return idStr; }; return ( <>
{agentMap[data.type]?.name || data.agent?.name}
{!Object.keys(agentMap).find((key) => key === data.type) && chatStore.tasks[chatStore.activeTaskId as string].messages .length === 0 && (
)}
{/* {JSON.stringify(data.agent)} */} {agentToolkits[ data.agent?.type as keyof typeof agentToolkits ]?.join(" ") || data.agent?.tools ?.map((tool) => (tool ? "# " + tool.replace(/_/g, " ") : "")) .filter(Boolean) .join(" ") || "No Toolkits"}
{ chatStore.setActiveWorkSpace( chatStore.activeTaskId as string, data.agent?.agent_id as string ); window.electronAPI.hideAllWebview(); }} > {/* {data.img.length} */} {data.img && data.img.filter((img) => img?.img).length > 0 && (
{data.img .filter((img) => img?.img) .slice(0, 4) .map( (img, index) => img.img && ( {data.type} ) )}
)} {data.type === "document_agent" && data?.agent?.tasks && data.agent.tasks.length > 0 && (
)} {data.type === "developer_agent" && data?.agent?.tasks && data?.agent?.tasks?.filter( (task) => task.terminal && task.terminal.length > 0 )?.length > 0 && (
{data.agent?.tasks .filter((task) => task.terminal && task.terminal.length > 0) .slice(0, 4) .map((task) => { return (
task.terminal && task.terminal.length > 0 ).length === 1 ? "min-w-full h-full" : "min-w-[calc(50%-8px)] h-[calc(50%-8px)]" } flex-1 rounded-sm object-cover relative overflow-hidden`} >
); })}
)}
{data.agent?.tasks && data.agent?.tasks.length > 0 && (
{/*
Subtasks
*/}
task.status === TaskStatus.COMPLETED && !task.reAssignTo ).length || 0 } reAssignTo={ data.agent.tasks?.filter((task) => task.reAssignTo) ?.length || 0 } progress={ data.agent?.tasks?.filter( (task) => task.status !== TaskStatus.FAILED && task.status !== TaskStatus.COMPLETED && task.status !== TaskStatus.SKIPPED && task.status !== TaskStatus.WAITING && task.status !== TaskStatus.EMPTY && !task.reAssignTo ).length || 0 } skipped={ data.agent?.tasks?.filter( (task) => (task.status === TaskStatus.SKIPPED || task.status === TaskStatus.WAITING || task.status === TaskStatus.EMPTY) && !task.reAssignTo ).length || 0 } failed={ data.agent?.tasks?.filter( (task) => task.status === TaskStatus.FAILED && !task.reAssignTo ).length || 0 } selectedState={selectedState} onStateChange={setSelectedState} clickable={true} />
)}
{ e.stopPropagation(); }} className={`mt-sm flex flex-col gap-2 animate-in fade-in-0 slide-in-from-bottom-4 duration-500 ease-out overflow-y-auto scrollbar pr-3 ${shouldScroll && "!overflow-y-scroll scrollbar " }`} style={{ maxHeight: data.img && data.img.length > 0 ? `calc(100vh - 200px - 180px - 60px - ${toolsHeight}px)` : `calc(100vh - 200px - 60px - ${toolsHeight}px)`, }} > {data.agent?.tasks && filterTasks.map((task, index) => { return (
{ setSelectedTask(task); setIsExpanded(true); data.onExpandChange(id, true); if (task.agent) { chatStore.setActiveWorkSpace( chatStore.activeTaskId as string, "workflow" ); chatStore.setActiveAgent( chatStore.activeTaskId as string, task.agent?.agent_id ); window.electronAPI.hideAllWebview(); } }} key={`taskList-${task.id}-${task.failure_count}`} className={`rounded-lg flex gap-2 py-sm px-sm transition-all duration-300 ease-in-out animate-in fade-in-0 slide-in-from-left-2 ${task.reAssignTo ? "bg-task-fill-warning" : task.status === TaskStatus.COMPLETED ? "bg-task-fill-success" : task.status === TaskStatus.FAILED ? "bg-task-fill-error" : task.status === TaskStatus.RUNNING ? "bg-task-fill-running" : task.status === TaskStatus.BLOCKED ? "bg-task-fill-warning" : "bg-task-fill-running" } border border-solid border-transparent cursor-pointer ${task.status === TaskStatus.COMPLETED ? "hover:border-bg-fill-success-primary" : task.status === TaskStatus.FAILED ? "hover:border-task-border-focus-error" : task.status === TaskStatus.RUNNING ? "hover:border-border-primary" : task.status === TaskStatus.BLOCKED ? "hover:border-task-border-focus-warning" : "border-transparent" } ${selectedTask?.id === task.id ? task.status === TaskStatus.COMPLETED ? "!border-bg-fill-success-primary" : task.status === TaskStatus.FAILED ? "!border-text-cuation-primary" : task.status === TaskStatus.RUNNING ? "!border-border-primary" : task.status === TaskStatus.BLOCKED ? "!border-text-warning-primary" : "border-transparent" : "border-transparent" }`} >
{task.reAssignTo ? ( // reassign to other agent ) : ( // normal task <> {task.status === TaskStatus.RUNNING && ( )} {task.status === TaskStatus.SKIPPED && ( )} {task.status === TaskStatus.COMPLETED && ( )} {task.status === TaskStatus.FAILED && ( )} {task.status === TaskStatus.BLOCKED && ( )} {(task.status === TaskStatus.EMPTY || task.status === TaskStatus.WAITING) && ( )} )}
No. {getTaskId(task.id)}
{task.reAssignTo ? (
Reassigned to {task.reAssignTo}
) : ( (task.failure_count ?? 0) > 0 && (
Attempt {task.failure_count}
) )}
{task.content}
{task?.status === TaskStatus.RUNNING && (
{/* active toolkit */} {task.toolkits && task.toolkits.length > 0 && task.toolkits .filter( (tool: any) => tool.toolkitName !== "notice" ) .at(-1)?.toolkitStatus === AgentStatusValue.RUNNING && (
{agentMap[data.type]?.icon ?? ( )}
)}
)}
); })}
{isExpanded && (
{ e.stopPropagation(); }} className=" flex flex-col gap-sm my-2 scrollbar max-h-[calc(100vh-200px)] scrollbar-gutter-stable overflow-y-auto pr-sm" > {selectedTask && selectedTask.toolkits && selectedTask.toolkits.length > 0 && selectedTask.toolkits.map((toolkit: any, index: number) => (
{toolkit.toolkitName === "notice" ? (
) : (
{ e.stopPropagation(); if (toolkit.toolkitMethods === "write to file") { chatStore.tasks[ chatStore.activeTaskId as string ].activeWorkSpace = "documentWorkSpace"; } else if ( toolkit.toolkitMethods === "visit page" ) { const parts = toolkit.message.split("\n"); const url = parts[0]; // the first line is the URL window.location.href = url; } else if (toolkit.toolkitMethods === "scrape") { window.location.href = toolkit.message; } }} className="py-0.5 px-xs bg-log-default rounded-sm flex gap-xs items-start hover:opacity-50 transition-all duration-300" > {/* {toolkit.toolkitStatus} */}
{toolkit.toolkitStatus === AgentStatusValue.RUNNING ? ( ) : ( agentMap[data.type]?.icon )}
{toolkit.toolkitName}
{toolkit.toolkitMethods}
{toolkit.message}
{toolkit.message && ( )}
)}
))} {selectedTask?.report && (
{ e.stopPropagation(); }} className="w-full flex flex-col gap-sm my-2 " >
Completion Report
)}
)}
); }