Spaces:
Build error
Build error
| import React from 'react' | |
| import Node from './Node' | |
| export default function NodeCanvas({ | |
| nodes, | |
| connections, | |
| selectedNode, | |
| onSelectNode, | |
| onEditNode, | |
| onDeleteNode, | |
| onAddConnection, | |
| onNodeDragStart | |
| }) { | |
| const getNodeColor = (type) => { | |
| switch (type) { | |
| case 'ceo': return 'from-yellow-500 to-amber-600' | |
| case 'claude': return 'from-violet-500 to-purple-600' | |
| case 'gemini': return 'from-blue-500 to-cyan-600' | |
| case 'openai': return 'from-emerald-500 to-teal-600' | |
| case 'llama': return 'from-orange-500 to-red-600' | |
| default: return 'from-slate-500 to-slate-600' | |
| } | |
| } | |
| const renderConnections = () => { | |
| return connections.map(conn => { | |
| const fromNode = nodes.find(n => n.id === conn.from) | |
| const toNode = nodes.find(n => n.id === conn.to) | |
| if (!fromNode || !toNode) return null | |
| const fromX = fromNode.position.x + 140 | |
| const fromY = fromNode.position.y + 60 | |
| const toX = toNode.position.x + 140 | |
| const toY = toNode.position.y + 60 | |
| const midX = (fromX + toX) / 2 | |
| const controlOffset = Math.abs(toX - fromX) / 3 | |
| return ( | |
| <g key={conn.id}> | |
| <path | |
| d={`M ${fromX} ${fromY} C ${fromX + controlOffset} ${fromY}, ${toX - controlOffset} ${toY}, ${toX} ${toY}`} | |
| className={`node-connection ${conn.active ? 'active' : ''}`} | |
| markerEnd="url(#arrowhead)" | |
| /> | |
| <circle | |
| cx={toX} | |
| cy={toY} | |
| r={6} | |
| fill={conn.active ? '#22c55e' : '#64748b'} | |
| className="cursor-pointer hover:fill-red-500 transition-colors" | |
| onClick={() => { | |
| if (confirm('Delete this connection?')) { | |
| onDeleteNode(conn.id) | |
| } | |
| /> | |
| </g> | |
| ) | |
| }) | |
| } | |
| return ( | |
| <div id="node-canvas" className="w-full h-full canvas-grid overflow-auto relative"> | |
| <svg className="absolute inset-0 w-full h-full pointer-events-none" style={{ minWidth: '100%', minHeight: '100%' }}> | |
| <defs> | |
| <marker | |
| id="arrowhead" | |
| markerWidth="10" | |
| markerHeight="7" | |
| refX="9" | |
| refY="3.5" | |
| orient="auto" | |
| > | |
| <polygon points="0 0, 10 3.5, 0 7" fill="#64748b" /> | |
| </marker> | |
| </defs> | |
| <g className="pointer-events-auto"> | |
| {renderConnections()} | |
| </g> | |
| </svg> | |
| {nodes.map(node => ( | |
| <Node | |
| key={node.id} | |
| node={node} | |
| isSelected={selectedNode?.id === node.id} | |
| color={getNodeColor(node.type)} | |
| onSelect={() => onSelectNode(node)} | |
| onEdit={() => onEditNode(node)} | |
| onDelete={() => onDeleteNode(node.id)} | |
| onAddConnection={() => onAddConnection(node.id)} | |
| onDragStart={(e) => onNodeDragStart(e, node)} | |
| /> | |
| ))} | |
| {nodes.length === 0 && ( | |
| <div className="absolute inset-0 flex items-center justify-center"> | |
| <div className="text-center"> | |
| <div className="w-20 h-20 mx-auto mb-4 bg-slate-700/50 rounded-2xl flex items-center justify-center"> | |
| <svg className="w-10 h-10 text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" /> | |
| </svg> | |
| </div> | |
| <h3 className="text-lg font-medium text-slate-400">No agents yet</h3> | |
| <p className="text-sm text-slate-500 mt-1">Click "Add Node" to create your first agent</p> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ) | |
| } |