gMAS / web_ui /frontend /src /components /graph /AgentNode.tsx
Артём Боярских
fix: fixed some bugs
81f5c1c
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);