/ src / components / protocols / builder / node-palette.tsx
node-palette.tsx
 1  import { useState, type DragEvent } from 'react'
 2  import { cn } from '@/lib/utils'
 3  import type { ProtocolStepKind } from '@/types'
 4  
 5  interface PaletteCategory {
 6    label: string
 7    items: Array<{ kind: ProtocolStepKind; label: string; description: string }>
 8  }
 9  
10  const CATEGORIES: PaletteCategory[] = [
11    {
12      label: 'Phases',
13      items: [
14        { kind: 'present', label: 'Present', description: 'Show info to participants' },
15        { kind: 'collect_independent_inputs', label: 'Collect Inputs', description: 'Gather independent responses' },
16        { kind: 'round_robin', label: 'Round Robin', description: 'Turn-based discussion' },
17        { kind: 'compare', label: 'Compare', description: 'Compare agent outputs' },
18        { kind: 'decide', label: 'Decide', description: 'Make a decision' },
19        { kind: 'summarize', label: 'Summarize', description: 'Synthesize results' },
20      ],
21    },
22    {
23      label: 'Actions',
24      items: [
25        { kind: 'emit_tasks', label: 'Emit Tasks', description: 'Create tasks from context' },
26        { kind: 'dispatch_task', label: 'Dispatch Task', description: 'Assign a specific task' },
27        { kind: 'dispatch_delegation', label: 'Delegate', description: 'Delegate to an agent' },
28      ],
29    },
30    {
31      label: 'Control Flow',
32      items: [
33        { kind: 'branch', label: 'Branch', description: 'Conditional path' },
34        { kind: 'repeat', label: 'Repeat', description: 'Loop with exit condition' },
35        { kind: 'parallel', label: 'Parallel', description: 'Fork into parallel branches' },
36        { kind: 'join', label: 'Join', description: 'Merge parallel branches' },
37        { kind: 'for_each', label: 'For Each', description: 'Iterate over items' },
38      ],
39    },
40    {
41      label: 'Advanced',
42      items: [
43        { kind: 'subflow', label: 'Subflow', description: 'Nested protocol template' },
44        { kind: 'swarm_claim', label: 'Swarm Claim', description: 'Competitive task claiming' },
45        { kind: 'wait', label: 'Wait', description: 'Pause until external input' },
46        { kind: 'complete', label: 'Complete', description: 'End the protocol' },
47      ],
48    },
49  ]
50  
51  export function NodePalette() {
52    const [expandedCategory, setExpandedCategory] = useState<string | null>('Phases')
53  
54    const onDragStart = (e: DragEvent, kind: ProtocolStepKind, label: string) => {
55      e.dataTransfer.effectAllowed = 'move'
56      e.dataTransfer.setData('application/x-protocol-node-kind', kind)
57      e.dataTransfer.setData('application/x-protocol-node-label', label)
58    }
59  
60    return (
61      <div className="flex w-52 flex-col overflow-y-auto rounded-lg border bg-card p-3 shadow-sm">
62        <h3 className="mb-3 text-xs font-bold uppercase tracking-wider text-muted-foreground">
63          Drag to canvas
64        </h3>
65  
66        {CATEGORIES.map((cat) => (
67          <div key={cat.label} className="mb-2">
68            <button
69              onClick={() => setExpandedCategory(expandedCategory === cat.label ? null : cat.label)}
70              className="mb-1 flex w-full items-center gap-1 text-xs font-semibold text-muted-foreground hover:text-foreground"
71            >
72              <span className="text-[10px]">{expandedCategory === cat.label ? '\u25BC' : '\u25B6'}</span>
73              {cat.label}
74            </button>
75            {expandedCategory === cat.label && (
76              <div className="space-y-1">
77                {cat.items.map(({ kind, label, description }) => (
78                  <div
79                    key={kind}
80                    draggable
81                    onDragStart={(e) => onDragStart(e, kind, label)}
82                    className={cn(
83                      'cursor-grab rounded-md border bg-background px-3 py-2 text-sm',
84                      'transition-shadow hover:shadow-md active:cursor-grabbing',
85                    )}
86                    title={description}
87                  >
88                    {label}
89                  </div>
90                ))}
91              </div>
92            )}
93          </div>
94        ))}
95      </div>
96    )
97  }