import * as LucideIcons from 'lucide-react'; import { Circle, ExternalLink, type LucideIcon, X } from 'lucide-react'; import { useEffect, useMemo } from 'react'; import type { NodeSpec } from '@/client/types.gen'; import { useNodeSpecs } from '@/components/flow/renderer'; import { Button } from '@/components/ui/button'; import { FlowNode, NodeType } from './types'; type AddNodePanelProps = { isOpen: boolean; onClose: () => void; onNodeSelect: (nodeType: NodeType) => void; nodes: FlowNode[]; }; // Section ordering and labels. Drives both the category → section title // mapping and the rendering order. const SECTION_ORDER: Array<{ category: NodeSpec['category']; title: string }> = [ { category: 'trigger', title: 'Triggers' }, { category: 'call_node', title: 'Agent Nodes' }, { category: 'global_node', title: 'Global Nodes' }, { category: 'integration', title: 'Integrations' }, ]; function resolveIcon(name: string): LucideIcon { const icons = LucideIcons as unknown as Record; return icons[name] ?? Circle; } function NodeSection({ title, specs, onNodeSelect, nodeTypeCounts, }: { title: string; specs: NodeSpec[]; onNodeSelect: (nodeType: NodeType) => void; nodeTypeCounts: Map; }) { if (specs.length === 0) return null; return (

{title}

{specs.map((spec) => { const Icon = resolveIcon(spec.icon); const maxInstances = spec.graph_constraints?.max_instances; const disabled = maxInstances !== undefined && maxInstances !== null && (nodeTypeCounts.get(spec.name) ?? 0) >= maxInstances; return ( ); })}
); } export default function AddNodePanel({ isOpen, onNodeSelect, onClose, nodes }: AddNodePanelProps) { const { specs } = useNodeSpecs(); // Group registered specs by category, preserving the SECTION_ORDER. // Adding a new node type with a new spec.category just shows up here. const sections = useMemo(() => { return SECTION_ORDER.map(({ category, title }) => ({ title, specs: specs.filter((s) => s.category === category), })); }, [specs]); const nodeTypeCounts = useMemo(() => { const counts = new Map(); nodes.forEach((node) => { counts.set(node.type, (counts.get(node.type) ?? 0) + 1); }); return counts; }, [nodes]); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape' && isOpen) { onClose(); } }; document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, [isOpen, onClose]); return (
{sections.map(({ title, specs }) => ( ))}
); }