最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用React?Flow構(gòu)建一個(gè)AI工作流工具

 更新時(shí)間:2026年03月30日 08:32:26   作者:?jiǎn)柕里w魚  
本文介紹了基于ReactFlow構(gòu)建AI工作流工具的方法,涵蓋核心概念、目標(biāo)功能、開發(fā)環(huán)境準(zhǔn)備、代碼實(shí)現(xiàn)、工程構(gòu)建與部署等內(nèi)容,該工具支持可視化拖拽、右鍵菜單、節(jié)點(diǎn)類型、JSON導(dǎo)出及后端執(zhí)行等功能,需要的朋友可以參考下

一、React Flow 核心概念(AI 工作流視角)

概念說明AI 工作流中的意義
Node(節(jié)點(diǎn))圖中的基本單元,代表一個(gè)操作如:User Input、LLM Call、RAG Retriever、Code Executor
Edge(邊)連接兩個(gè)節(jié)點(diǎn)的有向線表示數(shù)據(jù)流向(如 prompt → LLM → output)
Handle(句柄)節(jié)點(diǎn)上的連接點(diǎn)(source/target)控制哪些端口可連入/連出(如 LLM 節(jié)點(diǎn)只有 1 個(gè)輸入、1 個(gè)輸出)
ReactFlow Provider提供全局狀態(tài)和方法管理 nodes/edges、縮放、選擇等
Custom Node自定義節(jié)點(diǎn)組件可嵌入表單、下拉菜單、實(shí)時(shí)預(yù)覽等

關(guān)鍵思想每個(gè)節(jié)點(diǎn) = 一個(gè) AI 技能(Skill/Tool),整個(gè)圖 = 一個(gè)可執(zhí)行的 Agent 工作流。

二、目標(biāo)功能清單

我們要構(gòu)建的 AI 工作流工具需支持:

? 可視化拖拽節(jié)點(diǎn)
? 右鍵菜單:添加新節(jié)點(diǎn) / 刪除選中節(jié)點(diǎn)
? 節(jié)點(diǎn)類型:Input、LLM、Output、RAG
? 導(dǎo)出為 JSON(用于保存或分享)
? “發(fā)送到后端”按鈕:將 flow 發(fā)送給 API 執(zhí)行
? 響應(yīng)式布局 + 性能優(yōu)化

三、開發(fā)環(huán)境準(zhǔn)備

1. 前提

  • Node.js ≥ 18
  • npm / yarn / pnpm

2. 創(chuàng)建項(xiàng)目

npx create-react-app ai-workflow-builder
cd ai-workflow-builder

3. 安裝依賴

npm install @xyflow/react uuid axios
# 或
yarn add @xyflow/react uuid axios
  • @xyflow/react:React Flow 官方包(v12+)
  • uuid:生成唯一節(jié)點(diǎn) ID
  • axios:用于調(diào)用后端 API

4. 清理默認(rèn)文件

刪除 src/App.css、src/logo.svg,保留 src/App.jssrc/index.js

四、完整可執(zhí)行代碼實(shí)現(xiàn)

文件結(jié)構(gòu)

src/
├── components/
│   ├── nodes/
│   │   ├── InputNode.jsx
│   │   ├── LLMNode.jsx
│   │   └── OutputNode.jsx
│   └── RightClickMenu.jsx
├── App.js
└── index.js

