Spaces:
Running
Running
| import { memo } from "react"; | |
| import { Handle, Position, type NodeProps } from "@xyflow/react"; | |
| import { Bot, Play, CheckCircle2, XCircle, Clock, Cpu, Wrench, Pencil } from "lucide-react"; | |
| import { cn } from "@/lib/utils"; | |
| import { useGraphStore } from "@/stores/graphStore"; | |
| import type { AgentNodeData } from "@/stores/graphStore"; | |
| const statusConfig = { | |
| idle: { color: "border-border", icon: null, bg: "bg-card" }, | |
| pending: { color: "border-yellow-400", icon: Clock, bg: "bg-yellow-50 dark:bg-yellow-950" }, | |
| running: { color: "border-blue-500", icon: Play, bg: "bg-blue-50 dark:bg-blue-950" }, | |
| completed: { color: "border-green-500", icon: CheckCircle2, bg: "bg-green-50 dark:bg-green-950" }, | |
| error: { color: "border-red-500", icon: XCircle, bg: "bg-red-50 dark:bg-red-950" }, | |
| }; | |
| function AgentNodeComponent({ data }: NodeProps) { | |
| const nodeData = data as unknown as AgentNodeData; | |
| const status = statusConfig[nodeData.executionStatus] || statusConfig.idle; | |
| const StatusIcon = status.icon; | |
| const setEditingNodeId = useGraphStore((s) => s.setEditingNodeId); | |
| return ( | |
| <div | |
| className={cn( | |
| "group min-w-[200px] rounded-lg border-2 shadow-sm transition-all", | |
| status.color, | |
| status.bg, | |
| nodeData.isStartNode && "ring-2 ring-green-400 ring-offset-2", | |
| nodeData.isEndNode && "ring-2 ring-orange-400 ring-offset-2" | |
| )} | |
| > | |
| <Handle type="target" position={Position.Top} className="!bg-primary !w-3 !h-3" /> | |
| <div className="p-3"> | |
| <div className="flex items-center gap-2 mb-1"> | |
| {StatusIcon ? ( | |
| <StatusIcon | |
| className={cn( | |
| "h-4 w-4", | |
| nodeData.executionStatus === "running" && "animate-pulse text-blue-500", | |
| nodeData.executionStatus === "completed" && "text-green-500", | |
| nodeData.executionStatus === "error" && "text-red-500", | |
| nodeData.executionStatus === "pending" && "text-yellow-500" | |
| )} | |
| /> | |
| ) : ( | |
| <Bot className="h-4 w-4 text-primary" /> | |
| )} | |
| <span className="font-medium text-sm truncate flex-1">{nodeData.displayName}</span> | |
| <button | |
| className="opacity-0 group-hover:opacity-100 transition-opacity p-0.5 rounded hover:bg-accent" | |
| onClick={(e) => { | |
| e.stopPropagation(); | |
| setEditingNodeId(nodeData.agentId); | |
| }} | |
| > | |
| <Pencil className="h-3 w-3 text-muted-foreground" /> | |
| </button> | |
| </div> | |
| {nodeData.persona && ( | |
| <p className="text-xs text-muted-foreground line-clamp-2 mb-2">{nodeData.persona}</p> | |
| )} | |
| <div className="flex flex-wrap gap-1"> | |
| {nodeData.llmBackbone && ( | |
| <span className="inline-flex items-center gap-0.5 rounded bg-secondary px-1.5 py-0.5 text-[10px]"> | |
| <Cpu className="h-2.5 w-2.5" /> | |
| {nodeData.llmBackbone} | |
| </span> | |
| )} | |
| {nodeData.tools.slice(0, 2).map((tool) => ( | |
| <span | |
| key={tool} | |
| className="inline-flex items-center gap-0.5 rounded bg-secondary px-1.5 py-0.5 text-[10px]" | |
| > | |
| <Wrench className="h-2.5 w-2.5" /> | |
| {tool} | |
| </span> | |
| ))} | |
| {nodeData.tools.length > 2 && ( | |
| <span className="rounded bg-secondary px-1.5 py-0.5 text-[10px]"> | |
| +{nodeData.tools.length - 2} | |
| </span> | |
| )} | |
| </div> | |
| {nodeData.tokensUsed !== undefined && ( | |
| <div className="mt-1 text-[10px] text-muted-foreground"> | |
| {nodeData.tokensUsed} tokens | |
| </div> | |
| )} | |
| <div className="flex gap-1 mt-1"> | |
| {nodeData.isStartNode && ( | |
| <span className="rounded bg-green-100 dark:bg-green-900 px-1 py-0.5 text-[10px] text-green-700 dark:text-green-300"> | |
| START | |
| </span> | |
| )} | |
| {nodeData.isEndNode && ( | |
| <span className="rounded bg-orange-100 dark:bg-orange-900 px-1 py-0.5 text-[10px] text-orange-700 dark:text-orange-300"> | |
| END | |
| </span> | |
| )} | |
| </div> | |
| </div> | |
| <Handle type="source" position={Position.Bottom} className="!bg-primary !w-3 !h-3" /> | |
| </div> | |
| ); | |
| } | |
| export const AgentNode = memo(AgentNodeComponent); | |