From 5ed2e5b80965d77f2ae761817d8a58f869316c4b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Nov 2025 13:38:22 +0000 Subject: [PATCH] Implement visual workflow editor with React Flow and custom node types Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> --- package.json | 47 +- pnpm-lock.yaml | 461 +++++++++++++++++++- src/pages/scenario/index.tsx | 2 +- src/pages/scenario/nodeTypes.tsx | 299 +++++++++++++ src/pages/scenario/visualWorkflowModal.tsx | 471 +++++++++++++++++++++ 5 files changed, 1252 insertions(+), 28 deletions(-) create mode 100644 src/pages/scenario/nodeTypes.tsx create mode 100644 src/pages/scenario/visualWorkflowModal.tsx diff --git a/package.json b/package.json index 58c54148..1e48ebb0 100644 --- a/package.json +++ b/package.json @@ -54,12 +54,15 @@ } }, "dependencies": { + "@bufbuild/protobuf": "^2.10.0", "@grpc/grpc-js": "^1.14.0", "@grpc/proto-loader": "^0.8.0", + "@keyv/sqlite": "^4.0.1", "@otplib/preset-default": "^12.0.1", "body-parser": "^1.20.3", "celebrate": "^15.0.3", "chokidar": "^4.0.1", + "compression": "^1.7.4", "cors": "^2.8.5", "cron-parser": "^4.9.0", "cross-spawn": "^7.0.6", @@ -69,54 +72,51 @@ "express-jwt": "^8.4.1", "express-rate-limit": "^7.4.1", "express-urlrewrite": "^2.0.3", - "undici": "^7.9.0", + "helmet": "^8.1.0", "hpagent": "^1.2.0", "http-proxy-middleware": "^3.0.3", "iconv-lite": "^0.6.3", + "ip2region": "2.3.0", "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.2", + "keyv": "^5.2.3", "lodash": "^4.17.21", "multer": "1.4.5-lts.1", "node-schedule": "^2.1.0", "nodemailer": "^6.9.16", "p-queue-cjs": "7.3.4", - "@bufbuild/protobuf": "^2.10.0", + "proper-lockfile": "^4.1.2", "ps-tree": "^1.2.0", "reflect-metadata": "^0.2.2", + "request-ip": "3.3.0", "sequelize": "^6.37.5", "sockjs": "^0.3.24", "sqlite3": "git+https://github.com/whyour/node-sqlite3.git#v1.0.3", "toad-scheduler": "^3.0.1", "typedi": "^0.10.0", + "undici": "^7.9.0", "uuid": "^11.0.3", "winston": "^3.17.0", - "winston-daily-rotate-file": "^5.0.0", - "request-ip": "3.3.0", - "ip2region": "2.3.0", - "keyv": "^5.2.3", - "@keyv/sqlite": "^4.0.1", - "proper-lockfile": "^4.1.2", - "compression": "^1.7.4", - "helmet": "^8.1.0" + "winston-daily-rotate-file": "^5.0.0" }, "devDependencies": { - "@flowgram.ai/free-layout-editor": "^1.0.2", - "@flowgram.ai/core": "^1.0.2", - "@flowgram.ai/reactive": "^1.0.2", - "moment": "2.30.1", "@ant-design/icons": "^5.0.1", "@ant-design/pro-layout": "6.38.22", - "@codemirror/view": "^6.34.1", "@codemirror/state": "^6.4.1", + "@codemirror/view": "^6.34.1", + "@flowgram.ai/core": "^1.0.2", + "@flowgram.ai/free-layout-editor": "^1.0.2", + "@flowgram.ai/reactive": "^1.0.2", "@monaco-editor/react": "4.2.1", "@react-hook/resize-observer": "^2.0.2", - "react-router-dom": "6.26.1", "@types/body-parser": "^1.19.2", + "@types/compression": "^1.7.2", "@types/cors": "^2.8.12", "@types/cross-spawn": "^6.0.2", "@types/express": "^4.17.13", "@types/express-jwt": "^6.0.4", "@types/file-saver": "2.0.2", + "@types/helmet": "^4.0.0", "@types/js-yaml": "^4.0.5", "@types/jsonwebtoken": "^8.5.8", "@types/lodash": "^4.14.185", @@ -124,17 +124,17 @@ "@types/node": "^17.0.21", "@types/node-schedule": "^1.3.2", "@types/nodemailer": "^6.4.4", + "@types/proper-lockfile": "^4.1.4", + "@types/ps-tree": "^1.1.6", "@types/qrcode.react": "^1.0.2", "@types/react": "^18.0.20", "@types/react-copy-to-clipboard": "^5.0.4", "@types/react-dom": "^18.0.6", + "@types/request-ip": "0.0.41", "@types/serve-handler": "^6.1.1", "@types/sockjs": "^0.3.33", "@types/sockjs-client": "^1.5.1", "@types/uuid": "^8.3.4", - "@types/request-ip": "0.0.41", - "@types/proper-lockfile": "^4.1.4", - "@types/ps-tree": "^1.1.6", "@uiw/codemirror-extensions-langs": "^4.21.9", "@uiw/react-codemirror": "^4.21.9", "@umijs/max": "^4.4.4", @@ -146,9 +146,9 @@ "axios": "^1.4.0", "compression-webpack-plugin": "9.2.0", "concurrently": "^7.0.0", - "react-hotkeys-hook": "^4.6.1", "file-saver": "2.0.2", "lint-staged": "^13.0.3", + "moment": "2.30.1", "monaco-editor": "0.33.0", "nodemon": "^3.0.1", "prettier": "^2.5.1", @@ -164,16 +164,17 @@ "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dom": "18.3.1", + "react-hotkeys-hook": "^4.6.1", "react-intl-universal": "^2.12.0", + "react-router-dom": "6.26.1", "react-split-pane": "^0.1.92", + "reactflow": "^11.11.4", "sockjs-client": "^1.6.0", "ts-node": "^10.9.2", "ts-proto": "^2.6.1", "tslib": "^2.4.0", "typescript": "5.2.2", "vh-check": "^2.0.5", - "virtualizedtableforantd4": "1.3.0", - "@types/compression": "^1.7.2", - "@types/helmet": "^4.0.0" + "virtualizedtableforantd4": "1.3.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4e4bfc61..eaf08dc2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -348,6 +348,9 @@ importers: react-split-pane: specifier: ^0.1.92 version: 0.1.92(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + reactflow: + specifier: ^11.11.4 + version: 11.11.4(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) sockjs-client: specifier: ^1.6.0 version: 1.6.1 @@ -2030,6 +2033,42 @@ packages: peerDependencies: react: '>=18' + '@reactflow/background@11.3.14': + resolution: {integrity: sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + + '@reactflow/controls@11.2.14': + resolution: {integrity: sha512-MiJp5VldFD7FrqaBNIrQ85dxChrG6ivuZ+dcFhPQUwOK3HfYgX2RHdBua+gx+40p5Vw5It3dVNp/my4Z3jF0dw==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + + '@reactflow/core@11.11.4': + resolution: {integrity: sha512-H4vODklsjAq3AMq6Np4LE12i1I4Ta9PrDHuBR9GmL8uzTt2l2jh4CiQbEMpvMDcp7xi4be0hgXj+Ysodde/i7Q==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + + '@reactflow/minimap@11.7.14': + resolution: {integrity: sha512-mpwLKKrEAofgFJdkhwR5UQ1JYWlcAAL/ZU/bctBkuNTT1yqV+y0buoNVImsRehVYhJwffSWeSHaBR5/GJjlCSQ==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + + '@reactflow/node-resizer@2.2.14': + resolution: {integrity: sha512-fwqnks83jUlYr6OHcdFEedumWKChTHRGw/kbCxj0oqBd+ekfs+SIp4ddyNU0pdx96JIm5iNFS0oNrmEiJbbSaA==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + + '@reactflow/node-toolbar@1.3.14': + resolution: {integrity: sha512-rbynXQnH/xFNu4P9H+hVqlEUafDCkEoCy0Dg9mG22Sg+rY/0ck6KkrAQrYrTgXusd+cEJOMK0uOOFCK2/5rSGQ==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + '@remix-run/router@1.19.1': resolution: {integrity: sha512-S45oynt/WH19bHbIXjtli6QmwNYvaz+vtnubvNpNDvUOoA/OWh6j1OikIP3G+v5GHdxyC6EXoChG3HgYGEUfcg==} engines: {node: '>=14.0.0'} @@ -2423,6 +2462,99 @@ packages: '@types/cross-spawn@6.0.6': resolution: {integrity: sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==} + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.0': + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-polygon@3.0.2': + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.3': + resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + + '@types/d3-shape@3.1.7': + resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==} + + '@types/d3-time-format@4.0.3': + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/d3@7.4.3': + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -2451,6 +2583,9 @@ packages: '@types/file-saver@2.0.2': resolution: {integrity: sha512-xbqnZmGrCEqi/KUzOkeUSe77p7APvLuyellGaAoeww3CHJ1AbjQWjPSCFtKIzZn8L7LpEax4NXnC+gfa6nM7IA==} + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} @@ -3601,6 +3736,9 @@ packages: resolution: {integrity: sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==} engines: {node: '>= 0.10'} + classcat@5.0.5: + resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==} + classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} @@ -3922,9 +4060,47 @@ packages: d3-array@1.2.4: resolution: {integrity: sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==} + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + d3-polygon@1.0.6: resolution: {integrity: sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==} + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + d@1.0.2: resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} engines: {node: '>=0.12'} @@ -7175,6 +7351,12 @@ packages: peerDependencies: react: '*' + reactflow@11.11.4: + resolution: {integrity: sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} engines: {node: '>=8'} @@ -8567,6 +8749,21 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zustand@4.5.7: + resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + snapshots: '@ahooksjs/use-request@2.8.15(react@18.3.1)': @@ -11033,6 +11230,84 @@ snapshots: '@react-hook/passive-layout-effect': 1.2.1(react@18.3.1) react: 18.3.1 + '@reactflow/background@11.3.14(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@reactflow/core': 11.11.4(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classcat: 5.0.5 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + zustand: 4.5.7(@types/react@18.3.26)(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - immer + + '@reactflow/controls@11.2.14(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@reactflow/core': 11.11.4(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classcat: 5.0.5 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + zustand: 4.5.7(@types/react@18.3.26)(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - immer + + '@reactflow/core@11.11.4(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@types/d3': 7.4.3 + '@types/d3-drag': 3.0.7 + '@types/d3-selection': 3.0.11 + '@types/d3-zoom': 3.0.8 + classcat: 5.0.5 + d3-drag: 3.0.0 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + zustand: 4.5.7(@types/react@18.3.26)(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - immer + + '@reactflow/minimap@11.7.14(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@reactflow/core': 11.11.4(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@types/d3-selection': 3.0.11 + '@types/d3-zoom': 3.0.8 + classcat: 5.0.5 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + zustand: 4.5.7(@types/react@18.3.26)(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - immer + + '@reactflow/node-resizer@2.2.14(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@reactflow/core': 11.11.4(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classcat: 5.0.5 + d3-drag: 3.0.0 + d3-selection: 3.0.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + zustand: 4.5.7(@types/react@18.3.26)(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - immer + + '@reactflow/node-toolbar@1.3.14(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@reactflow/core': 11.11.4(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classcat: 5.0.5 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + zustand: 4.5.7(@types/react@18.3.26)(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - immer + '@remix-run/router@1.19.1': {} '@replit/codemirror-lang-nix@6.0.1(@codemirror/autocomplete@6.19.1)(@codemirror/language@6.11.3)(@codemirror/state@6.5.2)(@codemirror/view@6.38.6)(@lezer/common@1.3.0)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.3)': @@ -11534,6 +11809,123 @@ snapshots: dependencies: '@types/node': 17.0.45 + '@types/d3-array@3.2.2': {} + + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.6': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.2 + '@types/geojson': 7946.0.16 + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.7': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.3': {} + + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-shape@3.1.7': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time-format@4.0.3': {} + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.7 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.3 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.1.7 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + '@types/debug@4.1.12': dependencies: '@types/ms': 2.1.0 @@ -11575,6 +11967,8 @@ snapshots: '@types/file-saver@2.0.2': {} + '@types/geojson@7946.0.16': {} + '@types/graceful-fs@4.1.9': dependencies: '@types/node': 17.0.45 @@ -12008,7 +12402,7 @@ snapshots: postcss-preset-env: 7.5.0(postcss@8.5.6) rollup-plugin-visualizer: 5.9.0(rollup@3.29.5) systemjs: 6.15.1 - vite: 4.5.2(@types/node@17.0.45)(less@4.4.2)(lightningcss@1.22.1)(sass@1.54.0)(terser@5.44.1) + vite: 4.5.2(@types/node@17.0.45)(less@4.1.3)(lightningcss@1.22.1)(sass@1.54.0)(terser@5.44.1) transitivePeerDependencies: - '@types/node' - lightningcss @@ -12523,7 +12917,7 @@ snapshots: '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5) react-refresh: 0.14.2 - vite: 4.5.2(@types/node@17.0.45)(less@4.4.2)(lightningcss@1.22.1)(sass@1.54.0)(terser@5.44.1) + vite: 4.5.2(@types/node@17.0.45)(less@4.1.3)(lightningcss@1.22.1)(sass@1.54.0)(terser@5.44.1) transitivePeerDependencies: - supports-color @@ -13332,6 +13726,8 @@ snapshots: safe-buffer: 5.2.1 to-buffer: 1.2.2 + classcat@5.0.5: {} + classnames@2.5.1: {} clean-css@5.3.3: @@ -13681,8 +14077,44 @@ snapshots: d3-array@1.2.4: {} + d3-color@3.1.0: {} + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-ease@3.0.1: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + d3-polygon@1.0.6: {} + d3-selection@3.0.0: {} + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + d@1.0.2: dependencies: es5-ext: 0.10.64 @@ -17469,6 +17901,20 @@ snapshots: lodash: 4.17.21 react: 18.3.1 + reactflow@11.11.4(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@reactflow/background': 11.3.14(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@reactflow/controls': 11.2.14(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@reactflow/core': 11.11.4(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@reactflow/minimap': 11.7.14(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@reactflow/node-resizer': 2.2.14(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@reactflow/node-toolbar': 1.3.14(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - immer + read-pkg-up@7.0.1: dependencies: find-up: 4.1.0 @@ -18760,7 +19206,7 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - vite@4.5.2(@types/node@17.0.45)(less@4.4.2)(lightningcss@1.22.1)(sass@1.54.0)(terser@5.44.1): + vite@4.5.2(@types/node@17.0.45)(less@4.1.3)(lightningcss@1.22.1)(sass@1.54.0)(terser@5.44.1): dependencies: esbuild: 0.18.20 postcss: 8.5.6 @@ -18768,7 +19214,7 @@ snapshots: optionalDependencies: '@types/node': 17.0.45 fsevents: 2.3.3 - less: 4.4.2 + less: 4.1.3 lightningcss: 1.22.1 sass: 1.54.0 terser: 5.44.1 @@ -19003,3 +19449,10 @@ snapshots: zod: 3.25.76 zod@3.25.76: {} + + zustand@4.5.7(@types/react@18.3.26)(react@18.3.1): + dependencies: + use-sync-external-store: 1.6.0(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.26 + react: 18.3.1 diff --git a/src/pages/scenario/index.tsx b/src/pages/scenario/index.tsx index 32aadef8..62870ced 100644 --- a/src/pages/scenario/index.tsx +++ b/src/pages/scenario/index.tsx @@ -23,7 +23,7 @@ import { } from '@ant-design/icons'; import { request } from '@/utils/http'; import intl from 'react-intl-universal'; -import ScenarioModal from './flowgramModal'; +import ScenarioModal from './visualWorkflowModal'; import ScenarioLogModal from './logModal'; import dayjs from 'dayjs'; diff --git a/src/pages/scenario/nodeTypes.tsx b/src/pages/scenario/nodeTypes.tsx new file mode 100644 index 00000000..e20695b9 --- /dev/null +++ b/src/pages/scenario/nodeTypes.tsx @@ -0,0 +1,299 @@ +// Custom Node Type Definitions for Flowgram Workflow Editor +import React from 'react'; +import { Tag } from 'antd'; + +export interface NodeData { + label: string; + [key: string]: any; +} + +export interface WorkflowNode { + id: string; + type: string; + position: { x: number; y: number }; + data: NodeData; +} + +export interface WorkflowEdge { + id: string; + source: string; + target: string; +} + +export interface WorkflowGraph { + nodes: WorkflowNode[]; + edges: WorkflowEdge[]; +} + +// Trigger Node Types +export const TriggerNodeTypes = { + TIME: 'time', + WEBHOOK: 'webhook', + VARIABLE: 'variable', + TASK_STATUS: 'task_status', + SYSTEM_EVENT: 'system_event', +}; + +// Condition Node Types +export const ConditionNodeTypes = { + EQUALS: 'equals', + NOT_EQUALS: 'not_equals', + GREATER_THAN: 'greater_than', + LESS_THAN: 'less_than', + CONTAINS: 'contains', + NOT_CONTAINS: 'not_contains', +}; + +// Action Node Types +export const ActionNodeTypes = { + RUN_TASK: 'run_task', + SET_VARIABLE: 'set_variable', + EXECUTE_COMMAND: 'execute_command', + SEND_NOTIFICATION: 'send_notification', +}; + +// Control Flow Node Types +export const ControlFlowNodeTypes = { + DELAY: 'delay', + RETRY: 'retry', + CIRCUIT_BREAKER: 'circuit_breaker', + AND_GATE: 'and_gate', + OR_GATE: 'or_gate', +}; + +// Node Renderer Components +export const NodeRenderers = { + trigger: (node: WorkflowNode) => ( +
+
{node.data.label}
+ + {node.data.triggerType || 'trigger'} + +
+ ), + + condition: (node: WorkflowNode) => ( +
+
{node.data.label}
+
+ {node.data.field} {node.data.operator} {node.data.value} +
+
+ ), + + action: (node: WorkflowNode) => ( +
+
{node.data.label}
+ + {node.data.actionType || 'action'} + +
+ ), + + control: (node: WorkflowNode) => ( +
+
{node.data.label}
+
+ {node.data.controlType || 'control'} +
+
+ ), + + logic_gate: (node: WorkflowNode) => ( +
+
+ {node.data.gateType || 'AND'} +
+
+ ), +}; + +// Helper function to create a new node +export const createNode = ( + type: string, + position: { x: number; y: number }, + data: Partial, +): WorkflowNode => { + return { + id: `${type}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + type, + position, + data: { + label: data.label || type, + ...data, + }, + }; +}; + +// Helper function to create a new edge +export const createEdge = ( + source: string, + target: string, +): WorkflowEdge => { + return { + id: `edge-${source}-${target}`, + source, + target, + }; +}; + +// Node templates for quick creation +export const NodeTemplates = { + triggers: { + time: { + label: '时间触发', + triggerType: 'time', + config: { schedule: '0 0 * * *' }, + }, + webhook: { + label: 'Webhook触发', + triggerType: 'webhook', + config: {}, + }, + variable: { + label: '变量监听', + triggerType: 'variable', + config: { watchPath: '' }, + }, + task_status: { + label: '任务状态', + triggerType: 'task_status', + config: { cronId: null }, + }, + system_event: { + label: '系统事件', + triggerType: 'system_event', + config: { eventType: 'disk_space', threshold: 80 }, + }, + }, + conditions: { + equals: { + label: '等于判断', + operator: 'equals', + field: '', + value: '', + }, + greater_than: { + label: '大于判断', + operator: 'greater_than', + field: '', + value: 0, + }, + contains: { + label: '包含判断', + operator: 'contains', + field: '', + value: '', + }, + }, + actions: { + run_task: { + label: '运行任务', + actionType: 'run_task', + cronId: null, + }, + set_variable: { + label: '设置变量', + actionType: 'set_variable', + name: '', + value: '', + }, + execute_command: { + label: '执行命令', + actionType: 'execute_command', + command: '', + }, + send_notification: { + label: '发送通知', + actionType: 'send_notification', + message: '', + }, + }, + controls: { + delay: { + label: '延迟执行', + controlType: 'delay', + delaySeconds: 60, + }, + retry: { + label: '重试策略', + controlType: 'retry', + maxRetries: 3, + retryDelay: 5, + backoffMultiplier: 2, + }, + circuit_breaker: { + label: '熔断器', + controlType: 'circuit_breaker', + failureThreshold: 3, + }, + }, + logic_gates: { + and: { + label: 'AND', + gateType: 'AND', + }, + or: { + label: 'OR', + gateType: 'OR', + }, + }, +}; diff --git a/src/pages/scenario/visualWorkflowModal.tsx b/src/pages/scenario/visualWorkflowModal.tsx new file mode 100644 index 00000000..ab252575 --- /dev/null +++ b/src/pages/scenario/visualWorkflowModal.tsx @@ -0,0 +1,471 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { Modal, Form, Input, message, Button, Drawer, Select, InputNumber, Space } from 'antd'; +import { request } from '@/utils/http'; +import intl from 'react-intl-universal'; +import ReactFlow, { + Node, + Edge, + Controls, + Background, + applyNodeChanges, + applyEdgeChanges, + addEdge, + NodeChange, + EdgeChange, + Connection, + BackgroundVariant, +} from 'reactflow'; +import 'reactflow/dist/style.css'; +import { + NodeRenderers, + NodeTemplates, + createNode, + WorkflowGraph, +} from './nodeTypes'; +import { PlusOutlined } from '@ant-design/icons'; + +const { TextArea } = Input; +const { Option } = Select; + +interface ScenarioModalProps { + visible: boolean; + scenario: any; + onCancel: () => void; + onSuccess: () => void; +} + +const VisualWorkflowModal: React.FC = ({ + visible, + scenario, + onCancel, + onSuccess, +}) => { + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false); + const [nodes, setNodes] = useState([]); + const [edges, setEdges] = useState([]); + const [drawerVisible, setDrawerVisible] = useState(false); + const [selectedNode, setSelectedNode] = useState(null); + const [nodeConfigForm] = Form.useForm(); + + useEffect(() => { + if (visible) { + if (scenario) { + form.setFieldsValue({ + name: scenario.name, + description: scenario.description || '', + }); + + if (scenario.workflowGraph) { + setNodes(scenario.workflowGraph.nodes || []); + setEdges(scenario.workflowGraph.edges || []); + } else { + initializeDefaultWorkflow(); + } + } else { + form.resetFields(); + initializeDefaultWorkflow(); + } + } + }, [visible, scenario, form]); + + const initializeDefaultWorkflow = () => { + const triggerNode = createNode('trigger', { x: 250, y: 100 }, { + label: intl.get('时间触发'), + triggerType: 'time', + config: { schedule: '0 0 * * *' }, + }); + + setNodes([triggerNode]); + setEdges([]); + }; + + const onNodesChange = useCallback( + (changes: NodeChange[]) => setNodes((nds) => applyNodeChanges(changes, nds)), + [], + ); + + const onEdgesChange = useCallback( + (changes: EdgeChange[]) => setEdges((eds) => applyEdgeChanges(changes, eds)), + [], + ); + + const onConnect = useCallback( + (connection: Connection) => setEdges((eds) => addEdge(connection, eds)), + [], + ); + + const handleAddNode = (type: string, template: any) => { + const newNode = createNode(type, { x: Math.random() * 400 + 100, y: Math.random() * 300 + 100 }, template); + setNodes((nds) => [...nds, newNode]); + }; + + const handleNodeClick = (_event: React.MouseEvent, node: Node) => { + setSelectedNode(node); + nodeConfigForm.setFieldsValue(node.data); + setDrawerVisible(true); + }; + + const handleNodeConfigSave = async () => { + try { + const values = await nodeConfigForm.validateFields(); + setNodes((nds) => + nds.map((node) => + node.id === selectedNode?.id + ? { ...node, data: { ...node.data, ...values } } + : node + ) + ); + setDrawerVisible(false); + message.success(intl.get('配置已保存')); + } catch (error) { + console.error('节点配置验证失败:', error); + } + }; + + const handleSubmit = async () => { + try { + const values = await form.validateFields(); + setLoading(true); + + const workflowGraph: WorkflowGraph = { + nodes: nodes.map(node => ({ + id: node.id, + type: node.type || 'default', + position: node.position, + data: node.data, + })), + edges: edges.map(edge => ({ + id: edge.id, + source: edge.source, + target: edge.target, + })), + }; + + const endpoint = scenario ? '/api/scenarios' : '/api/scenarios'; + const method = scenario ? 'put' : 'post'; + const payload = { + ...values, + workflowGraph, + ...(scenario ? { id: scenario.id } : {}), + }; + + const { code } = await request[method](endpoint, payload); + if (code === 200) { + message.success( + scenario ? intl.get('更新成功') : intl.get('创建成功'), + ); + onSuccess(); + } + } catch (error) { + console.error('Failed to save scenario:', error); + } finally { + setLoading(false); + } + }; + + const nodeTypes = { + trigger: ({ data }: any) => NodeRenderers.trigger({ data, id: '', type: 'trigger', position: { x: 0, y: 0 } }), + condition: ({ data }: any) => NodeRenderers.condition({ data, id: '', type: 'condition', position: { x: 0, y: 0 } }), + action: ({ data }: any) => NodeRenderers.action({ data, id: '', type: 'action', position: { x: 0, y: 0 } }), + control: ({ data }: any) => NodeRenderers.control({ data, id: '', type: 'control', position: { x: 0, y: 0 } }), + logic_gate: ({ data }: any) => NodeRenderers.logic_gate({ data, id: '', type: 'logic_gate', position: { x: 0, y: 0 } }), + }; + + const renderNodeConfig = () => { + if (!selectedNode) return null; + + switch (selectedNode.type) { + case 'trigger': + return ( + <> + + + + + + + prev.triggerType !== curr.triggerType}> + {({ getFieldValue }) => { + const triggerType = getFieldValue('triggerType'); + if (triggerType === 'time') { + return ( + + + + ); + } else if (triggerType === 'variable') { + return ( + + + + ); + } else if (triggerType === 'system_event') { + return ( + <> + + + + + + + + ); + } + return null; + }} + + + ); + + case 'condition': + return ( + <> + + + + + + + + + + + + + + ); + + case 'action': + return ( + <> + + + + + + + prev.actionType !== curr.actionType}> + {({ getFieldValue }) => { + const actionType = getFieldValue('actionType'); + if (actionType === 'run_task') { + return ( + + + + ); + } else if (actionType === 'set_variable') { + return ( + <> + + + + + + + + ); + } else if (actionType === 'execute_command') { + return ( + +