Step 1:自定義節(jié)點(diǎn)組件(src/components/nodes/*.jsx)

InputNode.jsx

// src/components/nodes/InputNode.jsx
import { Handle, Position } from '@xyflow/react';
export default function InputNode({ data }) {
  return (
    <div className="border-2 border-blue-500 rounded-lg p-3 bg-blue-50 w-48 shadow">
      <strong>?? User Input</strong>
      <Handle type="source" position={Position.Right} id="out" />
    </div>
  );
}

LLMNode.jsx

// src/components/nodes/LLMNode.jsx
import { Handle, Position } from '@xyflow/react';
import { useState } from 'react';

export default function LLMNode({ data, id }) {
  const [model, setModel] = useState(data?.model || 'gpt-4o');

  const handleModelChange = (e) => {
    if (data?.onModelChange) {
      data.onModelChange(id, e.target.value);
    }
  };

  return (
    <div className="border-2 border-purple-500 rounded-lg p-3 bg-white w-48 shadow">
      <strong>?? LLM</strong>
      <div className="mt-2">
        <select value={model} onChange={handleModelChange} className="w-full text-sm border rounded p-1">
          <option value="gpt-4o">GPT-4o</option>
          <option value="claude-3-5-sonnet">Claude 3.5</option>
          <option value="qwen:7b">Qwen-7B (Ollama)</option>
        </select>
      </div>
      <Handle type="target" position={Position.Left} id="in" />
      <Handle type="source" position={Position.Right} id="out" />
    </div>
  );
}

OutputNode.jsx

// src/components/nodes/OutputNode.jsx
import { Handle, Position } from '@xyflow/react';

export default function OutputNode() {
  return (
    <div className="border-2 border-green-500 rounded-lg p-3 bg-green-50 w-48 shadow">
      <em>? Final Output</em>
      <Handle type="target" position={Position.Left} id="in" />
    </div>
  );
}

Step 2:右鍵菜單組件(src/components/RightClickMenu.jsx)

// src/components/RightClickMenu.jsx
import { useState, useEffect } from 'react';

export default function RightClickMenu({ x, y, onAddNode, onClose }) {
  const [position, setPosition] = useState({ x, y });

  useEffect(() => {
    setPosition({ x, y });
  }, [x, y]);

  const handleClick = (type) => {
    onAddNode(type, position.x, position.y);
    onClose();
  };

  // 防止菜單超出視窗
  const menuStyle = {
    position: 'fixed',
    top: Math.min(position.y, window.innerHeight - 120),
    left: Math.min(position.x, window.innerWidth - 150),
    backgroundColor: 'white',
    border: '1px solid #ccc',
    borderRadius: '4px',
    boxShadow: '0 2px 10px rgba(0,0,0,0.2)',
    zIndex: 1000,
    padding: '4px 0',
  };

  return (
    <div style={menuStyle} className="text-sm">
      <button onClick={() => handleClick('input')} className="block w-full text-left px-4 py-2 hover:bg-gray-100">? Add Input</button>
      <button onClick={() => handleClick('llm')} className="block w-full text-left px-4 py-2 hover:bg-gray-100">? Add LLM</button>
      <button onClick={() => handleClick('output')} className="block w-full text-left px-4 py-2 hover:bg-gray-100">? Add Output</button>
      <hr className="my-1" />
      <button onClick={() => handleClick('delete')} className="block w-full text-left px-4 py-2 hover:bg-red-50 text-red-600">??? Delete Selected</button>
    </div>
  );
}

Step 3:主應(yīng)用邏輯(src/App.js)

// src/App.js
import React, { useState, useCallback, useRef } from 'react';
import ReactFlow, {
  Controls,
  MiniMap,
  Background,
  useNodesState,
  useEdgesState,
  addEdge,
  ConnectionMode,
  applyNodeChanges,
  applyEdgeChanges,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import { v4 as uuidv4 } from 'uuid';
import axios from 'axios';

// 導(dǎo)入自定義節(jié)點(diǎn)
import InputNode from './components/nodes/InputNode';
import LLMNode from './components/nodes/LLMNode';
import OutputNode from './components/nodes/OutputNode';
import RightClickMenu from './components/RightClickMenu';

const nodeTypes = {
  input: InputNode,
  llm: LLMNode,
  output: OutputNode,
};

const initialNodes = [
  { id: '1', type: 'input', position: { x: 100, y: 100 }, data: {} },
  { id: '2', type: 'llm', position: { x: 300, y: 100 }, data: { model: 'gpt-4o' } },
  { id: '3', type: 'output', position: { x: 500, y: 100 }, data: {} },
];

const initialEdges = [
  { id: 'e1-2', source: '1', target: '2', sourceHandle: 'out', targetHandle: 'in' },
  { id: 'e2-3', source: '2', target: '3', sourceHandle: 'out', targetHandle: 'in' },
];

export default function App() {
  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
  const [menu, setMenu] = useState(null);
  const reactFlowWrapper = useRef(null);

  // 處理模型變更
  const handleModelChange = useCallback((nodeId, model) => {
    setNodes((nds) =>
      nds.map((node) =>
        node.id === nodeId ? { ...node, data: { ...node.data, model } } : node
      )
    );
  }, [setNodes]);

  // 添加節(jié)點(diǎn)
  const addNode = useCallback((type, x, y) => {
    if (type === 'delete') {
      // 刪除選中節(jié)點(diǎn)
      const selectedIds = nodes.filter(n => n.selected).map(n => n.id);
      setNodes(nds => nds.filter(n => !selectedIds.includes(n.id)));
      setEdges(eds => eds.filter(e => !selectedIds.includes(e.source) && !selectedIds.includes(e.target)));
      return;
    }

    const newNode = {
      id: uuidv4(),
      type,
      position: { x: x - 80, y: y - 30 },
      data: type === 'llm' ? { model: 'gpt-4o', onModelChange: handleModelChange } : {},
    };
    setNodes(nds => nds.concat(newNode));
  }, [nodes, setNodes, setEdges, handleModelChange]);

  // 右鍵事件
  const onPaneClick = useCallback(() => setMenu(null), []);
  const onPaneContextMenu = useCallback((event) => {
    event.preventDefault();
    setMenu({ x: event.clientX, y: event.clientY });
  }, []);

  // 連線
  const onConnect = useCallback((params) => {
    if (params.source !== params.target) {
      setEdges(eds => addEdge(params, eds));
    }
  }, [setEdges]);

  // 導(dǎo)出 JSON
  const exportFlow = () => {
    const flowData = { nodes, edges };
    const dataStr = JSON.stringify(flowData, null, 2);
    const blob = new Blob([dataStr], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'ai-workflow.json';
    a.click();
    URL.revokeObjectURL(url);
  };

  // 發(fā)送到后端執(zhí)行
  const sendToBackend = async () => {
    try {
      const response = await axios.post('http://localhost:8000/execute', { nodes, edges });
      alert('Execution started! Result: ' + JSON.stringify(response.data));
    } catch (error) {
      alert('Error: ' + error.message);
    }
  };

  return (
    <div style={{ width: '100vw', height: '100vh' }} ref={reactFlowWrapper}>
      <ReactFlow
        nodes={nodes}
        edges={edges}
        onNodesChange={onNodesChange}
        onEdgesChange={onEdgesChange}
        onConnect={onConnect}
        onPaneClick={onPaneClick}
        onPaneContextMenu={onPaneContextMenu}
        nodeTypes={nodeTypes}
        connectionMode={ConnectionMode.LOOSE}
        fitView
        attributionPosition="top-right"
      >
        <Background />
        <Controls />
        <MiniMap />
        
        {/* 頂部工具欄 */}
        <div style={{
          position: 'absolute',
          top: 10,
          left: 10,
          display: 'flex',
          gap: 10,
          zIndex: 10,
        }}>
          <button onClick={exportFlow} className="bg-blue-500 text-white px-3 py-1 rounded text-sm">
            ?? Export JSON
          </button>
          <button onClick={sendToBackend} className="bg-green-500 text-white px-3 py-1 rounded text-sm">
            ?? Send to Backend
          </button>
        </div>
      </ReactFlow>

      {/* 右鍵菜單 */}
      {menu && (
        <RightClickMenu
          x={menu.x}
          y={menu.y}
          onAddNode={addNode}
          onClose={() => setMenu(null)}
        />
      )}
    </div>
  );
}

