147 lines
3.3 KiB
TypeScript
147 lines
3.3 KiB
TypeScript
'use client';
|
|
|
|
import React, { useCallback, useMemo } from 'react';
|
|
import {
|
|
ReactFlow,
|
|
Background,
|
|
Controls,
|
|
MiniMap,
|
|
type Node,
|
|
type Edge,
|
|
type OnNodesChange,
|
|
type OnEdgesChange,
|
|
type OnConnect,
|
|
type NodeTypes,
|
|
type EdgeTypes,
|
|
BackgroundVariant,
|
|
ConnectionMode,
|
|
addEdge,
|
|
} from '@xyflow/react';
|
|
import '@xyflow/react/dist/style.css';
|
|
|
|
import { ToolNode } from './ToolNode';
|
|
import { GroupNode } from './GroupNode';
|
|
import { ConnectionEdge } from './ConnectionEdge';
|
|
import { CanvasToolbar } from './CanvasToolbar';
|
|
import { useCanvasState } from '../../hooks/useCanvasState';
|
|
|
|
import type { ToolDefinition } from '@mcpengine/ai-pipeline/types';
|
|
|
|
interface ToolCanvasProps {
|
|
tools: ToolDefinition[];
|
|
onToolSelect: (toolName: string | null) => void;
|
|
onToolsChange: (tools: ToolDefinition[]) => void;
|
|
}
|
|
|
|
const nodeTypes: NodeTypes = {
|
|
tool: ToolNode,
|
|
group: GroupNode,
|
|
};
|
|
|
|
const edgeTypes: EdgeTypes = {
|
|
connection: ConnectionEdge,
|
|
};
|
|
|
|
export function ToolCanvas({ tools, onToolSelect, onToolsChange }: ToolCanvasProps) {
|
|
const {
|
|
nodes,
|
|
edges,
|
|
setNodes,
|
|
setEdges,
|
|
selectedNodeId,
|
|
selectNode,
|
|
} = useCanvasState();
|
|
|
|
const onNodesChange: OnNodesChange = useCallback(
|
|
(changes) => {
|
|
setNodes(changes);
|
|
},
|
|
[setNodes]
|
|
);
|
|
|
|
const onEdgesChange: OnEdgesChange = useCallback(
|
|
(changes) => {
|
|
setEdges(changes);
|
|
},
|
|
[setEdges]
|
|
);
|
|
|
|
const onConnect: OnConnect = useCallback(
|
|
(connection) => {
|
|
useCanvasState.setState((state) => ({
|
|
edges: addEdge(
|
|
{ ...connection, type: 'connection', animated: true },
|
|
state.edges
|
|
),
|
|
}));
|
|
},
|
|
[]
|
|
);
|
|
|
|
const onNodeClick = useCallback(
|
|
(_: React.MouseEvent, node: Node) => {
|
|
selectNode(node.id);
|
|
onToolSelect(node.id);
|
|
},
|
|
[selectNode, onToolSelect]
|
|
);
|
|
|
|
const onPaneClick = useCallback(() => {
|
|
selectNode(null);
|
|
onToolSelect(null);
|
|
}, [selectNode, onToolSelect]);
|
|
|
|
const defaultEdgeOptions = useMemo(
|
|
() => ({
|
|
type: 'connection',
|
|
animated: true,
|
|
}),
|
|
[]
|
|
);
|
|
|
|
return (
|
|
<div className="relative h-full w-full bg-[#0A0F1E]">
|
|
<ReactFlow
|
|
nodes={nodes}
|
|
edges={edges}
|
|
onNodesChange={onNodesChange}
|
|
onEdgesChange={onEdgesChange}
|
|
onConnect={onConnect}
|
|
onNodeClick={onNodeClick}
|
|
onPaneClick={onPaneClick}
|
|
nodeTypes={nodeTypes}
|
|
edgeTypes={edgeTypes}
|
|
defaultEdgeOptions={defaultEdgeOptions}
|
|
connectionMode={ConnectionMode.Loose}
|
|
fitView
|
|
proOptions={{ hideAttribution: true }}
|
|
className="bg-[#0A0F1E]"
|
|
>
|
|
<Background
|
|
variant={BackgroundVariant.Dots}
|
|
gap={20}
|
|
size={1}
|
|
color="#1E293B"
|
|
/>
|
|
<Controls
|
|
showZoom={false}
|
|
showFitView={false}
|
|
showInteractive={false}
|
|
className="hidden"
|
|
/>
|
|
<MiniMap
|
|
nodeColor={(node) => {
|
|
if (node.id === selectedNodeId) return '#6366F1';
|
|
return '#374151';
|
|
}}
|
|
maskColor="rgba(10, 15, 30, 0.8)"
|
|
className="!bg-gray-900 !border-gray-700 rounded-lg"
|
|
pannable
|
|
zoomable
|
|
/>
|
|
</ReactFlow>
|
|
<CanvasToolbar />
|
|
</div>
|
|
);
|
|
}
|