实现LLMNode
This commit is contained in:
parent
37bdf4dbfd
commit
847b3e96c8
104
frontend/app/workflow/components/LLMNode.tsx
Normal file
104
frontend/app/workflow/components/LLMNode.tsx
Normal file
@ -0,0 +1,104 @@
|
||||
// File: frontend/app/workflow/components/LLMNode.tsx
|
||||
// Description: 自定义 LLM 节点组件
|
||||
|
||||
import React, { useState, useCallback, useEffect, memo, ChangeEvent } from 'react';
|
||||
import { Handle, Position, useUpdateNodeInternals, useReactFlow, Node } from 'reactflow';
|
||||
import { BrainCircuit } from 'lucide-react';
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { type LLMNodeData, type CustomNodeProps, availableModels } from './types'; // 导入类型和模型列表
|
||||
|
||||
const LLMNodeComponent = ({ id, data, isConnectable }: CustomNodeProps<LLMNodeData>) => {
|
||||
// const [currentData, setCurrentData] = useState<LLMNodeData>(data);
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
const { setNodes } = useReactFlow<LLMNodeData>(); // 获取 setNodes 方法
|
||||
|
||||
// 从 props 更新内部状态 (如果外部数据变化)
|
||||
// useEffect(() => {
|
||||
// setCurrentData(data);
|
||||
// }, [data]);
|
||||
|
||||
// 处理内部表单变化的通用回调 - 直接更新 React Flow 主状态
|
||||
const handleDataChange = useCallback((key: keyof LLMNodeData, value: any) => {
|
||||
// 使用 setNodes 更新特定节点的数据
|
||||
setNodes((nds: Node<LLMNodeData>[]) => // 显式指定类型
|
||||
nds.map((node) => {
|
||||
if (node.id === id) {
|
||||
// 创建一个新的 data 对象
|
||||
const updatedData = { ...node.data, [key]: value };
|
||||
return { ...node, data: updatedData };
|
||||
}
|
||||
return node;
|
||||
})
|
||||
);
|
||||
console.log(`(LLMNode) Node ${id} data updated:`, { [key]: value });
|
||||
}, [id, setNodes]); // 依赖 id 和 setNodes
|
||||
|
||||
const handleSliderChange = useCallback((value: number[]) => { handleDataChange('temperature', value[0]); }, [handleDataChange]);
|
||||
const handleSelectChange = useCallback((value: string) => { handleDataChange('model', value); }, [handleDataChange]);
|
||||
const handleTextChange = useCallback((event: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
|
||||
const { name, value } = event.target;
|
||||
handleDataChange(name as keyof LLMNodeData, value);
|
||||
}, [handleDataChange]);
|
||||
|
||||
// 检查 inputConnected 状态是否需要更新 (如果外部更新了)
|
||||
// 注意:这个逻辑依赖于父组件正确更新了 data.inputConnected
|
||||
useEffect(() => {
|
||||
// 可以在这里添加逻辑,如果 props.data.inputConnected 和 UI 显示不一致时触发更新
|
||||
// 但通常连接状态由 onConnect/onEdgesChange 在父组件处理更佳
|
||||
// updateNodeInternals(id); // 如果 Handle 显示依赖 inputConnected,可能需要调用
|
||||
}, [data.inputConnected, id, updateNodeInternals]);
|
||||
|
||||
return (
|
||||
// 调整宽度,例如 w-96 (24rem)
|
||||
<div className="react-flow__node react-flow__node-genericNode nopan selected selectable draggable bg-purple-50 dark:bg-gray-800 border border-purple-200 dark:border-gray-700 rounded-lg shadow-lg w-96 overflow-hidden">
|
||||
<div className="bg-purple-100 dark:bg-gray-700 p-3 border-b border-purple-200 dark:border-gray-600">
|
||||
<div className="flex items-center gap-2">
|
||||
<BrainCircuit size={18} className="text-purple-700 dark:text-purple-300" />
|
||||
<strong className="text-purple-800 dark:text-purple-200">LLM 调用</strong>
|
||||
</div>
|
||||
<p className="text-xs text-purple-600 dark:text-purple-400 mt-1">使用大语言模型生成文本</p>
|
||||
</div>
|
||||
<div className="p-4 space-y-3">
|
||||
<div className="relative nodrag">
|
||||
<Label className="text-xs font-semibold text-gray-500 dark:text-gray-400">输入</Label>
|
||||
<Handle type="target" position={Position.Left} id="input-text" isConnectable={isConnectable} className="w-3 h-3 !bg-blue-500 top-1/2" />
|
||||
{/* 直接使用 props.data.inputConnected */}
|
||||
{!data.inputConnected && <p className="text-xs text-gray-400 italic mt-1">连接文本或提示</p>}
|
||||
</div>
|
||||
<div className="nodrag">
|
||||
<Label htmlFor={`systemPrompt-${id}`} className="text-xs font-semibold">系统提示</Label>
|
||||
{/* 直接使用 props.data 和 onChange 回调 */}
|
||||
<Textarea id={`systemPrompt-${id}`} name="systemPrompt" value={data.systemPrompt} onChange={handleTextChange} placeholder="例如:你是一个乐于助人的助手。" className="mt-1 text-xs min-h-[60px] bg-white dark:bg-gray-700" rows={3} />
|
||||
</div>
|
||||
<div className="nodrag">
|
||||
<Label htmlFor={`model-${id}`} className="text-xs font-semibold">模型名称</Label>
|
||||
<Select name="model" value={data.model} onValueChange={handleSelectChange}>
|
||||
<SelectTrigger id={`model-${id}`} className="mt-1 h-8 text-xs bg-white dark:bg-gray-700"><SelectValue placeholder="选择模型" /></SelectTrigger>
|
||||
<SelectContent>{availableModels.map(model => (<SelectItem key={model.value} value={model.value} className="text-xs">{model.label}</SelectItem>))}</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="nodrag">
|
||||
<Label htmlFor={`apiKey-${id}`} className="text-xs font-semibold">API Key (不安全)</Label>
|
||||
<Input id={`apiKey-${id}`} name="apiKey" type="password" value={data.apiKey || ''} onChange={handleTextChange} placeholder="sk-..." className="mt-1 h-8 text-xs bg-white dark:bg-gray-700" />
|
||||
<p className="text-xs text-red-500 mt-1">警告:不建议在此处输入密钥。</p>
|
||||
</div>
|
||||
<div className="nodrag">
|
||||
<Label htmlFor={`temperature-${id}`} className="text-xs font-semibold">温度: {data.temperature?.toFixed(1) ?? 'N/A'}</Label>
|
||||
<Slider id={`temperature-${id}`} min={0} max={1} step={0.1} value={[data.temperature ?? 0.7]} onValueChange={handleSliderChange} className="mt-2" />
|
||||
<div className="flex justify-between text-xs text-gray-500 dark:text-gray-400 mt-1"><span>精确</span><span>创造性</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative p-2 border-t border-purple-200 dark:border-gray-600">
|
||||
<Label className="text-xs font-semibold text-gray-500 dark:text-gray-400 block text-right pr-6">输出</Label>
|
||||
<Handle type="source" position={Position.Right} id="output-message" isConnectable={isConnectable} className="w-3 h-3 !bg-green-500 top-1/2" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
LLMNodeComponent.displayName = 'LLMNode';
|
||||
export const LLMNode = memo(LLMNodeComponent);
|
||||
27
frontend/app/workflow/components/OutputNode.tsx
Normal file
27
frontend/app/workflow/components/OutputNode.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
// File: frontend/app/workflow/components/OutputNode.tsx
|
||||
// Description: 自定义结束节点组件
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { Handle, Position } from 'reactflow';
|
||||
import { CheckCircle } from 'lucide-react';
|
||||
import type { OutputNodeData, CustomNodeProps } from './types'; // 导入类型
|
||||
|
||||
const OutputNodeComponent = ({ data }: CustomNodeProps<OutputNodeData>) => {
|
||||
return (
|
||||
<div className="react-flow__node-default bg-blue-100 dark:bg-blue-900 border-blue-300 dark:border-blue-700 p-4 rounded-lg shadow-md w-40">
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Top}
|
||||
id="output-target"
|
||||
className="w-3 h-3 !bg-blue-500"
|
||||
/>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<CheckCircle size={16} className="text-blue-700 dark:text-blue-300" />
|
||||
<strong className="text-blue-800 dark:text-blue-200">{data.label || '结束流程'}</strong>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
OutputNodeComponent.displayName = 'OutputNode';
|
||||
export const OutputNode = memo(OutputNodeComponent);
|
||||
28
frontend/app/workflow/components/StartNode.tsx
Normal file
28
frontend/app/workflow/components/StartNode.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
// File: frontend/app/workflow/components/StartNode.tsx
|
||||
// Description: 自定义开始节点组件
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { Handle, Position } from 'reactflow';
|
||||
import { Play } from 'lucide-react';
|
||||
import type { StartNodeData, CustomNodeProps } from './types'; // 导入类型
|
||||
|
||||
const StartNodeComponent = ({ data }: CustomNodeProps<StartNodeData>) => {
|
||||
return (
|
||||
<div className="react-flow__node-default bg-green-100 dark:bg-green-900 border-green-300 dark:border-green-700 p-4 rounded-lg shadow-md w-40">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Play size={16} className="text-green-700 dark:text-green-300" />
|
||||
<strong className="text-green-800 dark:text-green-200">{data.label || '开始流程'}</strong>
|
||||
</div>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Bottom}
|
||||
id="start-source"
|
||||
className="w-3 h-3 !bg-green-500"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
StartNodeComponent.displayName = 'StartNode';
|
||||
// 使用 memo 优化
|
||||
export const StartNode = memo(StartNodeComponent);
|
||||
44
frontend/app/workflow/components/WorkflowSidebar.tsx
Normal file
44
frontend/app/workflow/components/WorkflowSidebar.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
// File: frontend/app/workflow/components/WorkflowSidebar.tsx
|
||||
// Description: 侧边栏组件,用于拖放节点
|
||||
|
||||
import React, { DragEvent } from 'react';
|
||||
import { MessageSquareText, BrainCircuit, Database, LogOut, Play, CheckCircle } from 'lucide-react';
|
||||
|
||||
// 定义可拖拽的节点类型 (从 page.tsx 移动过来)
|
||||
const nodeTypesForPalette = [
|
||||
{ type: 'startNode', label: '开始流程', icon: Play, defaultData: { label: '开始' } },
|
||||
{ type: 'inputNode', label: '文本输入', icon: MessageSquareText, defaultData: { text: '用户输入...' } },
|
||||
{ type: 'llmNode', label: 'LLM 调用', icon: BrainCircuit, defaultData: { model: 'gpt-3.5-turbo', temperature: 0.7, systemPrompt: '你是一个乐于助人的 AI 助手。' } },
|
||||
{ type: 'ragNode', label: 'RAG 查询', icon: Database, defaultData: { query: '...', knowledgeBase: 'default' } },
|
||||
{ type: 'outputNode', label: '结束流程', icon: CheckCircle, defaultData: { label: '结束' } },
|
||||
];
|
||||
|
||||
|
||||
export const WorkflowSidebar = () => {
|
||||
const onDragStart = (event: DragEvent, nodeType: string, defaultData: any) => {
|
||||
const nodeInfo = JSON.stringify({ nodeType, defaultData });
|
||||
event.dataTransfer.setData('application/reactflow', nodeInfo);
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
console.log(`Drag Start: ${nodeType}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<aside className="w-64 bg-white dark:bg-gray-800 p-4 border-r dark:border-gray-700 shadow-md overflow-y-auto flex-shrink-0"> {/* 添加 flex-shrink-0 */}
|
||||
<h3 className="text-lg font-semibold mb-4 text-gray-800 dark:text-gray-200">节点面板</h3>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mb-4">将节点拖拽到右侧画布</p>
|
||||
<div className="space-y-2">
|
||||
{nodeTypesForPalette.map((nodeInfo) => (
|
||||
<div
|
||||
key={nodeInfo.type}
|
||||
className="p-3 border dark:border-gray-600 rounded-lg cursor-grab flex items-center gap-2 bg-gray-50 dark:bg-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 transition-colors"
|
||||
onDragStart={(event) => onDragStart(event, nodeInfo.type, nodeInfo.defaultData)}
|
||||
draggable
|
||||
>
|
||||
<nodeInfo.icon size={18} className="text-gray-600 dark:text-gray-300 flex-shrink-0" />
|
||||
<span className="text-sm text-gray-700 dark:text-gray-200">{nodeInfo.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
34
frontend/app/workflow/components/types.ts
Normal file
34
frontend/app/workflow/components/types.ts
Normal file
@ -0,0 +1,34 @@
|
||||
// File: frontend/app/workflow/components/types.ts
|
||||
// Description: Shared types for workflow components
|
||||
|
||||
import { NodeProps } from 'reactflow';
|
||||
|
||||
// 定义 LLM 节点的数据结构
|
||||
export interface LLMNodeData {
|
||||
model: string;
|
||||
temperature: number;
|
||||
systemPrompt: string;
|
||||
apiKey?: string;
|
||||
inputConnected?: boolean;
|
||||
}
|
||||
|
||||
// 定义其他节点可能需要的数据类型
|
||||
export interface StartNodeData {
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface OutputNodeData {
|
||||
label: string;
|
||||
}
|
||||
|
||||
// 可以将 NodeProps 包装一下,方便使用
|
||||
export type CustomNodeProps<T> = NodeProps<T>;
|
||||
|
||||
// 可选的模型列表 (也可以放在这里共享)
|
||||
export const availableModels = [
|
||||
{ value: "gpt-3.5-turbo", label: "GPT-3.5 Turbo" },
|
||||
{ value: "gpt-4", label: "GPT-4" },
|
||||
{ value: "gpt-4-turbo", label: "GPT-4 Turbo" },
|
||||
{ value: "gemini-pro", label: "Gemini Pro" },
|
||||
{ value: "deepseek-coder", label: "DeepSeek Coder" },
|
||||
];
|
||||
@ -1,9 +1,9 @@
|
||||
// File: frontend/app/workflow/page.tsx
|
||||
// Description: 工作流编辑器页面,添加侧边栏用于拖放节点
|
||||
// File: frontend/app/workflow/page.tsx (更新)
|
||||
// Description: 工作流编辑器主页面,引入模块化组件
|
||||
|
||||
'use client';
|
||||
|
||||
import React, { useState, useCallback, useRef, DragEvent } from 'react'; // 添加 useRef, DragEvent
|
||||
import React, { useState, useCallback, useRef, DragEvent, useMemo } from 'react';
|
||||
import ReactFlow, {
|
||||
ReactFlowProvider,
|
||||
MiniMap,
|
||||
@ -21,80 +21,93 @@ import ReactFlow, {
|
||||
EdgeChange,
|
||||
applyNodeChanges,
|
||||
applyEdgeChanges,
|
||||
useReactFlow, // 导入 useReactFlow hook
|
||||
useReactFlow,
|
||||
XYPosition,
|
||||
BackgroundVariant, // 导入 XYPosition 类型
|
||||
BackgroundVariant,
|
||||
} from 'reactflow';
|
||||
import { MessageSquareText, BrainCircuit, Database, LogOut, Play } from 'lucide-react'; // 导入一些节点图标
|
||||
|
||||
// 引入自定义节点和侧边栏组件
|
||||
import { StartNode } from './components/StartNode';
|
||||
import { OutputNode } from './components/OutputNode';
|
||||
import { LLMNode } from './components/LLMNode';
|
||||
import { WorkflowSidebar } from './components/WorkflowSidebar';
|
||||
// 引入类型 (如果需要)
|
||||
// import type { LLMNodeData } from './components/types';
|
||||
|
||||
import 'reactflow/dist/style.css';
|
||||
|
||||
// --- 初始节点和边数据 (可以清空或保留示例) ---
|
||||
// --- 初始节点和边数据 ---
|
||||
const initialNodes: Node[] = [
|
||||
// { id: '1', type: 'inputNode', data: { label: '开始' }, position: { x: 100, y: 100 } },
|
||||
{ id: 'start-initial', type: 'startNode', data: { label: '开始' }, position: { x: 150, y: 50 } },
|
||||
{
|
||||
id: 'llm-initial',
|
||||
type: 'llmNode',
|
||||
data: {
|
||||
model: 'gpt-3.5-turbo',
|
||||
temperature: 0.7,
|
||||
systemPrompt: '你是一个乐于助人的 AI 助手。',
|
||||
inputConnected: false,
|
||||
apiKey: '',
|
||||
},
|
||||
position: { x: 350, y: 150 }
|
||||
},
|
||||
{ id: 'end-initial', type: 'outputNode', data: { label: '结束' }, position: { x: 650, y: 350 } },
|
||||
];
|
||||
const initialEdges: Edge[] = [];
|
||||
|
||||
// --- 定义可拖拽的节点类型 ---
|
||||
const nodeTypesForPalette = [
|
||||
{ type: 'inputNode', label: '输入', icon: MessageSquareText, defaultData: { text: '用户输入...' } },
|
||||
{ type: 'llmNode', label: 'LLM 调用', icon: BrainCircuit, defaultData: { model: 'gpt-3.5-turbo', prompt: '...' } },
|
||||
{ type: 'ragNode', label: 'RAG 查询', icon: Database, defaultData: { query: '...', knowledgeBase: 'default' } },
|
||||
{ type: 'outputNode', label: '输出', icon: LogOut, defaultData: { result: null } },
|
||||
{ type: 'startNode', label: '开始流程', icon: Play, defaultData: {} }, // 添加一个开始节点类型
|
||||
];
|
||||
|
||||
// --- 侧边栏组件 ---
|
||||
const Sidebar = () => {
|
||||
// 拖拽开始时设置数据
|
||||
const onDragStart = (event: DragEvent, nodeType: string, defaultData: any) => {
|
||||
// 将节点类型和默认数据存储在 dataTransfer 中
|
||||
const nodeInfo = JSON.stringify({ nodeType, defaultData });
|
||||
event.dataTransfer.setData('application/reactflow', nodeInfo);
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
console.log(`Drag Start: ${nodeType}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<aside className="w-64 bg-white dark:bg-gray-800 p-4 border-r dark:border-gray-700 shadow-md overflow-y-auto">
|
||||
<h3 className="text-lg font-semibold mb-4 text-gray-800 dark:text-gray-200">节点面板</h3>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mb-4">将节点拖拽到右侧画布</p>
|
||||
<div className="space-y-2">
|
||||
{nodeTypesForPalette.map((nodeInfo) => (
|
||||
<div
|
||||
key={nodeInfo.type}
|
||||
className="p-3 border dark:border-gray-600 rounded-lg cursor-grab flex items-center gap-2 bg-gray-50 dark:bg-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 transition-colors"
|
||||
onDragStart={(event) => onDragStart(event, nodeInfo.type, nodeInfo.defaultData)}
|
||||
draggable // 设置为可拖拽
|
||||
>
|
||||
<nodeInfo.icon size={18} className="text-gray-600 dark:text-gray-300 flex-shrink-0" />
|
||||
<span className="text-sm text-gray-700 dark:text-gray-200">{nodeInfo.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// --- 工作流编辑器组件 ---
|
||||
function WorkflowEditor() {
|
||||
const reactFlowWrapper = useRef<HTMLDivElement>(null); // Ref 指向 React Flow 容器
|
||||
const [nodes, setNodes] = useState<Node[]>(initialNodes);
|
||||
const [edges, setEdges] = useState<Edge[]>(initialEdges);
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
const { project } = useReactFlow(); // 使用 hook 获取 project 方法
|
||||
|
||||
const onNodesChange = useCallback(
|
||||
(changes: NodeChange[]) => setNodes((nds) => applyNodeChanges(changes, nds)),
|
||||
[setNodes]
|
||||
);
|
||||
const onEdgesChange = useCallback(
|
||||
(changes: EdgeChange[]) => setEdges((eds) => applyEdgeChanges(changes, eds)),
|
||||
[setEdges]
|
||||
);
|
||||
// --- 注册自定义节点类型 ---
|
||||
const nodeTypes = useMemo(() => ({
|
||||
startNode: StartNode,
|
||||
outputNode: OutputNode,
|
||||
llmNode: LLMNode, // LLMNode 现在需要一种方式来调用 updateNodeData
|
||||
// inputNode: CustomInputNode,
|
||||
// ragNode: RAGNode,
|
||||
}), []); // 移除 updateNodeData 依赖,因为它不应该直接传递
|
||||
|
||||
const onConnect = useCallback(
|
||||
(connection: Connection) => setEdges((eds) => addEdge({ ...connection, markerEnd: { type: MarkerType.ArrowClosed } }, eds)),
|
||||
[setEdges]
|
||||
(connection: Connection) => {
|
||||
setEdges((eds) => addEdge({ ...connection, markerEnd: { type: MarkerType.ArrowClosed } }, eds));
|
||||
if (connection.targetHandle === 'input-text' && connection.target) {
|
||||
setNodes((nds) =>
|
||||
nds.map((node) => {
|
||||
if (node.id === connection.target) {
|
||||
return { ...node, data: { ...node.data, inputConnected: true } };
|
||||
}
|
||||
return node;
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
[setEdges, setNodes] // 保持 updateNodeData 依赖
|
||||
);
|
||||
|
||||
const onEdgesChangeIntercepted = useCallback(
|
||||
(changes: EdgeChange[]) => {
|
||||
changes.forEach(change => {
|
||||
if (change.type === 'remove') {
|
||||
const edgeToRemove = edges.find(edge => edge.id === change.id);
|
||||
if (edgeToRemove?.targetHandle === 'input-text' && edgeToRemove.target) {
|
||||
setNodes((nds) =>
|
||||
nds.map((node) => {
|
||||
if (node.id === edgeToRemove.target) {
|
||||
return { ...node, data: { ...node.data, inputConnected: false } };
|
||||
}
|
||||
return node;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
onEdgesChange(changes);
|
||||
},
|
||||
[edges, onEdgesChange, setNodes] // 保持 updateNodeData 依赖
|
||||
);
|
||||
|
||||
// 处理画布上的拖拽悬停事件
|
||||
@ -146,10 +159,10 @@ function WorkflowEditor() {
|
||||
id: `${nodeType}-${Date.now()}`, // 使用更唯一的 ID
|
||||
type: nodeType, // 设置节点类型
|
||||
position,
|
||||
data: { label: `${nodeType} node`, ...defaultData }, // 合并默认数据
|
||||
data: { label: defaultData?.label || `${nodeType} node`, ...defaultData },
|
||||
// 根据节点类型设置 Handle 位置 (可选,也可以在自定义节点内部定义)
|
||||
sourcePosition: Position.Bottom,
|
||||
targetPosition: Position.Top,
|
||||
// sourcePosition: Position.Bottom,
|
||||
// targetPosition: Position.Top,
|
||||
};
|
||||
|
||||
console.log('Node dropped:', newNode);
|
||||
@ -159,15 +172,6 @@ function WorkflowEditor() {
|
||||
[project, setNodes] // 依赖 project 方法和 setNodes
|
||||
);
|
||||
|
||||
// --- (可选) 注册自定义节点类型 ---
|
||||
// const nodeTypes = useMemo(() => ({
|
||||
// inputNode: CustomInputNode,
|
||||
// llmNode: LLMNode,
|
||||
// ragNode: RAGNode,
|
||||
// outputNode: CustomOutputNode,
|
||||
// startNode: StartNode,
|
||||
// }), []);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden">
|
||||
<h1 className="text-xl font-semibold p-4 border-b dark:border-gray-700 text-gray-800 dark:text-gray-200">
|
||||
@ -176,18 +180,18 @@ function WorkflowEditor() {
|
||||
{/* 主容器使用 Flexbox */}
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* 侧边栏 */}
|
||||
<Sidebar />
|
||||
<WorkflowSidebar />
|
||||
{/* React Flow 画布容器 */}
|
||||
<div className="flex-1 h-full" ref={reactFlowWrapper}> {/* 添加 ref */}
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onEdgesChange={onEdgesChangeIntercepted}
|
||||
onConnect={onConnect}
|
||||
onDragOver={onDragOver} // 添加 onDragOver
|
||||
onDrop={onDrop} // 添加 onDrop
|
||||
// nodeTypes={nodeTypes}
|
||||
nodeTypes={nodeTypes} // <--- 传递自定义节点类型
|
||||
fitView
|
||||
className="bg-gray-50 dark:bg-gray-900"
|
||||
>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user