Step 4:簡(jiǎn)單后端模擬(可選,用于測(cè)試)

創(chuàng)建 backend/server.js(需 Node.js):

// backend/server.js
const express = require('express');
const cors = require('cors');

const app = express();
app.use(cors());
app.use(express.json());

app.post('/execute', (req, res) => {
  console.log('Received flow:', req.body);
  // 模擬執(zhí)行:返回節(jié)點(diǎn)數(shù)量
  res.json({
    status: 'success',
    message: `Executed workflow with ${req.body.nodes.length} nodes`,
    result: "Hello from AI Workflow!"
  });
});

app.listen(8000, () => console.log('Backend running on http://localhost:8000'));

安裝依賴并啟動(dòng):

cd backend
npm init -y
npm install express cors
node server.js

五、工程構(gòu)建與打包

1. 本地開發(fā)

npm start
# 訪問 http://localhost:3000

2. 生產(chǎn)構(gòu)建

npm run build
# 輸出到 ./build 目錄

3. 部署

  • build/ 目錄部署到 Nginx、Vercel、Netlify 等
  • 示例 Nginx 配置:
server {
  listen 80;
  server_name your-domain.com;
  root /path/to/build;
  index index.html;
  location / {
    try_files $uri $uri/ /index.html;
  }
}

六、功能演示說明

操作效果
右鍵空白處彈出菜單:可添加 Input/LLM/Output 節(jié)點(diǎn)
右鍵選中節(jié)點(diǎn) → Delete刪除所有選中節(jié)點(diǎn)及關(guān)聯(lián)連線
拖拽節(jié)點(diǎn)自由移動(dòng)
點(diǎn)擊“Export JSON”下載 ai-workflow.json
點(diǎn)擊“Send to Backend”POST 到 http://localhost:8000/execute

JSON 結(jié)構(gòu)示例

{
  "nodes": [
    { "id": "1", "type": "input", "position": { "x": 100, "y": 100 }, "data": {} },
    { "id": "2", "type": "llm", "position": { "x": 300, "y": 100 }, "data": { "model": "qwen:7b" } }
  ],
  "edges": [
    { "id": "e1-2", "source": "1", "target": "2", "sourceHandle": "out", "targetHandle": "in" }
  ]
}

