mirror of
https://github.com/dataelement/bisheng.git
synced 2026-09-24 23:19:52 +08:00
feat: 041版本需求
This commit is contained in:
Generated
+87
-202
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "bisheng",
|
||||
"version": "0.4.0",
|
||||
"version": "0.4.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bisheng",
|
||||
"version": "0.4.0",
|
||||
"version": "0.4.1",
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^2.0.4",
|
||||
"@radix-ui/react-accordion": "^1.2.0",
|
||||
@@ -38,7 +38,7 @@
|
||||
"base64-js": "^1.5.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"cmdk": "^0.2.1",
|
||||
"date-fns": "^3.6.0",
|
||||
"dompurify": "^3.1.5",
|
||||
"i18next": "^23.12.1",
|
||||
@@ -3154,12 +3154,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/cmdk/-/cmdk-1.0.0.tgz",
|
||||
"integrity": "sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==",
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/cmdk/-/cmdk-0.2.1.tgz",
|
||||
"integrity": "sha512-U6//9lQ6JvT47+6OF6Gi8BvkxYQ8SCRRSKIJkthIMsFsLZRG0cKvTtuTaefyIKMQb8rvvXy0wGdpTNq/jPtm+g==",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-dialog": "1.0.5",
|
||||
"@radix-ui/react-primitive": "1.0.3"
|
||||
"@radix-ui/react-dialog": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0",
|
||||
@@ -3167,332 +3166,218 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/primitive": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.0.1.tgz",
|
||||
"integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.0.0.tgz",
|
||||
"integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-compose-refs": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz",
|
||||
"integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz",
|
||||
"integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-context": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.0.1.tgz",
|
||||
"integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-context/-/react-context-1.0.0.tgz",
|
||||
"integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-dialog": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz",
|
||||
"integrity": "sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-dialog/-/react-dialog-1.0.0.tgz",
|
||||
"integrity": "sha512-Yn9YU+QlHYLWwV1XfKiqnGVpWYWk6MeBVM6x/bcoyPvxgjQGoeT35482viLPctTMWoMw0PoHgqfSox7Ig+957Q==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-dismissable-layer": "1.0.5",
|
||||
"@radix-ui/react-focus-guards": "1.0.1",
|
||||
"@radix-ui/react-focus-scope": "1.0.4",
|
||||
"@radix-ui/react-id": "1.0.1",
|
||||
"@radix-ui/react-portal": "1.0.4",
|
||||
"@radix-ui/react-presence": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-slot": "1.0.2",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.1",
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-context": "1.0.0",
|
||||
"@radix-ui/react-dismissable-layer": "1.0.0",
|
||||
"@radix-ui/react-focus-guards": "1.0.0",
|
||||
"@radix-ui/react-focus-scope": "1.0.0",
|
||||
"@radix-ui/react-id": "1.0.0",
|
||||
"@radix-ui/react-portal": "1.0.0",
|
||||
"@radix-ui/react-presence": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.0",
|
||||
"@radix-ui/react-slot": "1.0.0",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.0",
|
||||
"aria-hidden": "^1.1.1",
|
||||
"react-remove-scroll": "2.5.5"
|
||||
"react-remove-scroll": "2.5.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-dismissable-layer": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz",
|
||||
"integrity": "sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.0.tgz",
|
||||
"integrity": "sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1",
|
||||
"@radix-ui/react-use-escape-keydown": "1.0.3"
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.0",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0",
|
||||
"@radix-ui/react-use-escape-keydown": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-focus-guards": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz",
|
||||
"integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz",
|
||||
"integrity": "sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-focus-scope": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz",
|
||||
"integrity": "sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.0.tgz",
|
||||
"integrity": "sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1"
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.0",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-id": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-id/-/react-id-1.0.1.tgz",
|
||||
"integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-id/-/react-id-1.0.0.tgz",
|
||||
"integrity": "sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.1"
|
||||
"@radix-ui/react-use-layout-effect": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-portal": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-portal/-/react-portal-1.0.4.tgz",
|
||||
"integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-portal/-/react-portal-1.0.0.tgz",
|
||||
"integrity": "sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-primitive": "1.0.3"
|
||||
"@radix-ui/react-primitive": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-presence": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-1.0.1.tgz",
|
||||
"integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-1.0.0.tgz",
|
||||
"integrity": "sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.1"
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-primitive": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz",
|
||||
"integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-1.0.0.tgz",
|
||||
"integrity": "sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "1.0.2"
|
||||
"@radix-ui/react-slot": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
||||
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.0.0.tgz",
|
||||
"integrity": "sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.1"
|
||||
"@radix-ui/react-compose-refs": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-use-callback-ref": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz",
|
||||
"integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz",
|
||||
"integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-use-controllable-state": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz",
|
||||
"integrity": "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.0.tgz",
|
||||
"integrity": "sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1"
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-use-escape-keydown": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz",
|
||||
"integrity": "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.0.tgz",
|
||||
"integrity": "sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1"
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-use-layout-effect": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz",
|
||||
"integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz",
|
||||
"integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/react-remove-scroll": {
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmmirror.com/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz",
|
||||
"integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==",
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmmirror.com/react-remove-scroll/-/react-remove-scroll-2.5.4.tgz",
|
||||
"integrity": "sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==",
|
||||
"dependencies": {
|
||||
"react-remove-scroll-bar": "^2.3.3",
|
||||
"react-style-singleton": "^2.2.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bisheng",
|
||||
"version": "0.4.0",
|
||||
"version": "0.4.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^2.0.4",
|
||||
@@ -33,7 +33,7 @@
|
||||
"base64-js": "^1.5.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"cmdk": "^0.2.1",
|
||||
"date-fns": "^3.6.0",
|
||||
"dompurify": "^3.1.5",
|
||||
"i18next": "^23.12.1",
|
||||
|
||||
@@ -72,7 +72,7 @@ const Col = (props: {
|
||||
onClick: (o: Option, isLeaf: boolean) => void
|
||||
}) => {
|
||||
const { options, ...opros } = props
|
||||
return <div className="w-36 border-l first:border-none">
|
||||
return <div className="w-36 border-l first:border-none max-h-80 overflow-y-auto">
|
||||
{
|
||||
options.map(option => <Item {...opros} option={option} key={option.value} />)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export default function InputComponent({
|
||||
disabled,
|
||||
password,
|
||||
type = 'input',
|
||||
maxLength = 100,
|
||||
maxLength = 150,
|
||||
editNode = false,
|
||||
}: InputComponentType) {
|
||||
const [myValue, setMyValue] = useState(value ?? "");
|
||||
|
||||
@@ -92,4 +92,9 @@ export const getAssistantToolsApi = async (type: 'all' | 'default' | 'custom'):
|
||||
// 修改内置工具配置
|
||||
export const updateAssistantToolApi = async (tool_id, extra) => {
|
||||
return await axios.post(`/api/v1/assistant/tool/config`, { tool_id, extra })
|
||||
}
|
||||
|
||||
// 获取自动优化任务taskid
|
||||
export const getAssistantOptimizeTaskApi = async (assistant_id, prompt) => {
|
||||
return await axios.post(`/api/v1/assistant/auto/task`, { assistant_id, prompt })
|
||||
}
|
||||
@@ -457,8 +457,8 @@ export const getChatsApi = (page) => {
|
||||
if (isJsonSerializable(message)) {
|
||||
const json = JSON.parse(message);
|
||||
if (Array.isArray(json)) return message
|
||||
const chatKey = Object.keys(json)[0]
|
||||
return json[chatKey] || message
|
||||
const chatKey = el.flow_type === 'workflow' ? 'msg' : Object.keys(json)[0]
|
||||
return json[chatKey] || ''
|
||||
}
|
||||
return message;
|
||||
}())
|
||||
|
||||
@@ -56,6 +56,7 @@ export const saveGroupApi = async (data: any): Promise<any> => {
|
||||
adminUserId: admin_user_id,
|
||||
groupName: group_name,
|
||||
assistant,
|
||||
workFlows,
|
||||
skill } = data;
|
||||
// const {resourceId, groupId, resourceLimit} = assistant
|
||||
|
||||
@@ -66,7 +67,8 @@ export const saveGroupApi = async (data: any): Promise<any> => {
|
||||
admin_user_id,
|
||||
group_name,
|
||||
assistant,
|
||||
skill
|
||||
skill,
|
||||
workFlows
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -57,6 +57,19 @@ export const onlineWorkflow = async (flow, status = ''): Promise<any> => {
|
||||
return await axios.patch(`/api/v1/workflow/update/${flow.id}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 单节点运行
|
||||
*
|
||||
*/
|
||||
export const runWorkflowNodeApi = async (node_input, data): Promise<any> => {
|
||||
return await axios.post(`/api/v1/workflow/run_once`, {
|
||||
node_input, node_data: {
|
||||
id: data.id,
|
||||
data,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 工作流节点模板
|
||||
@@ -385,7 +398,7 @@ const workflowTemplate = [
|
||||
],
|
||||
"step": 1,
|
||||
"value": {
|
||||
"flag": false,
|
||||
"flag": true,
|
||||
"value": 50
|
||||
},
|
||||
"help": "是否携带历史对话记录。"
|
||||
@@ -442,7 +455,7 @@ const workflowTemplate = [
|
||||
"label": "将输出结果展示在会话中",
|
||||
"type": "switch",
|
||||
"help": "一般在问答等场景开启,文档审核、报告生成等场景可关闭。",
|
||||
"value": false
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"key": "output",
|
||||
@@ -535,6 +548,7 @@ const workflowTemplate = [
|
||||
"label": "检索范围",
|
||||
"global": "self=system_prompt,user_prompt",
|
||||
"type": "knowledge_select_multi",
|
||||
"placeholder": "请选择知识库",
|
||||
"value": {
|
||||
"type": "knowledge",
|
||||
"value": []
|
||||
|
||||
@@ -41,7 +41,7 @@ customAxios.interceptors.response.use(function (response) {
|
||||
const infoStr = localStorage.getItem(UUR_INFO)
|
||||
localStorage.removeItem(UUR_INFO)
|
||||
infoStr && location.reload()
|
||||
return Promise.reject(error);
|
||||
return Promise.reject('登录过期,请重新登录');
|
||||
}
|
||||
// app 弹窗
|
||||
toast({
|
||||
|
||||
@@ -107,7 +107,7 @@ ${t('build.exampleTwo')}
|
||||
return '';
|
||||
case 'desc':
|
||||
if (AppType.ASSISTANT === appType && value.length < 20) return '为了更好的助手效果,描述需要大于20 个字';
|
||||
if (AppType.FLOW === appType && value.length > 200) return '工作流描述不可超过 200 字';
|
||||
// if (AppType.FLOW === appType && value.length > 200) return '工作流描述不可超过 200 字';
|
||||
return '';
|
||||
default:
|
||||
return '';
|
||||
@@ -223,14 +223,13 @@ ${t('build.exampleTwo')}
|
||||
<div className="mb-6">
|
||||
<label htmlFor="desc" className="bisheng-label">
|
||||
{appType === AppType.ASSISTANT ? '你希望助手的角色是什么,具体完成什么任务?' : '描述'}
|
||||
<span className="bisheng-tip">*</span>
|
||||
</label>
|
||||
<Textarea
|
||||
id="desc"
|
||||
name="desc"
|
||||
placeholder={appType === AppType.ASSISTANT ? t('build.forExample') : '输入工作流描述'}
|
||||
maxLength={appType === AppType.ASSISTANT ? 1000 : undefined}
|
||||
className="mt-3 min-h-32"
|
||||
className="mt-3 h-12 min-h-12 pt-3"
|
||||
value={formData.desc}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
@@ -238,7 +237,7 @@ ${t('build.exampleTwo')}
|
||||
</div>
|
||||
</div>
|
||||
{/* 工作流安全审查 */}
|
||||
{false && appConfig.isPro && <Accordion type="multiple" className="w-full">
|
||||
{appConfig.isPro && <Accordion type="multiple" className="w-full">
|
||||
<AssistantSetting id={'xxx'} type={3} />
|
||||
</Accordion>}
|
||||
<DialogFooter>
|
||||
@@ -247,7 +246,7 @@ ${t('build.exampleTwo')}
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button disabled={!formData.name || !formData.desc || loading} type="submit" className="px-11" onClick={handleSubmit}>
|
||||
<Button disabled={!formData.name || loading} type="submit" className="px-11" onClick={handleSubmit}>
|
||||
{loading && <LoadIcon className="mr-2" />}
|
||||
{t(isEditMode ? '保存' : '创建')}
|
||||
</Button>
|
||||
|
||||
@@ -67,7 +67,6 @@ export default function Templates() {
|
||||
desc: `是否确认删除该${labelName}模板?`,
|
||||
okTxt: t('delete'),
|
||||
onOk(next) {
|
||||
// TODO 三类模板删除
|
||||
captureAndAlertRequestErrorHoc(deleteTempApi(id).then((res) => {
|
||||
onChange(res)
|
||||
setTemps(temps.filter((temp, i) => index !== i));
|
||||
|
||||
@@ -82,7 +82,7 @@ export default function apps() {
|
||||
}))
|
||||
} else if (data.flow_type === 5) {
|
||||
return captureAndAlertRequestErrorHoc(changeAssistantStatusApi(data.id, checked ? 2 : 1)).then(res => {
|
||||
if (res) {
|
||||
if (res === null) {
|
||||
refreshData((item) => item.id === data.id, { status: checked ? 2 : 1 })
|
||||
}
|
||||
return res
|
||||
@@ -216,7 +216,13 @@ export default function apps() {
|
||||
user={item.user_name}
|
||||
currentUser={user}
|
||||
onClick={() => handleSetting(item)}
|
||||
onSwitchClick={() => !item.write && item.status !== 2 && message({ title: t('prompt'), description: t('skills.contactAdmin'), variant: 'warning' })}
|
||||
onSwitchClick={() => {
|
||||
!item.write && item.status !== 2 && message({
|
||||
title: t('prompt'),
|
||||
description: `您没有权限上线此${typeCnNames[item.flow_type]},请联系管理员上线。`,
|
||||
variant: 'warning'
|
||||
})
|
||||
}}
|
||||
onAddTemp={toggleTempModal}
|
||||
onCheckedChange={handleCheckedChange}
|
||||
onDelete={handleDelete}
|
||||
@@ -237,7 +243,7 @@ export default function apps() {
|
||||
</LabelShow>
|
||||
}
|
||||
footer={
|
||||
<Badge className={`absolute py-0 px-1 right-0 bottom-0 rounded-none rounded-br-md ${item.flow_type === 1 && 'bg-gray-950'} ${item.flow_type === 5 && 'bg-blue-500'}`}>
|
||||
<Badge className={`absolute py-0 px-1 right-0 bottom-0 rounded-none rounded-br-md ${item.flow_type === 1 && 'bg-gray-950'} ${item.flow_type === 5 && 'bg-[#fdb136]'}`}>
|
||||
{typeCnNames[item.flow_type]}
|
||||
</Badge>
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Button } from "@/components/bs-ui/button";
|
||||
import { DialogClose, DialogContent, DialogFooter } from "@/components/bs-ui/dialog";
|
||||
import { Textarea } from "@/components/bs-ui/input";
|
||||
import { useToast } from "@/components/bs-ui/toast/use-toast";
|
||||
import { getAssistantOptimizeTaskApi } from "@/controllers/API/assistant";
|
||||
import { useAssistantStore } from "@/store/assistantStore";
|
||||
import { AssistantTool } from "@/types/assistant";
|
||||
import { FlowType } from "@/types/flow";
|
||||
@@ -25,9 +26,10 @@ export default function AutoPromptDialog({ onOpenChange }) {
|
||||
const { id } = useParams()
|
||||
const { assistantState, dispatchAssistant } = useAssistantStore()
|
||||
|
||||
const init = () => {
|
||||
const init = async () => {
|
||||
const prompt = areaRef.current?.value || assistantState.prompt
|
||||
const apiUrl = `${__APP_ENV__.BASE_URL}/api/v1/assistant/auto?assistant_id=${id}&prompt=${encodeURIComponent(prompt)}`;
|
||||
const res = await getAssistantOptimizeTaskApi(id, prompt)
|
||||
const apiUrl = `${__APP_ENV__.BASE_URL}/api/v1/assistant/auto?task_id=${res.task_id}`;
|
||||
const eventSource = new EventSource(apiUrl);
|
||||
if (areaRef.current) areaRef.current.value = ''
|
||||
let queue = LoadType.Prompt
|
||||
@@ -77,12 +79,6 @@ export default function AutoPromptDialog({ onOpenChange }) {
|
||||
variant: 'error',
|
||||
description: ''
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: '提示词过长,请缩减内容',
|
||||
variant: 'error',
|
||||
description: ''
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -178,7 +174,7 @@ export default function AutoPromptDialog({ onOpenChange }) {
|
||||
<div className="w-[50%] relative pr-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-lg font-semibold leading-none tracking-tight flex">{t('build.portraitOptimization')}{LoadType.Prompt === loading && <LoadIcon className="ml-2 text-gray-600" />}</span>
|
||||
<Button variant="link" size="sm" onClick={handleReload} disabled={!!loading} ><RefreshCw className="mr-2" />{t('build.retry')}</Button>
|
||||
<Button variant="link" size="sm" onClick={handleReload} disabled={!!loading} ><RefreshCw size={14} className="mr-2" />{t('build.retry')}</Button>
|
||||
</div>
|
||||
<div className="group flex justify-end mt-2 h-[600px] relative">
|
||||
<Textarea ref={areaRef} className="h-full"
|
||||
|
||||
@@ -3,6 +3,7 @@ import { AssistantIcon } from "@/components/bs-icons";
|
||||
import { Button } from "@/components/bs-ui/button";
|
||||
import { Dialog, DialogTrigger } from "@/components/bs-ui/dialog";
|
||||
import { useAssistantStore } from "@/store/assistantStore";
|
||||
import { OnlineState } from "@/types/flow";
|
||||
import { ChevronLeft, SquarePen } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -15,6 +16,7 @@ export default function Header({ onSave, onLine, onTabChange }) {
|
||||
const navigate = useNavigate()
|
||||
|
||||
const { assistantState, dispatchAssistant } = useAssistantStore()
|
||||
console.log('assistantState :>> ', assistantState);
|
||||
{/* 编辑助手 */ }
|
||||
const [editShow, setEditShow] = useState(false);
|
||||
|
||||
@@ -67,7 +69,7 @@ export default function Header({ onSave, onLine, onTabChange }) {
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<Button variant="outline" className="px-10" type="button" onClick={onSave}>{t('build.save')}</Button>
|
||||
<Button type="submit" className="px-10" onClick={onLine}>{t('build.online')}</Button>
|
||||
<Button type="submit" className="px-10" onClick={() => onLine(assistantState.status === OnlineState.OffLine)}>{assistantState.status === OnlineState.OnLine ? '下线' : t('build.online')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
};
|
||||
|
||||
@@ -65,20 +65,25 @@ export default function editAssistant() {
|
||||
}
|
||||
|
||||
// 上线助手
|
||||
const handleOnline = async () => {
|
||||
const handleOnline = async (online) => {
|
||||
if (!handleCheck()) return
|
||||
await handleSave()
|
||||
await captureAndAlertRequestErrorHoc(changeAssistantStatusApi(assistantState.id, 2)).then(res => {
|
||||
if (res === false) return
|
||||
message({
|
||||
title: t('prompt'),
|
||||
variant: 'success',
|
||||
description: t('skills.onlineSuccessful')
|
||||
if (online) {
|
||||
await handleSave()
|
||||
await captureAndAlertRequestErrorHoc(changeAssistantStatusApi(assistantState.id, 2)).then(res => {
|
||||
if (res === false) return
|
||||
message({
|
||||
title: t('prompt'),
|
||||
variant: 'success',
|
||||
description: t('skills.onlineSuccessful')
|
||||
})
|
||||
})
|
||||
})
|
||||
setTimeout(() => {
|
||||
navigate('/build')
|
||||
}, 1200);
|
||||
setTimeout(() => {
|
||||
navigate('/build')
|
||||
}, 1200);
|
||||
} else {
|
||||
// TODO 下线接口
|
||||
// 更新 assistantState.status = 1
|
||||
}
|
||||
}
|
||||
|
||||
// 校验助手数据
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import ChatInput from "./ChatInput";
|
||||
import ChatMessages from "./ChatMessages";
|
||||
import { LoadingIcon } from "@/components/bs-icons/loading";
|
||||
|
||||
export default function Chat({
|
||||
stop = false,
|
||||
@@ -17,11 +18,13 @@ export default function Chat({
|
||||
}) {
|
||||
const [loading, setLoading] = useState(autoRun)
|
||||
|
||||
return <div className="relative h-full bs-chat-bg" style={{ backgroundImage: `url(${__APP_ENV__.BASE_URL}/points.png)` }}>
|
||||
<ChatMessages logo={logo} useName={useName} guideWord={guideWord} loadMore={loadMore}></ChatMessages>
|
||||
<ChatInput autoRun={autoRun} clear={clear} form={form} wsUrl={wsUrl} inputForm={inputForm} onBeforSend={onBeforSend} onLoad={() => setLoading(false)} ></ChatInput>
|
||||
return <div className="h-full bs-chat-bg px-4" style={{ backgroundImage: `url(${__APP_ENV__.BASE_URL}/points.png)` }}>
|
||||
<div className="relative h-full">
|
||||
<ChatMessages logo={logo} useName={useName} guideWord={guideWord} loadMore={loadMore}></ChatMessages>
|
||||
<ChatInput autoRun={autoRun} clear={clear} form={form} wsUrl={wsUrl} inputForm={inputForm} onBeforSend={onBeforSend} onLoad={() => setLoading(false)} ></ChatInput>
|
||||
</div>
|
||||
{loading && <div className="absolute top-0 left-0 w-full h-full flex items-center justify-center bg-primary/5 z-10">
|
||||
<img className="w-[138px]" src="/application-start-logo.png" alt="" />
|
||||
<LoadingIcon className="size-24" />
|
||||
</div>}
|
||||
</div>
|
||||
};
|
||||
|
||||
@@ -55,6 +55,7 @@ export default function ChatPane({ autoRun = false, chatId, flow, wsUrl = '' }:
|
||||
|
||||
return {
|
||||
action,
|
||||
flow_id: flow.id,
|
||||
data: flow
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export const ChatTest = forwardRef((props, ref) => {
|
||||
><X /></Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`h-[calc(100vh-28px)] overflow-y-auto px-4 ${small ? 'hidden' : ''}`}>
|
||||
<div className={`h-[calc(100vh-28px)] relative overflow-y-auto ${small ? 'hidden' : ''}`}>
|
||||
<ChatPane autoRun chatId={chatId} flow={flow} wsUrl={`${host}${__APP_ENV__.BASE_URL}/api/v1/workflow/chat/${flow?.id}`} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Button } from "@/components/bs-ui/button";
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/bs-ui/select";
|
||||
import { useToast } from "@/components/bs-ui/toast/use-toast";
|
||||
import InputComponent from "@/components/inputComponent";
|
||||
import InputFileComponent from "@/components/inputFileComponent";
|
||||
import { WorkflowNodeParam } from "@/types/flow";
|
||||
@@ -28,16 +29,28 @@ const InputForm = ({ data, onSubmit }: { data: WorkflowNodeParam, onSubmit: (dat
|
||||
formDataRef.current[item.key].fileName = fileName
|
||||
}
|
||||
|
||||
const { message } = useToast()
|
||||
const submit = () => {
|
||||
const valuesObject = {}
|
||||
let stringObject = ""
|
||||
const errors = []
|
||||
|
||||
Object.keys(formDataRef.current).forEach((key: string) => {
|
||||
const item = formDataRef.current[key]
|
||||
valuesObject[key] = item.value
|
||||
stringObject += `${item.label}:${item.type === FormItemType.File ? item.fileName : item.value}\n`
|
||||
const fieldData = formDataRef.current[key]
|
||||
const required = data.value.find(item => item.key === key).required
|
||||
if (required && !fieldData.value) {
|
||||
errors.push(`${fieldData.label} 为必填项,不能为空。`)
|
||||
}
|
||||
valuesObject[key] = fieldData.value
|
||||
stringObject += `${fieldData.label}:${fieldData.type === FormItemType.File ? fieldData.fileName : fieldData.value}\n`
|
||||
})
|
||||
|
||||
if (errors.length) {
|
||||
return message({
|
||||
description: errors,
|
||||
variant: 'warning'
|
||||
})
|
||||
}
|
||||
onSubmit([valuesObject, stringObject])
|
||||
}
|
||||
|
||||
@@ -45,10 +58,11 @@ const InputForm = ({ data, onSubmit }: { data: WorkflowNodeParam, onSubmit: (dat
|
||||
<div className="max-h-[520px] overflow-y-auto">
|
||||
{
|
||||
data.value.map((item, i) => (
|
||||
<div key={item.id} className="w-full text-sm">
|
||||
<div key={item.id} className="w-full text-sm bisheng-label">
|
||||
{item.required && <span className="text-red-500">*</span>}
|
||||
{item.value}
|
||||
{/* <span className="text-status-red">{item.required ? " *" : ""}</span> */}
|
||||
<div className="mt-2">
|
||||
<div className="mb-2">
|
||||
{(() => {
|
||||
switch (item.type) {
|
||||
case FormItemType.Text:
|
||||
|
||||
@@ -170,7 +170,6 @@ export const useMessageStore = create<State & Actions>((set, get) => ({
|
||||
})
|
||||
},
|
||||
async loadHistoryMsg(flowid, chatId, { lastMsg }) {
|
||||
console.log('1234 :>> ', 1234);
|
||||
const res = await getChatHistory(flowid, chatId, 30, 0)
|
||||
const msgs = handleHistoryMsg(res)
|
||||
const hisMessages = msgs.reverse()
|
||||
|
||||
@@ -2,21 +2,70 @@ import { Button } from "@/components/bs-ui/button";
|
||||
import { Card, CardContent } from "@/components/bs-ui/card";
|
||||
import { BaseEdge, EdgeLabelRenderer, getBezierPath, Position } from "@xyflow/react";
|
||||
import { Plus } from "lucide-react";
|
||||
import { useMemo } from "react";
|
||||
import Sidebar from "./Sidebar";
|
||||
export default function CustomEdge({ id, sourceX, sourceY, targetX, targetY, onOptionSelect, onButtonClick, isDropdownOpen }) {
|
||||
const [edgePath, labelX, labelY] = getBezierPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition: Position.Right,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition: Position.Left,
|
||||
curvature: 0.4
|
||||
});
|
||||
|
||||
const handleOptionClick = (option) => {
|
||||
onOptionSelect({ label: option, position: { x: labelX, y: labelY } });
|
||||
function getUpperArcBezierPath(sourceX, sourceY, targetX, targetY) {
|
||||
const dx = Math.abs(targetX - sourceX);
|
||||
const dy = Math.abs(targetY - sourceY);
|
||||
|
||||
// 控制点的偏移量
|
||||
const offsetX = dx / 2.8; // 水平控制点偏移
|
||||
const offsetY = Math.max(dy, 180); // 垂直控制点偏移,确保曲线更弯曲
|
||||
|
||||
// 控制点的坐标(从上方绕弧线)
|
||||
const controlX1 = sourceX + offsetX;
|
||||
const controlY1 = sourceY - offsetY; // 控制点向上偏移
|
||||
const controlX2 = targetX - offsetX;
|
||||
const controlY2 = targetY - offsetY;
|
||||
|
||||
const path = `M${sourceX},${sourceY} C${controlX1},${controlY1} ${controlX2},${controlY2} ${targetX},${targetY}`;
|
||||
const centerX =
|
||||
0.125 * sourceX +
|
||||
0.375 * controlX1 +
|
||||
0.375 * controlX2 +
|
||||
0.125 * targetX;
|
||||
const centerY =
|
||||
0.125 * sourceY +
|
||||
0.375 * controlY1 +
|
||||
0.375 * controlY2 +
|
||||
0.125 * targetY;
|
||||
return [path, centerX, centerY];
|
||||
}
|
||||
|
||||
export default function CustomEdge({
|
||||
id,
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
selected,
|
||||
onOptionSelect,
|
||||
onButtonClick,
|
||||
isDropdownOpen,
|
||||
}) {
|
||||
|
||||
const [edgePath, labelX, labelY] = useMemo(() => {
|
||||
return targetX > sourceX ? getBezierPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition: Position.Right,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition: Position.Left,
|
||||
curvature: 0.4,
|
||||
}) : getUpperArcBezierPath(sourceX, sourceY, targetX, targetY)
|
||||
|
||||
}, [sourceX, sourceY, targetX, targetY])
|
||||
|
||||
const handleOptionClick = (flownodedata) => {
|
||||
onOptionSelect({
|
||||
node: flownodedata,
|
||||
edgeId: id,
|
||||
position: { x: labelX, y: labelY }
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* arrow */}
|
||||
@@ -39,28 +88,42 @@ export default function CustomEdge({ id, sourceX, sourceY, targetX, targetY, onO
|
||||
<BaseEdge
|
||||
id={id}
|
||||
path={edgePath}
|
||||
style={{ stroke: '#024de3', strokeWidth: 1 }}
|
||||
style={{ stroke: '#024de3', strokeWidth: selected ? 2 : 1, strokeDasharray: selected && 0 }}
|
||||
markerEnd="url(#arrow)"
|
||||
/>
|
||||
{/* 在连线中间添加一个加号按钮 */}
|
||||
{/* <EdgeLabelRenderer>
|
||||
{selected && <EdgeLabelRenderer>
|
||||
<div
|
||||
className="absolute"
|
||||
style={{
|
||||
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
||||
zIndex: 1000
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<Button size="icon" className="rounded-full w-6 h-6 pointer-events-auto" onClick={() => onButtonClick(id)}>
|
||||
<Button
|
||||
size="icon"
|
||||
className="rounded-full w-5 h-5 pointer-events-auto"
|
||||
onClick={() => onButtonClick(id)}
|
||||
>
|
||||
<Plus size={18} className="text-[#fff]" />
|
||||
</Button>
|
||||
{isDropdownOpen && <Card className="absolute top-10 translate-x-[-50%] bg-transparent hover:shadow-none" style={{ zIndex: 1001 }}>
|
||||
<CardContent className="min-w-56 pointer-events-auto px-0">
|
||||
<Sidebar dropdown onClick={(key) => handleOptionClick(key)}></Sidebar>
|
||||
</CardContent>
|
||||
</Card>}
|
||||
{isDropdownOpen && (
|
||||
<Card
|
||||
className="absolute top-8 translate-x-[-50%] bg-transparent hover:shadow-none hover:border-transparent"
|
||||
style={{ zIndex: 1001 }}
|
||||
>
|
||||
<CardContent className="min-w-56 pointer-events-auto px-0">
|
||||
<Sidebar
|
||||
dropdown
|
||||
disabledNodes={['end']}
|
||||
onClick={(flownodedata) => handleOptionClick(flownodedata)}
|
||||
></Sidebar>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</EdgeLabelRenderer> */}
|
||||
</EdgeLabelRenderer>
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function EditText({ type = 'input', reDefaultValue = false, child
|
||||
})
|
||||
}
|
||||
setEdit(false)
|
||||
if (reDefaultValue && !value) {
|
||||
if (reDefaultValue && !value.trim()) {
|
||||
// Restore default if empty
|
||||
setValue(defaultValue)
|
||||
return onChange(defaultValue)
|
||||
|
||||
@@ -22,9 +22,9 @@ export default function NodeToolbarComponent({ nodeId, type, onRun }) {
|
||||
|
||||
return <div className="rounded-xl shadow-sm p-1 bg-gradient-to-r from-gray-50 to-[#fff] border">
|
||||
{
|
||||
// ["agent", "tool", "llm", "rag", "qa_retriever", "code"].includes(type) && <Tip content="运行此节点" side="top">
|
||||
// <Button size="icon" variant="ghost" className="size-8" onClick={onRun}><Play size={16} /></Button>
|
||||
// </Tip>
|
||||
["agent", "tool", "llm", "rag", "qa_retriever", "code"].includes(type) && <Tip content="运行此节点" side="top">
|
||||
<Button size="icon" variant="ghost" className="size-8" onClick={onRun}><Play size={16} /></Button>
|
||||
</Tip>
|
||||
}
|
||||
<Tip content="复制" side="top">
|
||||
<Button
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { WorkflowNodeParam } from "@/types/flow";
|
||||
import { WorkflowNode, WorkflowNodeParam } from "@/types/flow";
|
||||
import CodeInputItem from "./component/CodeInputItem";
|
||||
import CodeOutputItem from "./component/CodeOutputItem";
|
||||
import CodePythonItem from "./component/CodePythonItem";
|
||||
@@ -23,9 +23,10 @@ import VarTextareaItem from "./component/VarTextareaItem";
|
||||
import VarTextareaUploadItem from "./component/VarTextareaUploadItem";
|
||||
|
||||
// 节点表单项
|
||||
export default function Parameter({ nodeId, item, onOutPutChange, onStatusChange, onVarEvent }
|
||||
export default function Parameter({ node, nodeId, item, onOutPutChange, onStatusChange, onVarEvent }
|
||||
: {
|
||||
nodeId: string,
|
||||
node: WorkflowNode,
|
||||
item: WorkflowNodeParam,
|
||||
onOutPutChange: (key: string, value: any) => void
|
||||
onStatusChange: (key: string, obj: any) => void
|
||||
@@ -65,7 +66,7 @@ export default function Parameter({ nodeId, item, onOutPutChange, onStatusChange
|
||||
case 'var_textarea_file':
|
||||
return <VarTextareaUploadItem nodeId={nodeId} data={item} onChange={handleOnNewValue} onValidate={bindValidate} onVarEvent={bindVarValidate} />
|
||||
case 'output_form':
|
||||
return <OutputItem nodeId={nodeId} data={item} onChange={handleOnNewValue} onValidate={bindValidate} />
|
||||
return <OutputItem nodeId={nodeId} node={node} data={item} onChange={handleOnNewValue} onValidate={bindValidate} />
|
||||
case 'bisheng_model':
|
||||
return <ModelItem data={item} onChange={handleOnNewValue} onValidate={bindValidate} />
|
||||
case 'agent_model':
|
||||
@@ -77,9 +78,20 @@ export default function Parameter({ nodeId, item, onOutPutChange, onStatusChange
|
||||
case 'switch':
|
||||
return <SwitchItem data={item} onChange={handleOnNewValue} />;
|
||||
case 'var_select':
|
||||
return <VarSelectSingleItem nodeId={nodeId} data={item} onChange={handleOnNewValue} onValidate={bindValidate} />;
|
||||
return <VarSelectSingleItem
|
||||
nodeId={nodeId}
|
||||
data={item}
|
||||
onChange={handleOnNewValue}
|
||||
onValidate={bindValidate}
|
||||
onVarEvent={bindVarValidate} />;
|
||||
case 'user_question':
|
||||
return <VarSelectItem nodeId={nodeId} data={item} onChange={handleOnNewValue} onOutPutChange={onOutPutChange} onValidate={bindValidate} />;
|
||||
return <VarSelectItem
|
||||
nodeId={nodeId}
|
||||
data={item}
|
||||
onChange={handleOnNewValue}
|
||||
onOutPutChange={onOutPutChange}
|
||||
onValidate={bindValidate}
|
||||
onVarEvent={bindVarValidate} />;
|
||||
case 'knowledge_select_multi':
|
||||
return <KnowledgeSelectItem data={item} onChange={handleOnNewValue} onValidate={bindValidate} />;
|
||||
case 'qa_select_multi':
|
||||
@@ -95,7 +107,7 @@ export default function Parameter({ nodeId, item, onOutPutChange, onStatusChange
|
||||
case 'add_tool':
|
||||
return <ToolItem data={item} onChange={handleOnNewValue} />;
|
||||
case 'condition':
|
||||
return <ConditionItem nodeId={nodeId} data={item} onChange={handleOnNewValue} onValidate={bindValidate} />;
|
||||
return <ConditionItem nodeId={nodeId} node={node} data={item} onChange={handleOnNewValue} onValidate={bindValidate} />;
|
||||
case 'report':
|
||||
return <ReportItem nodeId={nodeId} data={item} onChange={handleOnNewValue} onValidate={bindValidate} />;
|
||||
case 'sql_config':
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { WorkflowNode } from "@/types/flow";
|
||||
import Parameter from "./Parameter";
|
||||
|
||||
export default function ParameterGroup({ nodeId, cate, tab, onOutPutChange, onStatusChange, onVarEvent }
|
||||
export default function ParameterGroup({ nodeId, node, cate, tab, onOutPutChange, onStatusChange, onVarEvent }
|
||||
: {
|
||||
nodeId: string,
|
||||
node: WorkflowNode,
|
||||
cate: WorkflowNode['group_params'][number],
|
||||
tab: string,
|
||||
onOutPutChange: (key: string, value: any) => void
|
||||
@@ -17,6 +18,7 @@ export default function ParameterGroup({ nodeId, cate, tab, onOutPutChange, onSt
|
||||
{cate.name && <p className='mt-2 mb-3 text-sm font-bold'>{cate.name}</p>}
|
||||
{cate.params.map(item => tab === item.tab || !item.tab ? <Parameter
|
||||
nodeId={nodeId}
|
||||
node={node}
|
||||
key={item.key}
|
||||
item={item}
|
||||
onOutPutChange={onOutPutChange}
|
||||
|
||||
@@ -64,7 +64,7 @@ const Log = ({ type, name, data }) => {
|
||||
|
||||
export default function RunLog({ node, children }) {
|
||||
const [state, setState] = useState<Status>(Status.normal)
|
||||
const [data, setData] = useState<any[]>([])
|
||||
const [data, setData] = useState<any>({})
|
||||
|
||||
// 订阅日志事件
|
||||
useEffect(() => {
|
||||
@@ -72,8 +72,28 @@ export default function RunLog({ node, children }) {
|
||||
const { nodeId, action, data } = e.detail
|
||||
if (nodeId !== node.id && nodeId !== '*') return
|
||||
|
||||
if (Object.keys(data).length) {
|
||||
let result = {};
|
||||
let hasKeys = []
|
||||
|
||||
// 遍历 b 中的 group_params
|
||||
node.group_params.forEach(group => {
|
||||
group.params.forEach(param => {
|
||||
if (data[param.key]) {
|
||||
result[param.label] = data[param.key];
|
||||
hasKeys.push(param.key)
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
for (let key in data) {
|
||||
if (!hasKeys.includes(key)) {
|
||||
result[key] = data[key];
|
||||
}
|
||||
}
|
||||
setData(result)
|
||||
}
|
||||
setState(action)
|
||||
setData(data)
|
||||
}
|
||||
window.addEventListener('nodeLogEvent', onNodeLogEvent)
|
||||
return () => {
|
||||
|
||||
@@ -4,6 +4,8 @@ import { Textarea } from "@/components/bs-ui/input";
|
||||
import { Label } from "@/components/bs-ui/label";
|
||||
import { Sheet, SheetClose, SheetContent, SheetFooter, SheetHeader, SheetTitle } from "@/components/bs-ui/sheet";
|
||||
import { useToast } from "@/components/bs-ui/toast/use-toast";
|
||||
import { runWorkflowNodeApi } from "@/controllers/API/workflow";
|
||||
import { captureAndAlertRequestErrorHoc } from "@/controllers/request";
|
||||
import { WorkflowNode } from "@/types/flow";
|
||||
import { copyText } from "@/utils";
|
||||
import { Copy, CopyCheck } from "lucide-react";
|
||||
@@ -27,7 +29,6 @@ export const ResultText = ({ title, value }: { title: string, value: any }) => {
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
return value
|
||||
})
|
||||
const handleCopy = (e) => {
|
||||
e.stopPropagation()
|
||||
@@ -44,7 +45,7 @@ export const ResultText = ({ title, value }: { title: string, value: any }) => {
|
||||
<p>{title}</p>
|
||||
{copyed ? <CopyCheck size={14} /> : <Copy size={14} className="cursor-pointer" onClick={handleCopy} />}
|
||||
</div>
|
||||
<textarea defaultValue={text} disabled className="w-full p-2 block text-muted-foreground " />
|
||||
<textarea defaultValue={text} disabled className="w-full min-h-28 p-2 block text-muted-foreground " />
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -99,24 +100,19 @@ export const RunTest = forwardRef((props, ref) => {
|
||||
}
|
||||
})
|
||||
setLoading(true)
|
||||
|
||||
console.log('给后端什么 :>> ', node, inputs);
|
||||
console.log('结果展示 :>> ');
|
||||
setResults([
|
||||
{ title: '输出1', text: '输出1' },
|
||||
{ title: '输出1', text: '输出1' },
|
||||
{ title: '输出1', text: '输出1' },
|
||||
{ title: '输出1', text: '输出1' },
|
||||
{ title: '输出1', text: '输出1' },
|
||||
{ title: '输出1', text: '输出1' },
|
||||
{ title: '输出1', text: '输出1' },
|
||||
{ title: '输出1', text: '输出1' },
|
||||
{ title: '输出2', text: '输出2' }
|
||||
])
|
||||
|
||||
setTimeout(() => {
|
||||
setLoading(false)
|
||||
}, 2000);
|
||||
captureAndAlertRequestErrorHoc(
|
||||
runWorkflowNodeApi(
|
||||
inputs.reduce((result, input) => {
|
||||
result[`${node.id}.${input.key}`] = input.value;
|
||||
return result;
|
||||
}, {}),
|
||||
node
|
||||
).then(res => {
|
||||
const result = Object.keys(res).map(key => ({ title: key, text: res[key] }))
|
||||
setResults(result)
|
||||
setLoading(false)
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -156,7 +152,7 @@ export const RunTest = forwardRef((props, ref) => {
|
||||
|
||||
<Button className="w-full mb-2" disabled={loading} onClick={handleRunClick}>运行</Button>
|
||||
{results.length !== 0 && <p className='mt-2 mb-3 text-sm font-bold'>运行结果</p>}
|
||||
{results.map(res => <ResultText key={res.title} title={res.title} text={res.text} />)}
|
||||
{results.map(res => <ResultText key={res.text} title={res.title} value={res.text} />)}
|
||||
</div>
|
||||
<SheetFooter>
|
||||
<SheetClose asChild>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Handle, Position } from "@xyflow/react";
|
||||
import { ChevronDown, RefreshCcw, Trash2 } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import SelectVar from "./SelectVar";
|
||||
import { CustomHandle } from "..";
|
||||
|
||||
interface Item {
|
||||
id: string; // UUID 类型的字符串
|
||||
@@ -111,7 +112,7 @@ const Item = ({ nodeId, item, index, del, required, onUpdateItem, onDeleteItem }
|
||||
);
|
||||
};
|
||||
|
||||
export default function ConditionItem({ nodeId, data, onChange, onValidate }) {
|
||||
export default function ConditionItem({ nodeId, node, data, onChange, onValidate }) {
|
||||
const [value, setValue] = useState([]);
|
||||
|
||||
const handleAddCondition = () => {
|
||||
@@ -235,25 +236,13 @@ export default function ConditionItem({ nodeId, data, onChange, onValidate }) {
|
||||
<Button onClick={() => handleAddItem(val.id)} variant="outline" className="border-primary text-primary mt-2 h-8">
|
||||
+ 添加条件
|
||||
</Button>
|
||||
<Handle
|
||||
id={val.id}
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
className='bisheng-flow-handle group'
|
||||
style={{ top: 20, right: -30 }}
|
||||
><span></span></Handle>
|
||||
<CustomHandle id={val.id} node={node} className="top-[20px] -right-[30px]" />
|
||||
</div>)
|
||||
}
|
||||
|
||||
<div className="relative">
|
||||
<p className='mt-2 mb-3 text-sm font-bold'>否则</p>
|
||||
<Handle
|
||||
id='right_handle'
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
className='bisheng-flow-handle group'
|
||||
style={{ top: 12, right: -30 }}
|
||||
><span></span></Handle>
|
||||
<CustomHandle node={node} className="top-[12px] -right-[30px]" />
|
||||
</div>
|
||||
<Button onClick={handleAddCondition} variant="outline" className="border-primary text-primary mt-2 h-8">
|
||||
+ 添加分支
|
||||
|
||||
@@ -99,7 +99,7 @@ export default function DragOptions({ edges = false, scroll = false, options, on
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`${scroll && 'nowheel overflow-y-auto max-h-96'} mt-2`}>
|
||||
<div className={`${scroll && 'nowheel overflow-y-auto max-h-80'} mt-2`}>
|
||||
<DragDropContext onDragEnd={handleDragEnd} usePortal>
|
||||
<Droppable droppableId="options-list">
|
||||
{(provided) => (
|
||||
|
||||
@@ -187,6 +187,7 @@ function Form({ initialData, onSubmit, onCancel, existingOptions }) {
|
||||
<div>
|
||||
<Label className="bisheng-label">选项</Label>
|
||||
<DragOptions
|
||||
scroll
|
||||
options={formData.options}
|
||||
onChange={updateOptions}
|
||||
/>
|
||||
|
||||
@@ -56,7 +56,7 @@ export default function KnowledgeSelectItem({ data, onChange, onValidate }) {
|
||||
if (param.key === 'form_input') {
|
||||
param.value.forEach(val => {
|
||||
val.type === 'file' && files.push({
|
||||
label: val.value,
|
||||
label: val.key,
|
||||
value: `${node.id}.${val.key}`
|
||||
})
|
||||
})
|
||||
|
||||
@@ -31,8 +31,12 @@ export default function ModelItem({ agent = false, data, onChange, onValidate })
|
||||
setOptions(llmOptions)
|
||||
// return { llmOptions, embeddings }
|
||||
})
|
||||
|
||||
// TODO更新默认值
|
||||
// !data.value && onChange(60)
|
||||
}, [])
|
||||
|
||||
|
||||
const defaultValue = useMemo(() => {
|
||||
if (!options.length) return ''
|
||||
let _defaultValue = []
|
||||
|
||||
@@ -3,11 +3,11 @@ import { Label } from "@/components/bs-ui/label";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/bs-ui/radio";
|
||||
import { QuestionTooltip } from "@/components/bs-ui/tooltip";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { CustomHandle } from "..";
|
||||
import DragOptions from "./DragOptions";
|
||||
import VarInput from "./VarInput";
|
||||
import { Handle, Position } from "@xyflow/react";
|
||||
|
||||
const OutputItem = ({ nodeId, data, onChange, onValidate }) => {
|
||||
const OutputItem = ({ nodeId, node, data, onChange, onValidate }) => {
|
||||
const [interactionType, setInteractionType] = useState<string>(data.value.type || "none"); // 交互类型状态
|
||||
const options = useMemo(() => {
|
||||
return data.options.map(el => ({
|
||||
@@ -65,7 +65,7 @@ const OutputItem = ({ nodeId, data, onChange, onValidate }) => {
|
||||
return false
|
||||
})
|
||||
|
||||
return () => onValidate(() => {})
|
||||
return () => onValidate(() => { })
|
||||
}, [data.value, interactionType])
|
||||
|
||||
return (
|
||||
@@ -98,13 +98,7 @@ const OutputItem = ({ nodeId, data, onChange, onValidate }) => {
|
||||
<div className="interaction-content mt-4 nodrag">
|
||||
{renderContent()}
|
||||
{error && <div className="text-red-500 text-sm mt-2">选项不可为空</div>}
|
||||
{interactionType !== 'choose' && <Handle
|
||||
id="right_handle"
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
className='bisheng-flow-handle group'
|
||||
style={{ top: 58, right: -16 }}
|
||||
><span></span></Handle>}
|
||||
{interactionType !== 'choose' && <CustomHandle node={node} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -12,7 +12,7 @@ import { DialogClose } from "@/components/bs-ui/dialog"
|
||||
export default function ReportWordEdit({ versionKey, nodeId, onChange }) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { docx, loading, createDocx, importDocx } = useReport(versionKey, onChange)
|
||||
const { docx, loading, pageLoading, createDocx, importDocx } = useReport(versionKey, onChange)
|
||||
|
||||
// inset var
|
||||
const iframeRef = useRef(null)
|
||||
@@ -28,6 +28,10 @@ export default function ReportWordEdit({ versionKey, nodeId, onChange }) {
|
||||
}), '*');
|
||||
}
|
||||
|
||||
if (pageLoading) return <div className="absolute w-full h-full top-0 left-0 flex justify-center items-center z-10 bg-primary/20">
|
||||
<LoadingIcon />
|
||||
</div>
|
||||
|
||||
// new
|
||||
if (!docx.path) return <div className="relative size-full">
|
||||
<div className="absolute -top-10 z-10 flex gap-4">
|
||||
@@ -74,6 +78,7 @@ export default function ReportWordEdit({ versionKey, nodeId, onChange }) {
|
||||
|
||||
const useReport = (versionKey, onchange) => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [pageLoading, setPageLoading] = useState(true)
|
||||
|
||||
const [docx, setDocx] = useState({
|
||||
key: '',
|
||||
@@ -82,6 +87,7 @@ const useReport = (versionKey, onchange) => {
|
||||
|
||||
useEffect(() => {
|
||||
getWorkflowReportTemplate(versionKey).then(res => {
|
||||
setPageLoading(false)
|
||||
setDocx({
|
||||
key: res.version_key,
|
||||
path: res.url
|
||||
@@ -93,7 +99,7 @@ const useReport = (versionKey, onchange) => {
|
||||
|
||||
const handleCreate = async () => {
|
||||
// setDocx(docx => ({ ...docx, path: 'http://192.168.106.120:3002/empty.docx' }))
|
||||
setDocx(doc => ({...docx, path: location.origin + __APP_ENV__.BASE_URL + '/empty.docx'}))// 文档服务能访问到的文件地址
|
||||
setDocx(doc => ({ ...docx, path: location.origin + __APP_ENV__.BASE_URL + '/empty.docx' }))// 文档服务能访问到的文件地址
|
||||
}
|
||||
|
||||
const handleImport = () => {
|
||||
@@ -121,6 +127,7 @@ const useReport = (versionKey, onchange) => {
|
||||
|
||||
return {
|
||||
loading,
|
||||
pageLoading,
|
||||
docx,
|
||||
createDocx: handleCreate,
|
||||
importDocx: handleImport
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Select, SelectContent, SelectTrigger } from "@/components/bs-ui/select";
|
||||
import { cname } from "@/components/bs-ui/utils";
|
||||
import { ChevronRight } from "lucide-react";
|
||||
import { Check, ChevronRight } from "lucide-react";
|
||||
import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
|
||||
import useFlowStore from "../../flowStore";
|
||||
import NodeLogo from "../NodeLogo";
|
||||
@@ -14,12 +14,16 @@ const isMatch = (obj, expression) => {
|
||||
* @param nodeId 节点id, itemKey 当前变量key, children, onSelect
|
||||
* @returns
|
||||
*/
|
||||
const SelectVar = forwardRef(({ nodeId, itemKey, children, onSelect, className = '' }, ref) => {
|
||||
const SelectVar = forwardRef(({ nodeId, itemKey, multip = false, value = [], children, onSelect, className = '' }, ref) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const { flow } = useFlowStore()
|
||||
|
||||
const inputOpenRef = useRef(false)
|
||||
useImperativeHandle(ref, () => ({
|
||||
open() { setOpen(true) }
|
||||
open(inputOpen) {
|
||||
inputOpenRef.current = !!inputOpen
|
||||
setOpen(true)
|
||||
}
|
||||
}));
|
||||
|
||||
const getNodeDataByTemp = (temp) => {
|
||||
@@ -134,7 +138,10 @@ const SelectVar = forwardRef(({ nodeId, itemKey, children, onSelect, className =
|
||||
}, [open])
|
||||
|
||||
return <Select open={open} onOpenChange={setOpen}>
|
||||
<SelectTrigger showIcon={false} className={cname('group p-0 h-auto data-[placeholder]:text-inherit border-none bg-transparent shadow-none outline-none focus:shadow-none focus:outline-none focus:ring-0', className)}>
|
||||
<SelectTrigger
|
||||
onClick={() => inputOpenRef.current = false}
|
||||
showIcon={false}
|
||||
className={cname('group p-0 h-auto data-[placeholder]:text-inherit border-none bg-transparent shadow-none outline-none focus:shadow-none focus:outline-none focus:ring-0', className)}>
|
||||
{children}
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper" avoidCollisions={false}>
|
||||
@@ -160,25 +167,27 @@ const SelectVar = forwardRef(({ nodeId, itemKey, children, onSelect, className =
|
||||
className="relative flex justify-between w-full select-none items-center rounded-sm p-1.5 text-sm outline-none cursor-pointer hover:bg-[#EBF0FF] data-[focus=true]:bg-[#EBF0FF] dark:hover:bg-gray-700 dark:data-[focus=true]:bg-gray-700 data-[disabled]:pointer-events-none data-[disabled]:opacity-50"
|
||||
onClick={() => {
|
||||
if (v.param) return
|
||||
onSelect(currentMenuRef.current, v)
|
||||
setOpen(false)
|
||||
onSelect(currentMenuRef.current, v, inputOpenRef.current)
|
||||
!multip && setOpen(false)
|
||||
}}
|
||||
onMouseEnter={() => v.param && handleShowQuestions(v.param)}>
|
||||
onMouseEnter={() => v.param ? handleShowQuestions(v.param) : setQuestions([])}>
|
||||
<span className="w-28 overflow-hidden text-ellipsis">{v.label}</span>
|
||||
{v.param && <ChevronRight className="size-4" />}
|
||||
{value.includes(`${currentMenuRef.current.id}.${v.value}`) && <Check size={14} />}
|
||||
</div>
|
||||
)}
|
||||
</div>}
|
||||
{
|
||||
!!questions.length && <div className="w-36 border-l first:border-none">
|
||||
!!questions.length && <div className="w-44 border-l first:border-none">
|
||||
{questions.map(q =>
|
||||
<div
|
||||
className="relative flex justify-between w-full select-none items-center rounded-sm p-1.5 text-sm outline-none cursor-pointer hover:bg-[#EBF0FF] data-[focus=true]:bg-[#EBF0FF] dark:hover:bg-gray-700 dark:data-[focus=true]:bg-gray-700 data-[disabled]:pointer-events-none data-[disabled]:opacity-50"
|
||||
onClick={() => {
|
||||
onSelect(currentMenuRef.current, q)
|
||||
setOpen(false)
|
||||
onSelect(currentMenuRef.current, q, inputOpenRef.current)
|
||||
!multip && setOpen(false)
|
||||
}}>
|
||||
<span className="w-28 overflow-hidden text-ellipsis">{q.label}</span>
|
||||
<span className="w-full overflow-hidden text-ellipsis truncate">{q.label}</span>
|
||||
{value.includes(`${currentMenuRef.current.id}.${q.value}`) && <Check size={14} />}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -41,11 +41,11 @@ export const SwitchSliderItem = ({ data, onChange }) => {
|
||||
{data.label}
|
||||
{data.help && <QuestionTooltip content={data.help} />}
|
||||
</Label>
|
||||
<Switch checked={value.flag} onCheckedChange={(v) => {
|
||||
{/* <Switch checked={value.flag} onCheckedChange={(v) => {
|
||||
const newValue = { ...value, flag: v }
|
||||
setValue(newValue)
|
||||
onChange(newValue)
|
||||
}} />
|
||||
}} /> */}
|
||||
</div>
|
||||
<div className="flex gap-4 mt-2">
|
||||
<Slider
|
||||
|
||||
@@ -20,7 +20,7 @@ export default function ToolItem({ data, onChange }) {
|
||||
<TitleIconBg id={tool.id} className="h-6 w-6">
|
||||
<ToolIcon />
|
||||
</TitleIconBg>
|
||||
<p className="text-sm">{tool.name}</p>
|
||||
<p className="text-sm truncate w-60">{tool.name}</p>
|
||||
</div>
|
||||
<CircleMinus
|
||||
className="w-4 h-4 hidden text-primary group-hover:block"
|
||||
|
||||
@@ -112,7 +112,6 @@ export default function VarInput({ nodeId, itemKey, placeholder = '', flowNode,
|
||||
|
||||
// // 在光标位置插入内容
|
||||
const handleInsertVariable = (item, _var) => {
|
||||
console.log('1234 :>> ', 1234);
|
||||
const msg = textMsgRef.current
|
||||
// console.log('positon :>> ', cursorPositionRef.current, msg);
|
||||
const beforeCursor = msg.slice(0, cursorPositionRef.current);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { RbDragIcon } from "@/components/bs-icons/rbDrag";
|
||||
import { Button } from "@/components/bs-ui/button";
|
||||
import { Label } from "@/components/bs-ui/label";
|
||||
import { isVarInFlow } from "@/util/flowUtils";
|
||||
@@ -5,23 +6,34 @@ import { UploadCloud, Variable } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import useFlowStore from "../../flowStore";
|
||||
import SelectVar from "./SelectVar";
|
||||
import { RbDragIcon } from "@/components/bs-icons/rbDrag";
|
||||
|
||||
function encodeHTMLEntities(text) {
|
||||
const textarea = document.createElement("textarea");
|
||||
textarea.textContent = text;
|
||||
return textarea.innerHTML;
|
||||
}
|
||||
|
||||
function decodeHTMLEntities(text) {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(text, "text/html");
|
||||
return doc.documentElement.textContent;
|
||||
}
|
||||
// 解析富文本内容为保存格式
|
||||
function parseToValue(input, flowNode) {
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = input.replace(
|
||||
/<span[^>]*?>(.*?)<\/span>/g, // 匹配所有 <span> 标签
|
||||
(match, content) => {
|
||||
const _content = decodeHTMLEntities(content);
|
||||
// 在对象中查找匹配的 key
|
||||
const key = Object.keys(flowNode.varZh).find((k) => flowNode.varZh[k] === content);
|
||||
const key = Object.keys(flowNode.varZh).find((k) => flowNode.varZh[k] === _content);
|
||||
return key ? `{{#${key}#}}` : content; // 如果找到 key,返回 key,否则保持原内容
|
||||
}
|
||||
);
|
||||
|
||||
// 遍历子节点,将 <br> 转换为 \n,同时处理文本内容
|
||||
const traverseNodes = (node) => {
|
||||
let result = '';
|
||||
let result = tempDiv !== node && node.nodeName === 'DIV' ? '\n' : '';
|
||||
node.childNodes.forEach((child) => {
|
||||
if (child.nodeName === 'BR') {
|
||||
result += '\n'; // 换行符
|
||||
@@ -54,7 +66,6 @@ export default function VarInput({
|
||||
const selectVarRef = useRef(null);
|
||||
|
||||
const { flow } = useFlowStore();
|
||||
|
||||
// 校验变量是否可用
|
||||
const validateVarAvailble = () => {
|
||||
const value = valueRef.current;
|
||||
@@ -77,7 +88,7 @@ export default function VarInput({
|
||||
|
||||
function parseToHTML(input, validate = false) {
|
||||
let error = '';
|
||||
const html = input
|
||||
const html = encodeHTMLEntities(input)
|
||||
.replace(/{{#(.*?)#}}/g, (a, part) => {
|
||||
if (validate) {
|
||||
error = isVarInFlow(nodeId, flow.nodes, part, flowNode.varZh?.[part]);
|
||||
@@ -96,7 +107,7 @@ export default function VarInput({
|
||||
}, []);
|
||||
|
||||
// 在光标位置插入内容
|
||||
function handleInsertVariable(item, _var) {
|
||||
function handleInsertVariable(item, _var, inputOpen) {
|
||||
handleFocus();
|
||||
|
||||
const selection = window.getSelection();
|
||||
@@ -114,17 +125,25 @@ export default function VarInput({
|
||||
// 文本框内容
|
||||
const key = `${item.id}.${_var.value}`;
|
||||
const label = `${item.name}/${_var.label}`;
|
||||
|
||||
if (flowNode.varZh) {
|
||||
const existingProp = Object.keys(flowNode.varZh).find(prop => flowNode.varZh[prop] === label);
|
||||
if (existingProp) delete flowNode.varZh[existingProp];
|
||||
flowNode.varZh[key] = label;
|
||||
} else {
|
||||
flowNode.varZh = {
|
||||
[key]: label,
|
||||
};
|
||||
flowNode.varZh = { [key]: label };
|
||||
}
|
||||
|
||||
const html = `<span class="textarea-badge" contentEditable="false">${label}</span>`;
|
||||
const fragment = range.createContextualFragment(html);
|
||||
|
||||
if (inputOpen) {
|
||||
// 删除光标前一个字符{
|
||||
const r = selection.getRangeAt(0);
|
||||
r.setStart(r.endContainer, r.endOffset - 1);
|
||||
range.deleteContents();
|
||||
}
|
||||
|
||||
const lastChild = fragment.lastChild; // 提前保存引用
|
||||
if (lastChild) {
|
||||
range.deleteContents();
|
||||
@@ -182,11 +201,24 @@ export default function VarInput({
|
||||
onKeyDown={(e) => {
|
||||
// 唤起插入变量
|
||||
if (e.key === '{') {
|
||||
selectVarRef.current.open();
|
||||
e.preventDefault();
|
||||
requestAnimationFrame(() => {
|
||||
selectVarRef.current.open(true);
|
||||
});
|
||||
e.stopPropagation()
|
||||
}
|
||||
if (e.key === 'Backspace') {
|
||||
// del hook
|
||||
const selection = window.getSelection();
|
||||
const range = selection.getRangeAt(0);
|
||||
const node = range.startContainer;
|
||||
if (textareaRef.current === node) {
|
||||
range.setStart(range.endContainer, range.endOffset - 1);
|
||||
range.deleteContents();
|
||||
e.preventDefault(); // 阻止默认行为
|
||||
}
|
||||
}
|
||||
}}
|
||||
className="nowheel bisheng-richtext px-3 py-2 whitespace-pre-line min-h-[80px] max-h-64 overflow-y-auto overflow-x-hidden border-none outline-none bg-search-input rounded-md dark:text-gray-50 placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
className="nowheel bisheng-richtext px-3 py-2 cursor-text whitespace-pre-line min-h-[80px] max-h-64 overflow-y-auto overflow-x-hidden border-none outline-none bg-search-input rounded-md dark:text-gray-50 placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
></div>
|
||||
{children}
|
||||
<div
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ChevronUp } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function VarItem({ data }) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [open, setOpen] = useState(true)
|
||||
|
||||
if (Array.isArray(data.value) && data.value.length > 0) return <div className="mb-2">
|
||||
<div className="flex justify-between items-center">
|
||||
|
||||
@@ -1,52 +1,44 @@
|
||||
import { Badge } from "@/components/bs-ui/badge";
|
||||
import { Label } from "@/components/bs-ui/label";
|
||||
import { QuestionTooltip } from "@/components/bs-ui/tooltip";
|
||||
import { isVarInFlow } from "@/util/flowUtils";
|
||||
import { ChevronDown, X } from "lucide-react";
|
||||
import React, { useEffect } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import useFlowStore from "../../flowStore";
|
||||
import SelectVar from "./SelectVar";
|
||||
|
||||
export default function VarSelectItem({ nodeId, data, onChange, onOutPutChange, onValidate }) {
|
||||
const [value, setValue] = React.useState(data.value)
|
||||
const valueToOutput = (newValues, varZh) => {
|
||||
return newValues.map(el => {
|
||||
const labelName = varZh[el];
|
||||
return {
|
||||
key: el.split('.')[1].replace('#', '_'),
|
||||
label: labelName.split('/')[1]
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = (val) => {
|
||||
const newValues = value.filter(el => el !== val)
|
||||
const outputVar = valueToOutput(newValues)
|
||||
export default function VarSelectItem({ nodeId, data, onChange, onOutPutChange, onValidate, onVarEvent }) {
|
||||
const [value, setValue] = React.useState(() => data.value || []);
|
||||
const [error, setError] = React.useState(false);
|
||||
|
||||
onOutPutChange(data.linkage, outputVar)
|
||||
setValue(newValues)
|
||||
onChange(newValues)
|
||||
}
|
||||
const updateValue = (newValues) => {
|
||||
const outputVar = valueToOutput(newValues, data.varZh || {});
|
||||
onOutPutChange(data.linkage, outputVar);
|
||||
setValue(newValues);
|
||||
onChange(newValues);
|
||||
};
|
||||
|
||||
const handleDelete = (val) => updateValue(value.filter(el => el !== val));
|
||||
const handleChange = (item, v) => {
|
||||
// [nodeId.xxx]
|
||||
const itemVar = `${item.id}.${v.value}`
|
||||
if (value.includes(itemVar)) return
|
||||
const newValues = [...value, itemVar]
|
||||
// varZh {nodeId.xxx: '中文'}
|
||||
if (data.varZh) {
|
||||
data.varZh[itemVar] = `${item.name}/${v.label}`
|
||||
} else {
|
||||
data.varZh = { [itemVar]: `${item.name}/${v.label}` }
|
||||
const itemVar = `${item.id}.${v.value}`;
|
||||
if (!value.includes(itemVar)) {
|
||||
if (!data.varZh) data.varZh = {}; // 确保 data.varZh 已初始化
|
||||
data.varZh[itemVar] = `${item.name}/${v.label}`;
|
||||
const newValues = [...value, itemVar];
|
||||
updateValue(newValues);
|
||||
}
|
||||
// output {key: xxx , label: '中文'}[]
|
||||
const outputVar = valueToOutput(newValues)
|
||||
};
|
||||
|
||||
onOutPutChange(data.linkage, outputVar)
|
||||
setValue(newValues)
|
||||
onChange(newValues)
|
||||
}
|
||||
|
||||
const valueToOutput = (newValues) => {
|
||||
return newValues.map(el => {
|
||||
const labelName = data.varZh[el]
|
||||
return {
|
||||
key: el.split('.')[1].replace('#', '_'),
|
||||
label: labelName.split('/')[1]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const [error, setError] = React.useState(false)
|
||||
useEffect(() => {
|
||||
data.required && onValidate(() => {
|
||||
if (!data.value.length) {
|
||||
@@ -56,9 +48,28 @@ export default function VarSelectItem({ nodeId, data, onChange, onOutPutChange,
|
||||
setError(false)
|
||||
return false
|
||||
})
|
||||
return () => onValidate(() => {})
|
||||
return () => onValidate(() => { })
|
||||
}, [data.value])
|
||||
|
||||
|
||||
// 校验变量是否可用
|
||||
const { flow } = useFlowStore();
|
||||
const [errorKeys, setErrorKeys] = useState<string[]>([])
|
||||
const validateVarAvailble = () => {
|
||||
let error = ''
|
||||
const _errorKeys = []
|
||||
value.map(key => {
|
||||
error = isVarInFlow(nodeId, flow.nodes, key, data.varZh?.[key]);
|
||||
error && _errorKeys.push(key)
|
||||
})
|
||||
setErrorKeys(_errorKeys)
|
||||
return error;
|
||||
};
|
||||
useEffect(() => {
|
||||
onVarEvent && onVarEvent(validateVarAvailble);
|
||||
return () => onVarEvent && onVarEvent(() => { });
|
||||
}, [data, value]);
|
||||
|
||||
return <div className='node-item mb-4' data-key={data.key}>
|
||||
<div className="flex justify-between items-center">
|
||||
<Label className="flex items-center bisheng-label">
|
||||
@@ -68,10 +79,13 @@ export default function VarSelectItem({ nodeId, data, onChange, onOutPutChange,
|
||||
</Label>
|
||||
<Badge variant="outline" className="bg-[#E6ECF6] text-[#2B53A0]">{data.key}</Badge>
|
||||
</div>
|
||||
<SelectVar nodeId={nodeId} itemKey={data.key} onSelect={handleChange}>
|
||||
<SelectVar nodeId={nodeId} itemKey={data.key} multip value={value} onSelect={handleChange}>
|
||||
<div className={`${error && 'border-red-500'} no-drag nowheel mt-2 group flex min-h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-search-input px-3 py-1 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 data-[placeholder]:text-gray-400`}>
|
||||
<div className="flex flex-wrap size-full max-h-32 overflow-y-auto">
|
||||
{value.length ? value.map(item => <Badge onPointerDown={(e) => e.stopPropagation()} key={item} className="flex whitespace-normal items-center gap-1 select-none bg-primary/20 text-primary hover:bg-primary/15 m-[2px]">
|
||||
{value.length ? value.map(item => <Badge
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
key={item}
|
||||
className={`flex whitespace-normal items-center gap-1 select-none bg-primary/20 text-primary hover:bg-primary/15 m-[2px] ${errorKeys.includes(item) && 'bg-red-100 border-red-600'}`}>
|
||||
{data.varZh[item]}
|
||||
<X className="h-3 w-3 min-w-3" onClick={() => handleDelete(item)}></X>
|
||||
</Badge>
|
||||
@@ -85,7 +99,7 @@ export default function VarSelectItem({ nodeId, data, onChange, onOutPutChange,
|
||||
|
||||
|
||||
// 单选
|
||||
export function VarSelectSingleItem({ nodeId, data, onChange, onValidate }) {
|
||||
export function VarSelectSingleItem({ nodeId, data, onChange, onValidate, onVarEvent }) {
|
||||
const [value, setValue] = React.useState(data.value)
|
||||
|
||||
const handleChange = (item, v) => {
|
||||
@@ -113,9 +127,21 @@ export function VarSelectSingleItem({ nodeId, data, onChange, onValidate }) {
|
||||
setError(false)
|
||||
return false
|
||||
})
|
||||
return () => onValidate(() => {})
|
||||
return () => onValidate(() => { })
|
||||
}, [data.value])
|
||||
|
||||
// 校验变量是否可用
|
||||
const { flow } = useFlowStore();
|
||||
const validateVarAvailble = () => {
|
||||
const error = isVarInFlow(nodeId, flow.nodes, value, data.varZh?.[value])
|
||||
error && setError(true)
|
||||
return error;
|
||||
};
|
||||
useEffect(() => {
|
||||
onVarEvent && onVarEvent(validateVarAvailble);
|
||||
return () => onVarEvent && onVarEvent(() => { });
|
||||
}, [data, value]);
|
||||
|
||||
return <div className='node-item mb-4' data-key={data.key}>
|
||||
<div className="flex justify-between items-center">
|
||||
<Label className="flex items-center bisheng-label">
|
||||
|
||||
@@ -7,14 +7,14 @@ export default function VarTextareaItem({ nodeId, data, onChange, onValidate, on
|
||||
|
||||
useEffect(() => {
|
||||
data.required && onValidate(() => {
|
||||
if (!data.value) {
|
||||
if (!data.value.trim()) {
|
||||
setError(true)
|
||||
return data.label + '不可为空'
|
||||
}
|
||||
setError(false)
|
||||
return false
|
||||
})
|
||||
return () => onValidate(() => {})
|
||||
return () => onValidate(() => { })
|
||||
}, [data.value])
|
||||
|
||||
return (
|
||||
|
||||
@@ -20,7 +20,7 @@ export default function VarTextareaUploadItem({ nodeId, data, onChange, onValida
|
||||
const [error, setError] = useState(false)
|
||||
useEffect(() => {
|
||||
data.required && onValidate(() => {
|
||||
if (!data.value?.msg) {
|
||||
if (!data.value?.msg && data.value?.files.length === 0) {
|
||||
setError(true)
|
||||
return data.label + '不可为空'
|
||||
}
|
||||
@@ -32,7 +32,10 @@ export default function VarTextareaUploadItem({ nodeId, data, onChange, onValida
|
||||
|
||||
return (
|
||||
<div className='node-item mb-4 nodrag' data-key={data.key}>
|
||||
<Label className='bisheng-label'>{data.label}</Label>
|
||||
<Label className='bisheng-label'>
|
||||
{data.required && <span className="text-red-500">*</span>}
|
||||
{data.label}
|
||||
</Label>
|
||||
<VarInput
|
||||
error={error}
|
||||
placeholder={data.placeholder}
|
||||
@@ -89,7 +92,7 @@ export const useFileUpload = (_files, onFilesChange) => {
|
||||
const handleFileUpload = () => {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = "image/*"; // Restrict to images
|
||||
input.accept = "image/*,application/pdf"; // Restrict to images
|
||||
input.style.display = "none";
|
||||
input.multiple = false;
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { LoadingIcon } from '@/components/bs-icons/loading';
|
||||
import { Card, CardContent } from '@/components/bs-ui/card';
|
||||
import { useToast } from '@/components/bs-ui/toast/use-toast';
|
||||
import { cname } from '@/components/bs-ui/utils';
|
||||
import { WorkflowNode } from '@/types/flow';
|
||||
import { Handle, NodeToolbar, Position } from '@xyflow/react';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import Sidebar from '../Sidebar';
|
||||
import EditText from './EditText';
|
||||
import NodeLogo from './NodeLogo';
|
||||
import NodeTabs from './NodeTabs';
|
||||
@@ -11,7 +14,92 @@ import NodeToolbarComponent from './NodeToolbarComponent';
|
||||
import ParameterGroup from './ParameterGroup';
|
||||
import RunLog from './RunLog';
|
||||
import { RunTest } from './RunTest';
|
||||
import { cname } from '@/components/bs-ui/utils';
|
||||
|
||||
export const CustomHandle = ({ id = '', node, isLeft = false, className = '' }) => {
|
||||
const [openLeft, setOpenLeft] = useState(false);
|
||||
const [openRight, setOpenRight] = useState(false);
|
||||
const posRef = useRef({ x: 0, y: 0 });
|
||||
|
||||
useEffect(() => {
|
||||
const handleAddLine = () => {
|
||||
setOpenLeft(false);
|
||||
setOpenRight(false);
|
||||
};
|
||||
|
||||
window.addEventListener("closeHandleMenu", handleAddLine);
|
||||
return () => {
|
||||
window.removeEventListener("closeHandleMenu", handleAddLine);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleOptionClick = (newNode) => {
|
||||
const addNodeEvent = new CustomEvent("addNodeByHandle", {
|
||||
detail: {
|
||||
id,
|
||||
targetNode: node,
|
||||
newNode: newNode,
|
||||
isLeft: isLeft,
|
||||
position: posRef.current
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(addNodeEvent);
|
||||
};
|
||||
|
||||
if (isLeft) {
|
||||
return <div className={cname('absolute top-[58px] -left-[16px]', className)}>
|
||||
<Handle
|
||||
id={id || "left_handle"}
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
className='bisheng-flow-handle group'
|
||||
onClick={(e) => {
|
||||
posRef.current = { x: e.clientX, y: e.clientY }
|
||||
setOpenLeft(true)
|
||||
}}
|
||||
><span></span></Handle>
|
||||
{
|
||||
openLeft && <Card
|
||||
className="absolute top-4 translate-x-[-50%] bg-transparent hover:shadow-none hover:border-transparent"
|
||||
style={{ zIndex: 1001 }}
|
||||
>
|
||||
<CardContent className="min-w-56 pointer-events-auto px-0">
|
||||
<Sidebar
|
||||
dropdown
|
||||
disabledNodes={['end']}
|
||||
onClick={handleOptionClick}
|
||||
></Sidebar>
|
||||
</CardContent>
|
||||
</Card>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
return <div className={cname('absolute top-[58px] right-[-16px]', className)}>
|
||||
<Handle
|
||||
id={id || "right_handle"}
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
className='bisheng-flow-handle group'
|
||||
onClick={(e) => {
|
||||
posRef.current = { x: e.clientX, y: e.clientY }
|
||||
setOpenRight(true)
|
||||
}}
|
||||
><span></span></Handle>
|
||||
{
|
||||
openRight && <Card
|
||||
className="absolute top-4 translate-x-[-50%] bg-transparent hover:shadow-none hover:border-transparent"
|
||||
style={{ zIndex: 1001 }}
|
||||
>
|
||||
<CardContent className="min-w-56 pointer-events-auto px-0">
|
||||
<Sidebar
|
||||
dropdown
|
||||
onClick={handleOptionClick}
|
||||
></Sidebar>
|
||||
</CardContent>
|
||||
</Card>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
function CustomNode({ data: node, selected, isConnectable }: { data: WorkflowNode, selected: boolean, isConnectable: boolean }) {
|
||||
const [focusUpdate, setFocusUpdate] = useState(false)
|
||||
@@ -47,7 +135,6 @@ function CustomNode({ data: node, selected, isConnectable }: { data: WorkflowNod
|
||||
})
|
||||
})
|
||||
setFocusUpdate(!focusUpdate) // render
|
||||
console.log('node :>> ', key, value, node);
|
||||
}
|
||||
|
||||
const { paramValidateEntities, varValidateEntities, validateParams } = useEventMaster(node)
|
||||
@@ -63,7 +150,7 @@ function CustomNode({ data: node, selected, isConnectable }: { data: WorkflowNod
|
||||
runRef.current.run(node)
|
||||
}
|
||||
|
||||
const [expend, setExpend] = useState(false)
|
||||
const [expend, setExpend] = useState(!!node.expand)
|
||||
const nodeError = useBorderColor(node)
|
||||
|
||||
const { isVisible, handleMouseEnter, handleMouseLeave } = useHoverToolbar();
|
||||
@@ -109,9 +196,12 @@ function CustomNode({ data: node, selected, isConnectable }: { data: WorkflowNod
|
||||
</EditText>
|
||||
</div>
|
||||
{!['output', 'condition', 'end'].includes(node.type) && <ChevronDown
|
||||
className={`absolute right-0 bisheng-label cursor-pointer ${expend && 'rotate-180'}`}
|
||||
className={`absolute right-0 bisheng-label cursor-pointer ${!expend && 'rotate-180'}`}
|
||||
size={14}
|
||||
onClick={() => setExpend(!expend)}
|
||||
onClick={() => {
|
||||
setExpend(!expend)
|
||||
node.expand = !expend
|
||||
}}
|
||||
/>}
|
||||
</div>
|
||||
<EditText
|
||||
@@ -129,18 +219,29 @@ function CustomNode({ data: node, selected, isConnectable }: { data: WorkflowNod
|
||||
</div>
|
||||
{/* body */}
|
||||
<div className='-nowheel bg-[#F7F8FB] pb-5 rounded-b-[20px]'>
|
||||
<div className={expend ? `h-0 overflow-hidden` : ''}>
|
||||
<div className={expend || ['output', 'condition', 'end'].includes(node.type) ? `` : 'h-0 overflow-hidden'}>
|
||||
{node.tab && <NodeTabs
|
||||
data={node.tab}
|
||||
onChange={(val) => {
|
||||
setCurrentTab(val)
|
||||
node.tab.value = val
|
||||
// 特殊逻辑
|
||||
handleChangeOutPut('output', [])
|
||||
node.group_params.some(group => {
|
||||
return group.params.some(param => {
|
||||
if (param.key === "batch_variable") {
|
||||
param.value = []
|
||||
return true
|
||||
}
|
||||
})
|
||||
})
|
||||
}} />}
|
||||
{node.group_params.map(group =>
|
||||
<ParameterGroup
|
||||
nodeId={node.id}
|
||||
key={group.name}
|
||||
tab={currentTab}
|
||||
node={node}
|
||||
cate={group}
|
||||
onOutPutChange={handleChangeOutPut}
|
||||
onStatusChange={((key, obj) => paramValidateEntities.current[key] = obj)}
|
||||
@@ -150,20 +251,12 @@ function CustomNode({ data: node, selected, isConnectable }: { data: WorkflowNod
|
||||
</div>
|
||||
</div>
|
||||
{/* footer */}
|
||||
{node.type !== 'start' && <Handle
|
||||
id="left_handle"
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
className='bisheng-flow-handle group'
|
||||
style={{ top: 58, left: -16 }}
|
||||
><span></span></Handle>}
|
||||
{!['condition', 'output', 'end'].includes(node.type) && <Handle
|
||||
id="right_handle"
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
className='bisheng-flow-handle group'
|
||||
style={{ top: 58, right: -16 }}
|
||||
><span></span></Handle>}
|
||||
{
|
||||
node.type !== 'start' && <CustomHandle isLeft node={node} />
|
||||
}
|
||||
{
|
||||
!['condition', 'output', 'end'].includes(node.type) && <CustomHandle node={node} />
|
||||
}
|
||||
</div>
|
||||
|
||||
<RunTest ref={runRef} />
|
||||
|
||||
@@ -5,6 +5,7 @@ import { bsConfirm } from "@/components/bs-ui/alertDialog/useConfirm";
|
||||
import { Badge } from "@/components/bs-ui/badge";
|
||||
import { Button } from "@/components/bs-ui/button";
|
||||
import ActionButton from "@/components/bs-ui/button/actionButton";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/bs-ui/dialog";
|
||||
import TextInput from "@/components/bs-ui/input/textInput";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/bs-ui/popover";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/bs-ui/radio";
|
||||
@@ -16,23 +17,25 @@ import { captureAndAlertRequestErrorHoc } from "@/controllers/request";
|
||||
import { AppType } from "@/types/app";
|
||||
import { FlowVersionItem } from "@/types/flow";
|
||||
import { findParallelNodes } from "@/util/flowUtils";
|
||||
import { ChevronLeft, EllipsisVertical, PencilLineIcon, Play, Shield, ShieldCheck, TicketCheck } from "lucide-react";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import { isEqual } from "lodash-es";
|
||||
import { ChevronLeft, EllipsisVertical, PencilLineIcon, Play, ShieldCheck } from "lucide-react";
|
||||
import { useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { unstable_useBlocker as useBlocker, useNavigate } from "react-router-dom";
|
||||
import CreateApp from "../CreateApp";
|
||||
import { ChatTest } from "./FlowChat/ChatTest";
|
||||
import useFlowStore from "./flowStore";
|
||||
import Notification from "./Notification";
|
||||
|
||||
const Header = ({ flow, onTabChange }) => {
|
||||
const Header = ({ flow, onTabChange, preFlow }) => {
|
||||
const { message } = useToast()
|
||||
const testRef = useRef(null)
|
||||
const updateAppModalRef = useRef(null)
|
||||
const { uploadFlow } = useFlowStore()
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
console.log('flow :>> ', flow);
|
||||
const [modelVersionId, setModelVersionId] = useState(0)
|
||||
// console.log('flow :>> ', flow);
|
||||
|
||||
const validateNodes = useNodeEvent(flow)
|
||||
const addNotification = useFlowStore((state) => state.addNotification);
|
||||
@@ -70,22 +73,37 @@ const Header = ({ flow, onTabChange }) => {
|
||||
}
|
||||
|
||||
// api请求
|
||||
// TODO 指定版本上线
|
||||
const res = await captureAndAlertRequestErrorHoc(onlineWorkflow(flow, 2))
|
||||
if (res) {
|
||||
message({
|
||||
variant: 'success',
|
||||
description: "上线成功"
|
||||
description: `${version?.name} 已上线`
|
||||
})
|
||||
window.history.length > 1 ? window.history.back() : navigate('/build/apps')
|
||||
}
|
||||
}
|
||||
const handleOfflineClick = () => {
|
||||
message({
|
||||
variant: 'success',
|
||||
description: `${version?.name} 已下线`
|
||||
})
|
||||
}
|
||||
|
||||
const handleSaveClick = async () => {
|
||||
|
||||
if (isOnlineVersion()) return message({
|
||||
title: '提示',
|
||||
description: '工作流已上线不可编辑,您可以另存为新版本进行保存',
|
||||
variant: 'warning'
|
||||
})
|
||||
if (isOnlineVersionFun()) {
|
||||
hasChanged ? message({
|
||||
title: '提示',
|
||||
description: '当前版本已上线,请先下线或另存为新版本',
|
||||
variant: 'warning'
|
||||
}) : message({
|
||||
title: '提示',
|
||||
description: '工作流已上线不可编辑,您可以另存为新版本进行保存',
|
||||
variant: 'warning'
|
||||
})
|
||||
return !hasChanged
|
||||
}
|
||||
// temp
|
||||
// localStorage.setItem('flow_tmp', JSON.stringify(flow))
|
||||
const res = await captureAndAlertRequestErrorHoc(saveWorkflow(version.id, {
|
||||
@@ -99,8 +117,9 @@ const Header = ({ flow, onTabChange }) => {
|
||||
}))
|
||||
res && message({
|
||||
variant: 'success',
|
||||
description: "保存成功"
|
||||
description: "更改已保存"
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
const handleExportClick = () => {
|
||||
@@ -127,8 +146,8 @@ const Header = ({ flow, onTabChange }) => {
|
||||
}
|
||||
// versions
|
||||
const [loading, setLoading] = useState(false)
|
||||
const { flow: f, setFlow } = useFlowStore()
|
||||
const { versions, version, lastVersionIndexRef, isOnlineVersion, changeName, deleteVersion, refrenshVersions, setCurrentVersion } = useVersion(flow)
|
||||
const { flow: f, setFlow, setFitView } = useFlowStore()
|
||||
const { versions, version, lastVersionIndexRef, isOnlineVersion, isOnlineVersionFun, changeName, deleteVersion, refrenshVersions, setCurrentVersion } = useVersion(flow)
|
||||
// 切换版本
|
||||
const handleChangeVersion = async (versionId) => {
|
||||
setLoading(true)
|
||||
@@ -145,6 +164,7 @@ const Header = ({ flow, onTabChange }) => {
|
||||
description: ""
|
||||
})
|
||||
setLoading(false)
|
||||
setFitView()
|
||||
}
|
||||
const [saveVersionId, setVersionId] = useState('')
|
||||
useEffect(() => {
|
||||
@@ -172,6 +192,13 @@ const Header = ({ flow, onTabChange }) => {
|
||||
|
||||
const [tabType, setTabType] = useState('edit')
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const [blocker, hasChanged] = useBeforeUnload(flow, preFlow)
|
||||
// 离开并保存
|
||||
const handleSaveAndClose = async () => {
|
||||
const res = await handleSaveClick()
|
||||
res ? blocker.proceed?.() : blocker.reset?.()
|
||||
}
|
||||
return (
|
||||
<header className="flex justify-between items-center p-4 py-2 bisheng-bg border-b">
|
||||
{
|
||||
@@ -210,7 +237,7 @@ const Header = ({ flow, onTabChange }) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* <div>
|
||||
<div>
|
||||
<Button variant="secondary" className={`${tabType === 'edit' ? 'bg-[#fff] hover:bg-[#fff]/70 text-primary h-8"' : ''} h-8`}
|
||||
onClick={() => { setTabType('edit'); onTabChange('edit') }}
|
||||
>
|
||||
@@ -220,7 +247,7 @@ const Header = ({ flow, onTabChange }) => {
|
||||
onClick={() => { setTabType('api'); onTabChange('api') }}>
|
||||
对外发布
|
||||
</Button>
|
||||
</div> */}
|
||||
</div>
|
||||
{/* Right Section with Options */}
|
||||
<div className="flex items-center gap-3">
|
||||
<Notification />
|
||||
@@ -232,8 +259,7 @@ const Header = ({ flow, onTabChange }) => {
|
||||
保存
|
||||
</Button>
|
||||
{
|
||||
// version && <ActionButton
|
||||
false && <ActionButton
|
||||
version && <ActionButton
|
||||
size="sm"
|
||||
className="px-6 flex gap-2 bg-[#fff]"
|
||||
iconClassName="bg-[#fff]"
|
||||
@@ -250,7 +276,13 @@ const Header = ({ flow, onTabChange }) => {
|
||||
dropDown={(
|
||||
<div className=" overflow-y-auto max-h-96 max-h">
|
||||
<RadioGroup value={version.id + ''} onValueChange={(vid) => {
|
||||
updateVersion(version.id, { name: version.name, description: '', data: flow.data })
|
||||
if (isOnlineVersionFun() && hasChanged) return setModelVersionId(vid)
|
||||
const { edges, nodes, viewport } = flow
|
||||
updateVersion(version.id, {
|
||||
name: version.name, description: '', data: {
|
||||
edges, nodes, viewport
|
||||
}
|
||||
})
|
||||
handleChangeVersion(vid)
|
||||
}} className="gap-0">
|
||||
{versions.map((vers, index) => (
|
||||
@@ -284,9 +316,11 @@ const Header = ({ flow, onTabChange }) => {
|
||||
)}
|
||||
>{t('skills.saveVersion')}</ActionButton>
|
||||
}
|
||||
<Button size="sm" className="h-8 px-6" onClick={handleOnlineClick}>
|
||||
{isOnlineVersion ? <Button size="sm" className="h-8 px-6" onClick={handleOfflineClick}>
|
||||
下线
|
||||
</Button> : <Button size="sm" className="h-8 px-6" onClick={handleOnlineClick}>
|
||||
上线
|
||||
</Button>
|
||||
</Button>}
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild >
|
||||
<Button size="icon" variant="outline" className="bg-[#fff] size-8">
|
||||
@@ -312,6 +346,45 @@ const Header = ({ flow, onTabChange }) => {
|
||||
setFlow({ ...f, ...base })
|
||||
onlineWorkflow(f)
|
||||
}} />
|
||||
{/* 上线不可修改提示 */}
|
||||
<Dialog open={!!modelVersionId}>
|
||||
<DialogContent className="sm:max-w-[425px]" close={false}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>提示</DialogTitle>
|
||||
<DialogDescription>当前版本已上线不可修改,可另存为新版本保存修改的内容</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="mt-4">
|
||||
<Button className="h-8" onClick={() => {
|
||||
handleSaveNewVersion()
|
||||
setModelVersionId(0)
|
||||
}}>另存为新版本</Button>
|
||||
<Button className="leave h-8" variant="destructive" onClick={() => {
|
||||
handleChangeVersion(modelVersionId)
|
||||
setModelVersionId(0)
|
||||
}}>不保存,直接切换</Button>
|
||||
<Button className="h-8" variant="outline" onClick={() => setModelVersionId(0)}>取消</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 离开并保存提示 */}
|
||||
<Dialog open={blocker.state === "blocked"}>
|
||||
<DialogContent className="sm:max-w-[425px]" close={false}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>提示</DialogTitle>
|
||||
<DialogDescription>您有未保存的更改,确定要离开吗?</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button className="leave h-8" onClick={handleSaveAndClose}>保存</Button>
|
||||
<Button className="h-8" variant="destructive" onClick={() => blocker.proceed?.()}>不保存</Button>
|
||||
<Button className="h-8" variant="outline" onClick={() => {
|
||||
const dom = document.getElementById("flow-page") as HTMLElement;
|
||||
blocker.reset?.()
|
||||
if (dom) dom.className = dom.className.replace('report-hidden', '');
|
||||
}}>取消</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
@@ -365,7 +438,9 @@ const useNodeEvent = (flow) => {
|
||||
*/
|
||||
const branchLines: { branch: string, nodeIds: { branch: string, nodeId: string }[], end: boolean }[] = []
|
||||
const nodeMap = {}
|
||||
const startNodeId = flow.edges.find(node => node.source.indexOf('start') === 0).source
|
||||
const startEdge = flow.edges.find(node => node.source.indexOf('start') === 0)
|
||||
if (!startEdge) return ['请先链接开始节点']
|
||||
const startNodeId = startEdge.source
|
||||
const findEdgesByNodeId = (id) => {
|
||||
return flow.edges.filter(node => node.source === id)
|
||||
}
|
||||
@@ -428,7 +503,7 @@ const useNodeEvent = (flow) => {
|
||||
[]
|
||||
// ...outputParallelNids.length > 1 ? outputParallelNids.map(node => node.nodeId) : []
|
||||
])
|
||||
return ['不支持多个 input 节点(输入型交互)并行执行']
|
||||
return ['不支持多个 input 节点并行执行']
|
||||
}
|
||||
|
||||
// 开始到结束流程是否完整
|
||||
@@ -525,7 +600,8 @@ const useVersion = (flow) => {
|
||||
return {
|
||||
versions,
|
||||
version,
|
||||
isOnlineVersion: () => version.id === onlineVid,
|
||||
isOnlineVersion: version?.id === onlineVid,
|
||||
isOnlineVersionFun: () => version.id === onlineVid,
|
||||
lastVersionIndexRef,
|
||||
setCurrentVersion(versionId) {
|
||||
const currentV = versions.find(el => el.id === versionId)
|
||||
@@ -539,4 +615,32 @@ const useVersion = (flow) => {
|
||||
}
|
||||
|
||||
|
||||
// 离开页面保存提示
|
||||
const useBeforeUnload = (flow, preFlow) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
// 离开提示保存
|
||||
useEffect(() => {
|
||||
const fun = (e) => {
|
||||
var confirmationMessage = `${t('flow.unsavedChangesConfirmation')}`;
|
||||
(e || window.event).returnValue = confirmationMessage; // Compatible with different browsers
|
||||
return confirmationMessage;
|
||||
}
|
||||
window.addEventListener('beforeunload', fun);
|
||||
return () => { window.removeEventListener('beforeunload', fun) }
|
||||
}, [])
|
||||
|
||||
const hasChanged = useMemo(() => {
|
||||
if (!flow) return false
|
||||
const oldFlowData = JSON.parse(preFlow)
|
||||
if (!oldFlowData) return true
|
||||
// 比较新旧
|
||||
const { edges, nodes } = flow
|
||||
const { edges: oldEdges, nodes: oldNodes } = oldFlowData
|
||||
return !(isEqual(edges, oldEdges) && isEqual(nodes, oldNodes))
|
||||
}, [preFlow, flow.nodes, flow.edges])
|
||||
|
||||
return [useBlocker(hasChanged), hasChanged] as const;
|
||||
}
|
||||
|
||||
export default Header;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import ApiMainPage from "@/components/bs-comp/apiComponent";
|
||||
import { generateUUID } from "@/components/bs-ui/utils";
|
||||
import { WorkFlow, WorkflowNode } from "@/types/flow";
|
||||
import { autoNodeName, initNode } from "@/util/flowUtils";
|
||||
import { autoNodeName, initNode, useCopyPasteNode } from "@/util/flowUtils";
|
||||
import { useUndoRedo } from "@/util/hook";
|
||||
import { Background, BackgroundVariant, Connection, Controls, ReactFlow, addEdge, applyEdgeChanges, applyNodeChanges, useReactFlow } from '@xyflow/react';
|
||||
import '@xyflow/react/dist/base.css';
|
||||
@@ -16,7 +17,7 @@ import useFlowStore from "./flowStore";
|
||||
// 自定义组件
|
||||
const nodeTypes = { flowNode: FlowNode };
|
||||
// 流程编排面板
|
||||
export default function Panne({ flow }: { flow: WorkFlow }) {
|
||||
export default function Panne({ flow, preFlow }: { flow: WorkFlow, preFlow: string }) {
|
||||
const [reactFlowInstance, setReactFlowInstance] = useState(null);
|
||||
// 导入自适应布局
|
||||
const fitView = useFlowStore(state => state.fitView)
|
||||
@@ -34,15 +35,11 @@ export default function Panne({ flow }: { flow: WorkFlow }) {
|
||||
}
|
||||
}, [])
|
||||
|
||||
const { takeSnapshot } = useUndoRedo()
|
||||
|
||||
const { reactFlowWrapper, nodes, edges, keyBoardPanneRef,
|
||||
setNodes, onNodesChange, onSelectionChange, onEdgesChange, onEdgeSelect, onConnect, onDragOver, onDrop } = useFlow(reactFlowInstance, flow)
|
||||
setNodes, onNodesChange, onSelectionChange, onEdgesChange, onEdgeSelect, onConnect, onDragOver, onDrop } = useFlow(reactFlowInstance, flow, takeSnapshot)
|
||||
|
||||
useUndoRedo(nodes,
|
||||
(data) => {
|
||||
console.log('undo :>> ', data);
|
||||
}, (data) => {
|
||||
console.log('redo :>> ', data);
|
||||
})
|
||||
/**
|
||||
* 监听节点变化,更新flow数据
|
||||
* 用户手动修改节点,或者使用reactFlow实例的setNode、setEdge都会触发
|
||||
@@ -70,9 +67,26 @@ export default function Panne({ flow }: { flow: WorkFlow }) {
|
||||
}
|
||||
};
|
||||
|
||||
const onNodeDragStart = useCallback(() => {
|
||||
// 👇 make dragging a node undoable
|
||||
takeSnapshot();
|
||||
// 👉 you can place your event handlers here
|
||||
}, [takeSnapshot]);
|
||||
|
||||
const onSelectionDragStart = useCallback(() => {
|
||||
// 👇 make dragging a selection undoable
|
||||
takeSnapshot();
|
||||
}, [takeSnapshot]);
|
||||
|
||||
const onEdgesDelete = useCallback(() => {
|
||||
// 👇 make deleting edges undoable
|
||||
takeSnapshot();
|
||||
}, [takeSnapshot]);
|
||||
|
||||
const [showApiPage, setShowApiPage] = useState(false)
|
||||
|
||||
return <div className="flex flex-col h-full overflow-hidden">
|
||||
<Header flow={flow} onTabChange={(type) => setShowApiPage('api' === type)}></Header>
|
||||
<Header flow={flow} onTabChange={(type) => setShowApiPage('api' === type)} preFlow={preFlow}></Header>
|
||||
<div className={`flex-1 min-h-0 overflow-hidden ${showApiPage ? 'hidden' : ''} relative`}>
|
||||
<Sidebar onInitStartNode={node => {
|
||||
// start node
|
||||
@@ -95,14 +109,17 @@ export default function Panne({ flow }: { flow: WorkFlow }) {
|
||||
onEdgesChange={onEdgesChange} // rebuild?
|
||||
onConnect={onConnect}
|
||||
nodeTypes={nodeTypes}
|
||||
onPaneClick={() => { setDropdownOpenEdgeId(null) }}
|
||||
onPaneClick={() => {
|
||||
setDropdownOpenEdgeId(null);
|
||||
window.dispatchEvent(new CustomEvent("closeHandleMenu"));
|
||||
}}
|
||||
edgeTypes={{
|
||||
customEdge: (edgeProps) => (
|
||||
<CustomEdge
|
||||
{...edgeProps}
|
||||
isDropdownOpen={dropdownOpenEdgeId === edgeProps.id}
|
||||
onButtonClick={handleButtonClick}
|
||||
onOptionSelect={() => { onEdgeSelect(); setDropdownOpenEdgeId(null) }}
|
||||
onOptionSelect={(data) => { onEdgeSelect(data); setDropdownOpenEdgeId(null) }}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
@@ -118,17 +135,17 @@ export default function Panne({ flow }: { flow: WorkFlow }) {
|
||||
// 阻止start节点删除
|
||||
!e.nodes.some(node => node.data.type === 'start')
|
||||
}
|
||||
onEdgesDelete={onEdgesDelete}
|
||||
onNodeDragStart={onNodeDragStart} // 快照
|
||||
onSelectionDragStart={onSelectionDragStart} // 快照
|
||||
onNodesDelete={() => takeSnapshot(flow)} // 更新setEdges
|
||||
// 自定义线组件
|
||||
// connectionLineComponent={ConnectionLineComponent}
|
||||
// 校验连线合法性
|
||||
// onReconnect={onEdgeUpdate}
|
||||
// onReconnectStart={onEdgeUpdateStart}
|
||||
// onReconnectEnd={onEdgeUpdateEnd}
|
||||
// onEdgesDelete={onEdgesDelete}
|
||||
|
||||
// onNodesDelete={onDelete} // 更新setEdges
|
||||
// onNodeDragStart={onNodeDragStart} // 快照
|
||||
// onSelectionDragStart={onSelectionDragStart} // 快照
|
||||
// 框选 (group)
|
||||
// onSelectionStart={(e) => { e.preventDefault(); setSelectionEnded(false) }}
|
||||
// onSelectionEnd={() => setSelectionEnded(true)}
|
||||
@@ -147,14 +164,13 @@ export default function Panne({ flow }: { flow: WorkFlow }) {
|
||||
</main>
|
||||
</div>
|
||||
<div className={`flex flex-1 min-h-0 overflow-hidden ${showApiPage ? '' : 'hidden'}`}>
|
||||
{/* <ApiMainPage type={'flow'} /> */}
|
||||
come sun~
|
||||
<ApiMainPage type={'flow'} />
|
||||
</div>
|
||||
</div>
|
||||
};
|
||||
|
||||
|
||||
const useFlow = (_reactFlowInstance, data) => {
|
||||
const useFlow = (_reactFlowInstance, data, takeSnapshot) => {
|
||||
|
||||
const reactFlowWrapper = useRef(null);
|
||||
|
||||
@@ -173,7 +189,7 @@ const useFlow = (_reactFlowInstance, data) => {
|
||||
}, [data, _reactFlowInstance, setEdges, setNodes, setViewport]);
|
||||
|
||||
// 绑定快捷键
|
||||
const { keyBoardPanneRef, setLastSelection } = useKeyBoard(reactFlowWrapper, setNodes)
|
||||
const { keyBoardPanneRef, setLastSelection } = useKeyBoard(_reactFlowInstance, reactFlowWrapper, setNodes, setEdges)
|
||||
|
||||
const onNodesChange = useCallback(
|
||||
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
|
||||
@@ -185,7 +201,7 @@ const useFlow = (_reactFlowInstance, data) => {
|
||||
);
|
||||
const onConnect = useCallback(
|
||||
(params: Connection) => {
|
||||
console.log('conect :>> ', params);
|
||||
takeSnapshot()
|
||||
setEdges((eds) => {
|
||||
return addEdge(
|
||||
{
|
||||
@@ -200,7 +216,7 @@ const useFlow = (_reactFlowInstance, data) => {
|
||||
});
|
||||
setNodes((x) => cloneDeep(x));
|
||||
},
|
||||
[setEdges, setNodes]
|
||||
[setEdges, setNodes, takeSnapshot]
|
||||
);
|
||||
|
||||
// 拖拽结束样式
|
||||
@@ -216,8 +232,9 @@ const useFlow = (_reactFlowInstance, data) => {
|
||||
(event: React.DragEvent) => {
|
||||
event.preventDefault();
|
||||
if (event.dataTransfer.types.some((t) => t === "flownodedata")) {
|
||||
takeSnapshot();
|
||||
const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||
let data: { type: string; node?: WorkflowNode } = JSON.parse(
|
||||
let flowdata: { type: string; node?: WorkflowNode } = JSON.parse(
|
||||
event.dataTransfer.getData("flownodedata")
|
||||
);
|
||||
|
||||
@@ -225,14 +242,13 @@ const useFlow = (_reactFlowInstance, data) => {
|
||||
x: event.clientX - reactflowBounds.left,
|
||||
y: event.clientY - reactflowBounds.top,
|
||||
});
|
||||
console.log('object :>> ', position, data);
|
||||
|
||||
const nodeId = `${data.node.type}_${generateUUID(5)}`
|
||||
data.node.id = nodeId
|
||||
const nodeId = `${flowdata.node.type}_${generateUUID(5)}`
|
||||
flowdata.node.id = nodeId
|
||||
// 增加节点
|
||||
setNodes((nds) => {
|
||||
const newName = autoNodeName(nds, data.node.name)
|
||||
const newNode = initNode(data.node)
|
||||
const newName = autoNodeName(nds, flowdata.node.name)
|
||||
const newNode = initNode(flowdata.node)
|
||||
newNode.name = newName
|
||||
return nds.concat({ id: nodeId, type: 'flowNode', position, data: newNode })
|
||||
});
|
||||
@@ -243,19 +259,38 @@ const useFlow = (_reactFlowInstance, data) => {
|
||||
}
|
||||
},
|
||||
// Specify dependencies for useCallback
|
||||
[setNodes, _reactFlowInstance]
|
||||
[setNodes, _reactFlowInstance, takeSnapshot]
|
||||
);
|
||||
|
||||
const onEdgeSelect = useCallback(() => {
|
||||
const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||
const position = _reactFlowInstance.screenToFlowPosition({
|
||||
x: 1 - reactflowBounds.left,
|
||||
y: 2 - reactflowBounds.top,
|
||||
const onEdgeSelect = (obj) => {
|
||||
takeSnapshot()
|
||||
const { node, edgeId, position } = obj
|
||||
let flowdata: { type: string; node: WorkflowNode } = cloneDeep(node)
|
||||
const nodeId = `${flowdata.node.type}_${generateUUID(5)}`
|
||||
flowdata.node.id = nodeId
|
||||
// 增加节点
|
||||
setNodes((nds) => {
|
||||
const newName = autoNodeName(nds, flowdata.node.name)
|
||||
const newNode = initNode(flowdata.node)
|
||||
newNode.name = newName
|
||||
return nds.concat({
|
||||
id: nodeId, type: 'flowNode', position: {
|
||||
x: position.x - 160,
|
||||
y: position.y - 100,
|
||||
}, data: newNode
|
||||
})
|
||||
});
|
||||
// setNodes((nds) => nds.concat(
|
||||
// { id: 'c', type: 'flowNode', position, data: { value: 'Node B' } }
|
||||
// ));
|
||||
}, [setNodes, _reactFlowInstance])
|
||||
// 增加边
|
||||
setEdges((eds) => {
|
||||
const edge = eds.find(el => el.id === edgeId)
|
||||
const leftEdge = { ...edge, selected: false, target: nodeId, id: `xy-edge__${edge.source}${edge.sourceHandle}-${nodeId}${edge.targetHandle}` }
|
||||
const rightEdge = { ...edge, selected: false, source: nodeId, sourceHandle: "right_handle", id: `xy-edge__${nodeId}right_handle-${edge.target}${edge.targetHandle}` }
|
||||
|
||||
return eds
|
||||
.filter(el => el.id !== edgeId)
|
||||
.concat(leftEdge, rightEdge);
|
||||
})
|
||||
}
|
||||
|
||||
// 监听来自自定义节点的Chang value
|
||||
useEffect(() => {
|
||||
@@ -271,6 +306,7 @@ const useFlow = (_reactFlowInstance, data) => {
|
||||
};
|
||||
// del node
|
||||
const handleNodeDelete = (event) => {
|
||||
takeSnapshot()
|
||||
const nodeId = event.detail;
|
||||
setNodes((nodes) => nodes.filter((n) => n.id !== nodeId));
|
||||
setEdges((edges) => edges.filter((ns) => ns.source !== nodeId && ns.target !== nodeId));
|
||||
@@ -308,11 +344,61 @@ const useFlow = (_reactFlowInstance, data) => {
|
||||
return nds.map((e) => ({ ...e, selected: false })).concat(_newNodes)
|
||||
});
|
||||
}
|
||||
// add node by handle
|
||||
const handleAddNode = (event) => {
|
||||
takeSnapshot()
|
||||
const { id, targetNode, isLeft, position } = event.detail;
|
||||
const newNode = cloneDeep(event.detail.newNode)
|
||||
window.dispatchEvent(new CustomEvent("closeHandleMenu"));
|
||||
|
||||
const nodeId = `${newNode.type}_${generateUUID(5)}`
|
||||
newNode.node.id = nodeId
|
||||
const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||
const pos = _reactFlowInstance.screenToFlowPosition({
|
||||
x: position.x - reactflowBounds.left + (isLeft ? -300 : 80),
|
||||
y: position.y - reactflowBounds.top,
|
||||
});
|
||||
// 增加节点
|
||||
setNodes((nds) => {
|
||||
const newName = autoNodeName(nds, newNode.node.name)
|
||||
const _newNode = initNode(newNode.node)
|
||||
_newNode.name = newName
|
||||
return nds.concat({
|
||||
id: nodeId, type: 'flowNode', position: pos, data: _newNode
|
||||
})
|
||||
});
|
||||
|
||||
// let data: { type: string; node: WorkflowNode } = node
|
||||
// data.node.id = nodeId
|
||||
// // 增加边
|
||||
const edge = isLeft ? {
|
||||
animated: true,
|
||||
id: `xy-edge__${nodeId}right_handle-${targetNode.id}left_handle`,
|
||||
source: nodeId,
|
||||
sourceHandle: "right_handle",
|
||||
target: targetNode.id,
|
||||
targetHandle: "left_handle",
|
||||
type: "customEdge"
|
||||
} : {
|
||||
animated: true,
|
||||
id: `xy-edge__${targetNode.id}${id || 'right_handle'}-${nodeId}left_handle`,
|
||||
source: targetNode.id,
|
||||
sourceHandle: id || "right_handle",
|
||||
target: nodeId,
|
||||
targetHandle: "left_handle",
|
||||
type: "customEdge"
|
||||
}
|
||||
setEdges((eds) => [
|
||||
edge,
|
||||
...eds
|
||||
]);
|
||||
}
|
||||
|
||||
// 监听自定义事件
|
||||
window.addEventListener('nodeUpdate', handleNodeUpdate);
|
||||
window.addEventListener('nodeDelete', handleNodeDelete);
|
||||
window.addEventListener('nodeCopy', handleCopy);
|
||||
window.addEventListener('addNodeByHandle', handleAddNode);
|
||||
|
||||
|
||||
// 在组件卸载时移除事件监听
|
||||
@@ -320,6 +406,7 @@ const useFlow = (_reactFlowInstance, data) => {
|
||||
window.removeEventListener('nodeUpdate', handleNodeUpdate);
|
||||
window.addEventListener('nodeDelete', handleNodeDelete);
|
||||
window.removeEventListener('nodeCopy', handleCopy);
|
||||
window.removeEventListener('addNodeByHandle', handleAddNode);
|
||||
};
|
||||
}, [_reactFlowInstance]);
|
||||
|
||||
@@ -334,24 +421,40 @@ const useFlow = (_reactFlowInstance, data) => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 复制粘贴组件,支持跨技能粘贴
|
||||
const useKeyBoard = (reactFlowWrapper, setNodes) => {
|
||||
const useKeyBoard = (_reactFlowInstance, reactFlowWrapper) => {
|
||||
const keyBoardPanneRef = useRef<HTMLDivElement>(null); // 绑定快捷键
|
||||
const [lastSelection, setLastSelection] = useState(null);
|
||||
// useCopyPaste(keyBoardPanneRef.current, lastSelection, (newSelection, position) => {
|
||||
// let bounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||
// setNodes((nds) => nds.concat(
|
||||
// {
|
||||
// id: 'd', type: 'flowNode', position: {
|
||||
// x: position.x - bounds.left,
|
||||
// y: position.y - bounds.top,
|
||||
// }, data: { value: 'Node 2' }
|
||||
// }
|
||||
// ))
|
||||
// }, [setNodes])
|
||||
const { setNodes, setEdges } = useReactFlow();
|
||||
|
||||
useCopyPasteNode(keyBoardPanneRef.current, lastSelection, (newSelectNode, position) => {
|
||||
if (newSelectNode.nodes.some(node => node.data.type === 'start')) return
|
||||
let bounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||
setNodes((nds) => {
|
||||
const newNodes = newSelectNode.nodes.map(node => {
|
||||
const newNode = cloneDeep(node)
|
||||
const nodeId = `${newNode.data.type}_${generateUUID(5)}`
|
||||
newNode.id = nodeId
|
||||
newNode.data.id = nodeId
|
||||
const newName = autoNodeName(nds, newNode.data.name)
|
||||
newNode.data.name = newName
|
||||
// newNode.selected = false
|
||||
|
||||
newNode.position = _reactFlowInstance.screenToFlowPosition({
|
||||
x: position.x - bounds.left,
|
||||
y: position.y - bounds.top,
|
||||
});
|
||||
return newNode
|
||||
})
|
||||
return [...newNodes, ...nds]
|
||||
})
|
||||
}, (selectNode) => {
|
||||
// takeSnapshot()
|
||||
const targetNodes = selectNode.nodes;
|
||||
if (targetNodes.some(node => node.data.type === 'start')) return
|
||||
setNodes((nodes) => nodes.filter((n) => !targetNodes.some(el => el.id === n.id)));
|
||||
setEdges((edges) => edges.filter((ns) => !targetNodes.some(el => el.id === ns.source) && !targetNodes.some(el => el.id === ns.target)));
|
||||
}, [_reactFlowInstance, setNodes])
|
||||
|
||||
return { keyBoardPanneRef, setLastSelection }
|
||||
}
|
||||
|
||||
// TODO 离开页面保存提示
|
||||
}
|
||||
@@ -11,11 +11,10 @@ import { useMemo, useState } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import NodeLogo from "./FlowNode/NodeLogo";
|
||||
|
||||
export default function Sidebar({ dropdown = false, onInitStartNode = (node: any) => { }, onClick = (k) => { } }) {
|
||||
export default function Sidebar({ dropdown = false, disabledNodes = [], onInitStartNode = (node: any) => { }, onClick = (k) => { } }) {
|
||||
const { data: tempData, refetch } = useQuery({
|
||||
queryKey: "QueryWorkFlowTempKey",
|
||||
queryFn: () => getWorkflowNodeTemplate(),
|
||||
cacheTime: 0
|
||||
queryFn: () => getWorkflowNodeTemplate()
|
||||
});
|
||||
|
||||
const getNodeDataByTemp = (temp) => {
|
||||
@@ -30,16 +29,19 @@ export default function Sidebar({ dropdown = false, onInitStartNode = (node: any
|
||||
const nodeTemps = useMemo(() => {
|
||||
if (!tempData) return []
|
||||
return tempData.reduce((list, temp) => {
|
||||
if (disabledNodes.includes(temp.type)) return list
|
||||
const newNode = getNodeDataByTemp(temp)
|
||||
temp.type === 'start' ? onInitStartNode(cloneDeep(temp)) : list.push(newNode)
|
||||
return list
|
||||
}, [])
|
||||
}, [tempData])
|
||||
}, [tempData, disabledNodes])
|
||||
|
||||
|
||||
// tool
|
||||
const { data: toolTempData } = useQuery({
|
||||
queryKey: "QueryToolsKey",
|
||||
queryFn: () => getAssistantToolsApi('all')
|
||||
queryFn: () => getAssistantToolsApi('all'),
|
||||
cacheTime: 0
|
||||
});
|
||||
|
||||
const toolTemps = useMemo(() => {
|
||||
@@ -112,7 +114,7 @@ export default function Sidebar({ dropdown = false, onInitStartNode = (node: any
|
||||
)[0]
|
||||
);
|
||||
}}
|
||||
onClick={() => dropdown && onClick(item.type)}
|
||||
onClick={() => dropdown && onClick({ type: item.type, node: tempData.find(tmp => tmp.type === item.type) })}
|
||||
>
|
||||
{item.icon}
|
||||
<span className="text-sm">{item.name}</span>
|
||||
@@ -159,7 +161,15 @@ export default function Sidebar({ dropdown = false, onInitStartNode = (node: any
|
||||
)[0]
|
||||
);
|
||||
}}
|
||||
// onClick={() => dropdown && onClick(el.type)}
|
||||
onClick={() => {
|
||||
if (!dropdown) return
|
||||
let node = null
|
||||
toolTemps.some((temp) => {
|
||||
node = temp.children.find(node => node.tool_key === el.tool_key)
|
||||
return node
|
||||
})
|
||||
onClick({ type: el.type, node })
|
||||
}}
|
||||
>
|
||||
<NodeLogo type="tool" colorStr={el.name} />
|
||||
<span className="text-sm truncate">{el.name}</span>
|
||||
|
||||
@@ -9,6 +9,7 @@ type State = {
|
||||
type Actions = {
|
||||
setFlow: (flowid: WorkFlow) => void;
|
||||
uploadFlow: (file?: File) => void;
|
||||
setFitView: () => void;
|
||||
// updateNode: (id: string, data: any) => void;
|
||||
}
|
||||
|
||||
@@ -17,6 +18,7 @@ const useFlowStore = create<State & Actions & { notifications: Notification[]; a
|
||||
fitView: false,
|
||||
notifications: [], // 消息队列
|
||||
setFlow: (newFlow) => set({ flow: newFlow }),
|
||||
setFitView: () => set((state) => ({ fitView: !state.fitView })),
|
||||
uploadFlow(file?: File) {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getFlowApi } from "@/controllers/API/flow";
|
||||
import { useEffect } from "react";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import Panne from "./Panne";
|
||||
import useFlowStore from "./flowStore";
|
||||
@@ -16,16 +17,6 @@ export default function FlowPage() {
|
||||
// }
|
||||
// }, [])
|
||||
|
||||
// const [copyFlow, preFlow] = useMemo(() => {
|
||||
// if (flow?.id === id) {
|
||||
// const copyFlow = cloneDeep(flow)
|
||||
// return [copyFlow, JSON.stringify(copyFlow?.data || null)] as const
|
||||
// }
|
||||
// return []
|
||||
// }, [flow, id])
|
||||
// const { user } = useContext(userContext);
|
||||
|
||||
|
||||
const { flow, setFlow } = useFlowStore()
|
||||
|
||||
useEffect(() => {
|
||||
@@ -52,18 +43,20 @@ export default function FlowPage() {
|
||||
version_list: []
|
||||
})
|
||||
})
|
||||
// const str = localStorage.getItem('flow_tmp')
|
||||
// let f = str ? JSON.parse(str) : flow
|
||||
// if ('workflow_test' === user.user_name) {
|
||||
// f = test
|
||||
// }
|
||||
// setFlow(f)
|
||||
// return f
|
||||
return () => setFlow(null)
|
||||
}, [])
|
||||
|
||||
const [copyFlow, preFlow] = useMemo(() => {
|
||||
if (flow?.id === id) {
|
||||
const copyFlow = cloneDeep(flow)
|
||||
return [copyFlow, JSON.stringify(copyFlow || null)] as const
|
||||
}
|
||||
return []
|
||||
}, [flow, id])
|
||||
|
||||
return (
|
||||
<div className="flow-page-positioning">
|
||||
{flow && <Panne flow={flow} />}
|
||||
{flow && <Panne flow={copyFlow} preFlow={preFlow} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ export default function HomePage({ onSelect }) {
|
||||
type="sheet"
|
||||
icon={flow.flow_type === 'flow' ? SkillIcon : flow.flow_type === 'assistant' ? AssistantIcon : FlowIcon}
|
||||
footer={
|
||||
<Badge className={`absolute right-0 bottom-0 rounded-none rounded-br-md ${flow.flow_type === 'flow' && 'bg-gray-950'} ${flow.flow_type === 'assistant' && 'bg-blue-600'}`}>
|
||||
<Badge className={`absolute right-0 bottom-0 rounded-none rounded-br-md ${flow.flow_type === 'flow' && 'bg-gray-950'} ${flow.flow_type === 'assistant' && 'bg-[#fdb136]'}`}>
|
||||
{typeCnNames[flow.flow_type]}
|
||||
</Badge>
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export default function ChatPanne({ customWsHost = '', appendHistory = false, da
|
||||
const { assistantState, loadAssistantState, destroy } = useAssistantStore()
|
||||
// console.log('data :>> ', flow);
|
||||
const build = useBuild()
|
||||
const { messages, loadHistoryMsg, loadMoreHistoryMsg, changeChatId, clearMsgs } = useMessageStore() // TODO del
|
||||
const { messages, loadHistoryMsg, loadMoreHistoryMsg, changeChatId, clearMsgs } = useMessageStore()
|
||||
const { loadHistoryMsg: loadFlowHistoryMsg } = useFlowMessageStore()
|
||||
|
||||
useEffect(() => {
|
||||
@@ -72,7 +72,6 @@ export default function ChatPanne({ customWsHost = '', appendHistory = false, da
|
||||
lastMsg: '本轮会话已结束'
|
||||
}).then(res =>
|
||||
{
|
||||
console.log('1234 :>> ', 1234);
|
||||
setAutoRun(!res.length)
|
||||
}
|
||||
// setAutoRun()
|
||||
|
||||
@@ -16,6 +16,47 @@ import { generateUUID } from "../../utils";
|
||||
import HomePage from "./components/ChatHome";
|
||||
import ChatPanne from "./components/ChatPanne";
|
||||
|
||||
const ChatItem = ({ chat, chatId, location, handleSelectChat, handleDeleteChat }) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
key={chat.chat_id}
|
||||
className={`group item w-full rounded-lg mt-2 p-4 relative hover:bg-[#EDEFF6] cursor-pointer dark:hover:bg-[#34353A] ${location
|
||||
? 'bg-[#f9f9fc] dark:bg-[#212122]'
|
||||
: (chatId === chat.chat_id
|
||||
? 'bg-[#EDEFF6] dark:bg-[#34353A]'
|
||||
: 'bg-[#f9f9fc] dark:bg-[#212122]')}`}
|
||||
onClick={() => handleSelectChat(chat)}
|
||||
>
|
||||
<div className="flex place-items-center space-x-3">
|
||||
<div className="inline-block bg-purple-500 rounded-md">
|
||||
<TitleLogo
|
||||
url={chat.logo}
|
||||
id={chat.flow_id}
|
||||
>
|
||||
{chat.flow_type === 'assistant' ? <AssistantIcon /> : <SkillIcon />}
|
||||
</TitleLogo>
|
||||
</div>
|
||||
<p className="truncate text-sm font-bold leading-6">{chat.flow_name}</p>
|
||||
</div>
|
||||
<span className="block text-xs text-gray-600 dark:text-[#8D8D8E] mt-3 break-words truncate">
|
||||
{chat.latest_message?.message || ''}
|
||||
</span>
|
||||
<div className="mt-6">
|
||||
<span className="text-gray-400 text-xs absolute bottom-2 left-4">
|
||||
{formatStrTime(chat.update_time, 'MM 月 dd 日')}
|
||||
</span>
|
||||
<Trash2
|
||||
size={14}
|
||||
className="absolute bottom-2 right-2 text-gray-400 hidden group-hover:block"
|
||||
onClick={(e) => handleDeleteChat(e, chat.chat_id)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default function SkillChatPage() {
|
||||
const { t } = useTranslation()
|
||||
const [selectChat, setSelelctChat] = useState<any>({
|
||||
@@ -95,30 +136,14 @@ export default function SkillChatPage() {
|
||||
<div ref={chatsRef} className="scroll h-full overflow-y-scroll no-scrollbar p-2 pt-14">
|
||||
{
|
||||
chatList.map((chat, i) => (
|
||||
<div key={chat.chat_id}
|
||||
className={`group item w-full rounded-lg mt-2 p-4 relative hover:bg-[#EDEFF6] cursor-pointer dark:hover:bg-[#34353A] ${location
|
||||
? 'bg-[#f9f9fc] dark:bg-[#212122]'
|
||||
: (chatId === chat.chat_id
|
||||
? 'bg-[#EDEFF6] dark:bg-[#34353A]'
|
||||
: 'bg-[#f9f9fc] dark:bg-[#212122]')}`}
|
||||
onClick={() => handleSelectChat(chat)}>
|
||||
<div className="flex place-items-center space-x-3">
|
||||
<div className=" inline-block bg-purple-500 rounded-md">
|
||||
<TitleLogo
|
||||
url={chat.logo}
|
||||
id={chat.flow_id}
|
||||
>
|
||||
{chat.flow_type === 'assistant' ? <AssistantIcon /> : <SkillIcon />}
|
||||
</TitleLogo>
|
||||
</div>
|
||||
<p className="truncate text-sm font-bold leading-6">{chat.flow_name}</p>
|
||||
</div>
|
||||
<span className="block text-xs text-gray-600 dark:text-[#8D8D8E] mt-3 break-words truncate">{chat.latest_message?.message || ''}</span>
|
||||
<div className="mt-6">
|
||||
<span className="text-gray-400 text-xs absolute bottom-2 left-4">{formatStrTime(chat.update_time, 'MM 月 dd 日')}</span>
|
||||
<Trash2 size={14} className="absolute bottom-2 right-2 text-gray-400 hidden group-hover:block" onClick={(e) => handleDeleteChat(e, chat.chat_id)}></Trash2>
|
||||
</div>
|
||||
</div>
|
||||
<ChatItem
|
||||
key={chat.chat_id}
|
||||
chat={chat}
|
||||
chatId={chatId}
|
||||
location={location}
|
||||
handleSelectChat={handleSelectChat}
|
||||
handleDeleteChat={handleDeleteChat}
|
||||
/>
|
||||
))
|
||||
}
|
||||
<LoadMore onScrollLoad={onScrollLoad} />
|
||||
|
||||
@@ -36,7 +36,7 @@ export function transformEvent(event: string): string {
|
||||
export function transformObjectType(object: string): string {
|
||||
switch(object) {
|
||||
case 'none': return '无'
|
||||
case 'workflow': return '工作流' // TODO 确认名称
|
||||
case 'workflow': return '工作流'
|
||||
case 'flow': return '技能'
|
||||
case 'assistant': return '助手'
|
||||
case 'knowledge': return '知识库'
|
||||
|
||||
@@ -14,8 +14,8 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../../
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Input } from "../../../components/bs-ui/input";
|
||||
import { Label } from "../../../components/bs-ui/label";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../../../components/bs-ui/table";
|
||||
import { RadioGroup, RadioGroupItem } from "../../../components/bs-ui/radio-group";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../../../components/bs-ui/table";
|
||||
import { alertContext } from "../../../contexts/alertContext";
|
||||
import { getFTServicesApi, getServicesApi } from "../../../controllers/API";
|
||||
import { createTaskApi } from "../../../controllers/API/finetune";
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/bs-ui/tabs";
|
||||
import ShadTooltip from "@/components/ShadTooltipComponent";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/bs-ui/tabs";
|
||||
import AssisModel from "./tabs/AssisModel";
|
||||
import EvaluationModel from "./tabs/EvaluationModel";
|
||||
import KnowledgeModle from "./tabs/KnowledgeModel";
|
||||
import { useMemo } from "react";
|
||||
import WorkflowModel from "./tabs/WorkflowModel";
|
||||
|
||||
export default function SystemModelConfig({ data, onBack }) {
|
||||
const { t } = useTranslation('model')
|
||||
@@ -49,6 +50,7 @@ export default function SystemModelConfig({ data, onBack }) {
|
||||
<TabsTrigger value="knowledge" className="w-[150px]">{t('model.knowledgeBaseModel')}</TabsTrigger>
|
||||
<TabsTrigger value="assis" className="w-[150px]">{t('model.assistantModel')}</TabsTrigger>
|
||||
<TabsTrigger value="evaluation" className="w-[150px]">{t('model.evaluationModel')}</TabsTrigger>
|
||||
<TabsTrigger value="workflow" className="w-[150px]">工作流模型</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="knowledge">
|
||||
<KnowledgeModle llmOptions={llmOptions} embeddings={embeddings} onBack={onBack}></KnowledgeModle>
|
||||
@@ -59,6 +61,9 @@ export default function SystemModelConfig({ data, onBack }) {
|
||||
<TabsContent value="evaluation">
|
||||
<EvaluationModel llmOptions={llmOptions} onBack={onBack}></EvaluationModel>
|
||||
</TabsContent>
|
||||
<TabsContent value="workflow">
|
||||
<WorkflowModel llmOptions={llmOptions} onBack={onBack}></WorkflowModel>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Button } from "@/components/bs-ui/button";
|
||||
import { Label } from "@/components/bs-ui/label";
|
||||
import { useToast } from "@/components/bs-ui/toast/use-toast";
|
||||
import { getEvaluationModelConfig, updateEvaluationModelConfig } from "@/controllers/API/finetune";
|
||||
import { captureAndAlertRequestErrorHoc } from "@/controllers/request";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ModelSelect } from "./KnowledgeModel";
|
||||
import { LoadingIcon } from "@/components/bs-icons/loading";
|
||||
|
||||
export default function WorkflowModel({ llmOptions, onBack }) {
|
||||
const { t } = useTranslation('model')
|
||||
const [selectedModel, setSelectedModel] = useState(null);
|
||||
const [loading, setLoading] = useState(true)
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
// TODO 获取默认值
|
||||
getEvaluationModelConfig().then(res => {
|
||||
setSelectedModel(res.model_id)
|
||||
setLoading(false)
|
||||
})
|
||||
}, []);
|
||||
|
||||
const { message } = useToast()
|
||||
const handleSave = () => {
|
||||
if (!selectedModel) {
|
||||
return message({ variant: 'error', description: t('model.defaultEvaluationFeature') + t('bs:required') })
|
||||
}
|
||||
const data = {
|
||||
model_id: selectedModel
|
||||
};
|
||||
// TODO 更新
|
||||
captureAndAlertRequestErrorHoc(updateEvaluationModelConfig(data).then(res => {
|
||||
message({ variant: 'success', description: t('model.saveSuccess') })
|
||||
}));
|
||||
};
|
||||
|
||||
if (loading) return <div className="absolute w-full h-full top-0 left-0 flex justify-center items-center z-10 bg-[rgba(255,255,255,0.6)] dark:bg-blur-shared">
|
||||
<LoadingIcon />
|
||||
</div>
|
||||
|
||||
return (
|
||||
<div className="max-w-[520px] mx-auto">
|
||||
<div className="mt-10">
|
||||
<Label className="bisheng-label">大模型及RAG节点<span className="text-red-500 text-xs">*</span></Label>
|
||||
<ModelSelect
|
||||
label={''}
|
||||
value={selectedModel}
|
||||
options={llmOptions}
|
||||
onChange={(val) => setSelectedModel(val)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-10 text-center space-x-6">
|
||||
<Button className="px-6" variant="outline" onClick={onBack}>{t('model.cancel')}</Button>
|
||||
<Button className="px-10" onClick={handleSave}>{t('model.save')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -81,9 +81,12 @@ function FlowRadio({ limit, onChange }) {
|
||||
|
||||
function FlowControl({ groupId, type, onChange }) {
|
||||
const { t } = useTranslation()
|
||||
const { name, label, placeholder } = type === 3
|
||||
? { name: t('build.assistantName'), label: t('system.AssistantFlowCtrl'), placeholder: t('system.assistantName') }
|
||||
: { name: t('skills.skillName'), label: t('system.SkillFlowCtrl'), placeholder: t('skills.skillName') }
|
||||
const map = {
|
||||
3: { name: t('build.assistantName'), label: t('system.AssistantFlowCtrl'), placeholder: t('system.assistantName') },
|
||||
2: { name: t('skills.skillName'), label: t('system.SkillFlowCtrl'), placeholder: t('skills.skillName') },
|
||||
5: { name: '工作流名称', label: '工作流流量控制', placeholder: '工作流名称' },
|
||||
}
|
||||
const { name, label, placeholder } = map[type]
|
||||
const { page, pageSize, data, total, setPage, search, refreshData } = useTable({ pageSize: 10 }, (params) =>
|
||||
getGroupFlowsApi(params.page, params.pageSize, type, groupId, params.keyword)
|
||||
)
|
||||
@@ -167,7 +170,8 @@ export default function EditUserGroup({ data, onBeforeChange, onChange }) {
|
||||
adminUser: '',
|
||||
groupLimit: 0,
|
||||
assistant: [],
|
||||
skill: []
|
||||
skill: [],
|
||||
workFlows: []
|
||||
})
|
||||
/**
|
||||
* 用户
|
||||
@@ -257,6 +261,13 @@ export default function EditUserGroup({ data, onBeforeChange, onChange }) {
|
||||
onChange={(vals) => setForm({ ...form, skill: vals })}
|
||||
></FlowControl>
|
||||
</div>
|
||||
<div className="mt-12 mb-20">
|
||||
<FlowControl
|
||||
groupId={data.id}
|
||||
type={5}
|
||||
onChange={(vals) => setForm({ ...form, workFlows: vals })}
|
||||
></FlowControl>
|
||||
</div>
|
||||
</>}
|
||||
<div className="flex justify-center items-center absolute bottom-0 w-[630px] h-[8vh] gap-4 mt-[100px] bg-background-login">
|
||||
<Button variant="outline" className="px-16" onClick={onChange}>{t('cancel')}</Button>
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { Edge, Node, ReactFlowJsonObject, Viewport, XYPosition } from "@xyflow/react";
|
||||
import { APIClassType } from "../api/index";
|
||||
|
||||
export const enum OnlineState {
|
||||
OffLine = 1,
|
||||
OnLine = 2
|
||||
}
|
||||
|
||||
/** 流程 */
|
||||
export type FlowState = {
|
||||
edges: {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Node } from "@xyflow/react";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
// 节点名称自动命名
|
||||
export function autoNodeName(nodes: Node[], name: string): string {
|
||||
@@ -15,7 +17,7 @@ export function autoNodeName(nodes: Node[], name: string): string {
|
||||
return newName;
|
||||
}
|
||||
|
||||
// 节点初始化,node中的变量动态替换id
|
||||
// 在节点初始化时,将node中的模板变量替换为界面中对应的变量key
|
||||
export function initNode(node) {
|
||||
const { id } = node;
|
||||
|
||||
@@ -104,7 +106,7 @@ export function isVarInFlow(nodeId, nodes, varName, varNameCn) {
|
||||
})
|
||||
) : false
|
||||
)
|
||||
return res ? '' : `${nodeName}节点错误:${varNameCn}不存在`
|
||||
return res ? '' : `${nodeName}节点错误:${varNameCn}已失效,可能是相关节点已被删除或替换,请重新引用变量。`
|
||||
}
|
||||
|
||||
|
||||
@@ -168,3 +170,55 @@ export function findParallelNodes(a, b) {
|
||||
return [...new Set(result)];
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制粘贴节点
|
||||
* @param dom 事件绑定 dom
|
||||
* @param lastSelection 被复制对象
|
||||
* @param paste 粘贴时间回调,参数(克隆的lastSelection,鼠标当前坐标)
|
||||
* @param deps 依赖
|
||||
*/
|
||||
export function useCopyPasteNode(dom, lastSelection, paste, del, deps) {
|
||||
const position = useRef({ x: 0, y: 0 });
|
||||
const [lastCopiedSelection, setLastCopiedSelection] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!dom) return
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
console.log('event.target :>> ', event.target);
|
||||
if (['INPUT', 'TEXTAREA'].includes(event.target.tagName)) return // 排除输入框内复制粘贴
|
||||
if (
|
||||
event.target instanceof HTMLInputElement ||
|
||||
(event.target instanceof HTMLElement && event.target.isContentEditable)
|
||||
) return
|
||||
|
||||
if (
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.key === "c" &&
|
||||
lastSelection
|
||||
) {
|
||||
event.preventDefault();
|
||||
setLastCopiedSelection(cloneDeep(lastSelection));
|
||||
} else if (
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.key === "v" &&
|
||||
lastCopiedSelection
|
||||
) {
|
||||
event.preventDefault();
|
||||
paste(lastCopiedSelection, position.current)
|
||||
} else if (event.key === 'Delete' && lastSelection) {
|
||||
del(lastSelection)
|
||||
}
|
||||
};
|
||||
const handleMouseMove = (event) => {
|
||||
position.current = { x: event.clientX, y: event.clientY };
|
||||
};
|
||||
|
||||
dom.addEventListener("keydown", onKeyDown);
|
||||
dom.addEventListener("mousemove", handleMouseMove);
|
||||
|
||||
return () => {
|
||||
dom?.removeEventListener("keydown", onKeyDown);
|
||||
dom?.removeEventListener("mousemove", handleMouseMove);
|
||||
};
|
||||
}, [dom, lastSelection, lastCopiedSelection, ...deps]);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { copyText } from "../utils";
|
||||
import { alertContext } from "../contexts/alertContext";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
import { useReactFlow } from "@xyflow/react";
|
||||
|
||||
// 防抖
|
||||
export function useDebounce(func: any, wait: number, immediate: boolean, callback?: any,): (any?: any) => any {
|
||||
@@ -197,27 +198,29 @@ export function useCopyPaste(dom, lastSelection, paste, deps) {
|
||||
}
|
||||
|
||||
// undo redo
|
||||
export function useUndoRedo<T>(data, undoCall, redoCall, maxHistorySize = 100) {
|
||||
export function useUndoRedo<T>(maxHistorySize = 100) {
|
||||
const [past, setPast] = useState<T[]>([]); // 过去的历史记录(past)
|
||||
const [future, setFuture] = useState<T[]>([]); // 和未来的历史记录(future)
|
||||
const { setNodes, setEdges, getNodes, getEdges } = useReactFlow();
|
||||
|
||||
/**
|
||||
* 快照功能
|
||||
* 将上一次的状态保存到 past 中,并清空 future
|
||||
* if max = 2: [1,x,x] -> [x, x]
|
||||
* [x,x,new]
|
||||
*/
|
||||
const takeSnapshot = useCallback((data: T) => {
|
||||
const takeSnapshot = useCallback(() => {
|
||||
setPast((old) => {
|
||||
// let newPast = cloneDeep(old);
|
||||
const newPast = old.slice(
|
||||
old.length - maxHistorySize + 1,
|
||||
old.length
|
||||
);
|
||||
newPast.push(data);
|
||||
newPast.push({ nodes: cloneDeep(getNodes()), edges: cloneDeep(getEdges()) });
|
||||
return newPast;
|
||||
});
|
||||
setFuture([]);
|
||||
}, [setPast, setFuture]);
|
||||
}, [setPast, setFuture, getNodes, getEdges]);
|
||||
|
||||
/**
|
||||
* 撤销
|
||||
* 将状态恢复到 past 中的上一个状态,并将当前状态保存到 future 中
|
||||
@@ -237,12 +240,13 @@ export function useUndoRedo<T>(data, undoCall, redoCall, maxHistorySize = 100) {
|
||||
setFuture((old) => {
|
||||
// let newFuture = cloneDeep(old);
|
||||
let newFuture = old;
|
||||
newFuture.push(data);
|
||||
newFuture.push({ nodes: cloneDeep(getNodes()), edges: cloneDeep(getEdges()) });
|
||||
return newFuture;
|
||||
});
|
||||
undoCall(pastState)
|
||||
setNodes(pastState.nodes);
|
||||
setEdges(pastState.edges);
|
||||
}
|
||||
}, [data, past, setFuture, setPast, undoCall]);
|
||||
}, [past, setFuture, setPast]);
|
||||
/**
|
||||
* 重做
|
||||
* 将状态恢复到 future 中的下一个状态,并将当前状态保存到 past 中
|
||||
@@ -262,12 +266,13 @@ export function useUndoRedo<T>(data, undoCall, redoCall, maxHistorySize = 100) {
|
||||
setPast((old) => {
|
||||
// let newPast = cloneDeep(old);
|
||||
let newPast = old
|
||||
newPast.push(data);
|
||||
newPast.push({ nodes: cloneDeep(getNodes()), edges: cloneDeep(getEdges()) });;
|
||||
return newPast;
|
||||
});
|
||||
redoCall(futureState)
|
||||
setNodes(futureState.nodes);
|
||||
setEdges(futureState.edges);
|
||||
}
|
||||
}, [data, future, setFuture, setPast, redoCall]);
|
||||
}, [future, setFuture, setPast]);
|
||||
|
||||
// 快捷键
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user