后端可根據(jù)此結(jié)構(gòu)動(dòng)態(tài)構(gòu)建 LangChain 鏈或調(diào)用 MCP 服務(wù)。

七、后續(xù)擴(kuò)展建議

  1. 保存到數(shù)據(jù)庫:將 JSON 存入 PostgreSQL/MongoDB
  2. 版本管理:支持 flow 版本回溯
  3. 實(shí)時(shí)協(xié)作:集成 Yjs 實(shí)現(xiàn)多人協(xié)同編輯
  4. 執(zhí)行結(jié)果展示:在節(jié)點(diǎn)上顯示 LLM 輸出
  5. 權(quán)限控制:企業(yè)級(jí)用戶/團(tuán)隊(duì)管理

總結(jié)

你現(xiàn)在已經(jīng)擁有一個(gè):

  • 基于 React Flow 的 AI 工作流構(gòu)建器
  • 支持 右鍵操作、JSON 導(dǎo)出、后端集成
  • 可直接運(yùn)行、打包、部署

以上就是使用React Flow構(gòu)建一個(gè)AI工作流工具的詳細(xì)內(nèi)容,更多關(guān)于React Flow構(gòu)建AI工作流的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • React特征Form?單向數(shù)據(jù)流示例詳解

    React特征Form?單向數(shù)據(jù)流示例詳解

    這篇文章主要為大家介紹了React特征Form?單向數(shù)據(jù)流示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • ahooks封裝cookie?localStorage?sessionStorage方法

    ahooks封裝cookie?localStorage?sessionStorage方法

    這篇文章主要為大家介紹了ahooks封裝cookie?localStorage?sessionStorage的方法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • 淺析React 對(duì)state的理解

    淺析React 對(duì)state的理解

    state狀態(tài)是組件實(shí)例對(duì)象身上的狀態(tài),不是組件類本身身上的,是由這個(gè)類締造的實(shí)例身上的。這篇文章主要介紹了React 對(duì)state的理解,需要的朋友可以參考下
    2021-09-09
  • react 路由Link配置詳解

    react 路由Link配置詳解

    本文主要介紹了react 路由Link配置詳解,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • React router基礎(chǔ)使用方法詳解

    React router基礎(chǔ)使用方法詳解

    這篇文章主要介紹了React router基礎(chǔ)使用方法,React Router是React生態(tài)系統(tǒng)中最受歡迎的第三方庫之一,近一半的React項(xiàng)目中使用了React Router,下面就來看看如何在React項(xiàng)目中使用
    2023-04-04
  • react+redux的升級(jí)版todoList的實(shí)現(xiàn)

    react+redux的升級(jí)版todoList的實(shí)現(xiàn)

    本篇文章主要介紹了react+redux的升級(jí)版todoList的實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-12-12
  • 想用好React的你必須要知道的一些事情

    想用好React的你必須要知道的一些事情

    現(xiàn)在最熱門的前端框架,毫無疑問是 React 。下面這篇文章主要給大家分享了關(guān)于想用好React的你必須要知道的一些事情,文中介紹的非常詳細(xì),對(duì)大家具有一定參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧。
    2017-07-07
  • React視頻播放控制組件Video Controls的實(shí)現(xiàn)

    React視頻播放控制組件Video Controls的實(shí)現(xiàn)

    本文主要介紹了React視頻播放控制組件Video Controls的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-02-02
  • react顯示文件上傳進(jìn)度的示例

    react顯示文件上傳進(jìn)度的示例

    這篇文章主要介紹了react顯示文件上傳進(jìn)度的示例,幫助大家更好的理解和學(xué)習(xí)使用react,感興趣的朋友可以了解下
    2021-04-04
  • react如何實(shí)現(xiàn)側(cè)邊欄聯(lián)動(dòng)頭部導(dǎo)航欄效果

    react如何實(shí)現(xiàn)側(cè)邊欄聯(lián)動(dòng)頭部導(dǎo)航欄效果

    這篇文章主要介紹了react如何實(shí)現(xiàn)側(cè)邊欄聯(lián)動(dòng)頭部導(dǎo)航欄效果,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-03-03

最新評(píng)論

西城区| 琼结县| 南木林县| 永靖县| 高台县| 江城| 桐庐县| 衡水市| 偃师市| 敦煌市| 永春县| 庆元县| 岚皋县| 全椒县| 密云县| 兴城市| 合江县| 山西省| 柞水县| 舟曲县| 昌图县| 宝应县| 施秉县| 木兰县| 梨树县| 泸西县| 泰州市| 黑河市| 安阳市| 沈丘县| 宁陵县| 中江县| 德江县| 曲松县| 建水县| 蒙山县| 当阳市| 龙胜| 卓资县| 安新县| 华蓥市|