diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json
index 04e6d5dd9..2a674f658 100644
--- a/src/frontend/package-lock.json
+++ b/src/frontend/package-lock.json
@@ -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",
diff --git a/src/frontend/package.json b/src/frontend/package.json
index df1c785a7..45806e420 100644
--- a/src/frontend/package.json
+++ b/src/frontend/package.json
@@ -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",
diff --git a/src/frontend/src/components/bs-ui/select/cascader.tsx b/src/frontend/src/components/bs-ui/select/cascader.tsx
index 551d315de..9e12aaa30 100644
--- a/src/frontend/src/components/bs-ui/select/cascader.tsx
+++ b/src/frontend/src/components/bs-ui/select/cascader.tsx
@@ -72,7 +72,7 @@ const Col = (props: {
onClick: (o: Option, isLeaf: boolean) => void
}) => {
const { options, ...opros } = props
- return
+ return
{
options.map(option =>
)
}
diff --git a/src/frontend/src/components/inputComponent/index.tsx b/src/frontend/src/components/inputComponent/index.tsx
index efc8ac94a..5ba7cdf26 100644
--- a/src/frontend/src/components/inputComponent/index.tsx
+++ b/src/frontend/src/components/inputComponent/index.tsx
@@ -9,7 +9,7 @@ export default function InputComponent({
disabled,
password,
type = 'input',
- maxLength = 100,
+ maxLength = 150,
editNode = false,
}: InputComponentType) {
const [myValue, setMyValue] = useState(value ?? "");
diff --git a/src/frontend/src/controllers/API/assistant.ts b/src/frontend/src/controllers/API/assistant.ts
index 153a33545..74e908cbd 100644
--- a/src/frontend/src/controllers/API/assistant.ts
+++ b/src/frontend/src/controllers/API/assistant.ts
@@ -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 })
}
\ No newline at end of file
diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts
index 58c33bf95..dac0f2ae5 100644
--- a/src/frontend/src/controllers/API/index.ts
+++ b/src/frontend/src/controllers/API/index.ts
@@ -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;
}())
diff --git a/src/frontend/src/controllers/API/pro.ts b/src/frontend/src/controllers/API/pro.ts
index 3d196f505..9971ee468 100644
--- a/src/frontend/src/controllers/API/pro.ts
+++ b/src/frontend/src/controllers/API/pro.ts
@@ -56,6 +56,7 @@ export const saveGroupApi = async (data: any): Promise
=> {
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 => {
admin_user_id,
group_name,
assistant,
- skill
+ skill,
+ workFlows
});
};
diff --git a/src/frontend/src/controllers/API/workflow.ts b/src/frontend/src/controllers/API/workflow.ts
index 5a51234a7..b08d9981f 100644
--- a/src/frontend/src/controllers/API/workflow.ts
+++ b/src/frontend/src/controllers/API/workflow.ts
@@ -57,6 +57,19 @@ export const onlineWorkflow = async (flow, status = ''): Promise => {
return await axios.patch(`/api/v1/workflow/update/${flow.id}`, data);
}
+/**
+ * 单节点运行
+ *
+ */
+export const runWorkflowNodeApi = async (node_input, data): Promise => {
+ 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": []
diff --git a/src/frontend/src/controllers/request.ts b/src/frontend/src/controllers/request.ts
index 11ee63d9d..9e898ed37 100644
--- a/src/frontend/src/controllers/request.ts
+++ b/src/frontend/src/controllers/request.ts
@@ -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({
diff --git a/src/frontend/src/pages/BuildPage/CreateApp.tsx b/src/frontend/src/pages/BuildPage/CreateApp.tsx
index 0ddabf250..322c19a10 100644
--- a/src/frontend/src/pages/BuildPage/CreateApp.tsx
+++ b/src/frontend/src/pages/BuildPage/CreateApp.tsx
@@ -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')}
@@ -238,7 +237,7 @@ ${t('build.exampleTwo')}
{/* 工作流安全审查 */}
- {false && appConfig.isPro &&
+ {appConfig.isPro &&
}
@@ -247,7 +246,7 @@ ${t('build.exampleTwo')}
{t('cancel')}
-
-
diff --git a/src/frontend/src/pages/BuildPage/flow/FlowChat/InputForm.tsx b/src/frontend/src/pages/BuildPage/flow/FlowChat/InputForm.tsx
index 96e72bd3f..d185487c8 100644
--- a/src/frontend/src/pages/BuildPage/flow/FlowChat/InputForm.tsx
+++ b/src/frontend/src/pages/BuildPage/flow/FlowChat/InputForm.tsx
@@ -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
{
data.value.map((item, i) => (
-
+
+ {item.required &&
*}
{item.value}
{/*
{item.required ? " *" : ""} */}
-
+
{(() => {
switch (item.type) {
case FormItemType.Text:
diff --git a/src/frontend/src/pages/BuildPage/flow/FlowChat/messageStore.ts b/src/frontend/src/pages/BuildPage/flow/FlowChat/messageStore.ts
index 9b26eeee4..e8a9c237b 100644
--- a/src/frontend/src/pages/BuildPage/flow/FlowChat/messageStore.ts
+++ b/src/frontend/src/pages/BuildPage/flow/FlowChat/messageStore.ts
@@ -170,7 +170,6 @@ export const useMessageStore = create
((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()
diff --git a/src/frontend/src/pages/BuildPage/flow/FlowEdge.tsx b/src/frontend/src/pages/BuildPage/flow/FlowEdge.tsx
index 45eb8dbc9..a9fa49c92 100644
--- a/src/frontend/src/pages/BuildPage/flow/FlowEdge.tsx
+++ b/src/frontend/src/pages/BuildPage/flow/FlowEdge.tsx
@@ -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
{/* 在连线中间添加一个加号按钮 */}
- {/*
+ {selected &&
-
onButtonClick(id)}>
+ onButtonClick(id)}
+ >
- {isDropdownOpen &&
-
- handleOptionClick(key)}>
-
- }
+ {isDropdownOpen && (
+
+
+ handleOptionClick(flownodedata)}
+ >
+
+
+ )}
- */}
+
+ }
>
);
-};
\ No newline at end of file
+}
diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/EditText.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/EditText.tsx
index 6e9dbdd46..8588e4df9 100644
--- a/src/frontend/src/pages/BuildPage/flow/FlowNode/EditText.tsx
+++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/EditText.tsx
@@ -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)
diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/NodeToolbarComponent.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/NodeToolbarComponent.tsx
index 2270081f6..793c6be15 100644
--- a/src/frontend/src/pages/BuildPage/flow/FlowNode/NodeToolbarComponent.tsx
+++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/NodeToolbarComponent.tsx
@@ -22,9 +22,9 @@ export default function NodeToolbarComponent({ nodeId, type, onRun }) {
return
{
- // ["agent", "tool", "llm", "rag", "qa_retriever", "code"].includes(type) &&
- //
- //
+ ["agent", "tool", "llm", "rag", "qa_retriever", "code"].includes(type) &&
+
+
}
void
onStatusChange: (key: string, obj: any) => void
@@ -65,7 +66,7 @@ export default function Parameter({ nodeId, item, onOutPutChange, onStatusChange
case 'var_textarea_file':
return
case 'output_form':
- return
+ return
case 'bisheng_model':
return
case 'agent_model':
@@ -77,9 +78,20 @@ export default function Parameter({ nodeId, item, onOutPutChange, onStatusChange
case 'switch':
return ;
case 'var_select':
- return ;
+ return ;
case 'user_question':
- return ;
+ return ;
case 'knowledge_select_multi':
return ;
case 'qa_select_multi':
@@ -95,7 +107,7 @@ export default function Parameter({ nodeId, item, onOutPutChange, onStatusChange
case 'add_tool':
return ;
case 'condition':
- return ;
+ return ;
case 'report':
return ;
case 'sql_config':
diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/ParameterGroup.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/ParameterGroup.tsx
index 648f5ab5d..5b77eba39 100644
--- a/src/frontend/src/pages/BuildPage/flow/FlowNode/ParameterGroup.tsx
+++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/ParameterGroup.tsx
@@ -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 && {cate.name}
}
{cate.params.map(item => tab === item.tab || !item.tab ? {
export default function RunLog({ node, children }) {
const [state, setState] = useState(Status.normal)
- const [data, setData] = useState([])
+ const [data, setData] = useState({})
// 订阅日志事件
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 () => {
diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/RunTest.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/RunTest.tsx
index fefcc3cf6..c17918b48 100644
--- a/src/frontend/src/pages/BuildPage/flow/FlowNode/RunTest.tsx
+++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/RunTest.tsx
@@ -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 }) => {
{title}
{copyed ? : }
-
+
}
@@ -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) => {
运行
{results.length !== 0 &&
运行结果
}
- {results.map(res =>
)}
+ {results.map(res =>
)}
diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/ConditionItem.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/ConditionItem.tsx
index 344a374a6..cff98eb20 100644
--- a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/ConditionItem.tsx
+++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/ConditionItem.tsx
@@ -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 }) {
handleAddItem(val.id)} variant="outline" className="border-primary text-primary mt-2 h-8">
+ 添加条件
-
+
)
}
+ 添加分支
diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/DragOptions.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/DragOptions.tsx
index bd0add9e0..85cb760ed 100644
--- a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/DragOptions.tsx
+++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/DragOptions.tsx
@@ -99,7 +99,7 @@ export default function DragOptions({ edges = false, scroll = false, options, on
};
return (
-
+
{(provided) => (
diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/InputFormItem.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/InputFormItem.tsx
index 972bbbae1..a159a19ec 100644
--- a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/InputFormItem.tsx
+++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/InputFormItem.tsx
@@ -187,6 +187,7 @@ function Form({ initialData, onSubmit, onCancel, existingOptions }) {
diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/KnowledgeSelectItem.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/KnowledgeSelectItem.tsx
index 379033f7b..6110b79b3 100644
--- a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/KnowledgeSelectItem.tsx
+++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/KnowledgeSelectItem.tsx
@@ -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}`
})
})
diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/ModelItem.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/ModelItem.tsx
index fbf901db9..cefd34c25 100644
--- a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/ModelItem.tsx
+++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/ModelItem.tsx
@@ -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 = []
diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/OutputItem.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/OutputItem.tsx
index f9afde379..022bfa2d8 100644
--- a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/OutputItem.tsx
+++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/OutputItem.tsx
@@ -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
(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 }) => {
{renderContent()}
{error &&
选项不可为空
}
- {interactionType !== 'choose' &&
}
+ {interactionType !== 'choose' &&
}
);
diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/ReportWordEdit.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/ReportWordEdit.tsx
index 7d70dda16..62fa772d3 100644
--- a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/ReportWordEdit.tsx
+++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/ReportWordEdit.tsx
@@ -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
+
+
+
// new
if (!docx.path) return
@@ -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
diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/SelectVar.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/SelectVar.tsx
index c24f2885e..7fec985e3 100644
--- a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/SelectVar.tsx
+++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/SelectVar.tsx
@@ -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
)}
}
{
- !!questions.length &&
+ !!questions.length &&
{questions.map(q =>
{
- onSelect(currentMenuRef.current, q)
- setOpen(false)
+ onSelect(currentMenuRef.current, q, inputOpenRef.current)
+ !multip && setOpen(false)
}}>
- {q.label}
+ {q.label}
+ {value.includes(`${currentMenuRef.current.id}.${q.value}`) && }
)}
diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/SliderItem.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/SliderItem.tsx
index c88e1f69c..338209202 100644
--- a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/SliderItem.tsx
+++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/SliderItem.tsx
@@ -41,11 +41,11 @@ export const SwitchSliderItem = ({ data, onChange }) => {
{data.label}
{data.help &&
}
-
{
+ {/* {
const newValue = { ...value, flag: v }
setValue(newValue)
onChange(newValue)
- }} />
+ }} /> */}
- {tool.name}
+ {tool.name}
{
- console.log('1234 :>> ', 1234);
const msg = textMsgRef.current
// console.log('positon :>> ', cursorPositionRef.current, msg);
const beforeCursor = msg.slice(0, cursorPositionRef.current);
diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/VarInput.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/VarInput.tsx
index 69a9fd6de..c05614d5f 100644
--- a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/VarInput.tsx
+++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/VarInput.tsx
@@ -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>/g, // 匹配所有 标签
(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,否则保持原内容
}
);
// 遍历子节点,将
转换为 \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 = `${label}`;
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"
>
{children}
0) return
diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/VarSelectItem.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/VarSelectItem.tsx
index 7687b822a..c48892e29 100644
--- a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/VarSelectItem.tsx
+++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/VarSelectItem.tsx
@@ -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
([])
+ 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
{data.key}
-
+
span]:line-clamp-1 data-[placeholder]:text-gray-400`}>
- {value.length ? value.map(item =>
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 => 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]}
handleDelete(item)}>
@@ -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
{/* body */}
-
+
{node.tab &&
{
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 =>
paramValidateEntities.current[key] = obj)}
@@ -150,20 +251,12 @@ function CustomNode({ data: node, selected, isConnectable }: { data: WorkflowNod
{/* footer */}
- {node.type !== 'start' &&
}
- {!['condition', 'output', 'end'].includes(node.type) &&
}
+ {
+ node.type !== 'start' &&
+ }
+ {
+ !['condition', 'output', 'end'].includes(node.type) &&
+ }
diff --git a/src/frontend/src/pages/BuildPage/flow/Header.tsx b/src/frontend/src/pages/BuildPage/flow/Header.tsx
index 93d3d00dd..465c88a38 100644
--- a/src/frontend/src/pages/BuildPage/flow/Header.tsx
+++ b/src/frontend/src/pages/BuildPage/flow/Header.tsx
@@ -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 (
{
@@ -210,7 +237,7 @@ const Header = ({ flow, onTabChange }) => {
- {/*
+
{ setTabType('edit'); onTabChange('edit') }}
>
@@ -220,7 +247,7 @@ const Header = ({ flow, onTabChange }) => {
onClick={() => { setTabType('api'); onTabChange('api') }}>
对外发布
-
*/}
+
{/* Right Section with Options */}
@@ -232,8 +259,7 @@ const Header = ({ flow, onTabChange }) => {
保存
{
- // version &&
{
dropDown={(
{
- 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')}
}
-
+ {isOnlineVersion ?
+ 下线
+ :
上线
-
+ }
@@ -312,6 +346,45 @@ const Header = ({ flow, onTabChange }) => {
setFlow({ ...f, ...base })
onlineWorkflow(f)
}} />
+ {/* 上线不可修改提示 */}
+
+
+ {/* 离开并保存提示 */}
+
);
};
@@ -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;
diff --git a/src/frontend/src/pages/BuildPage/flow/Panne.tsx b/src/frontend/src/pages/BuildPage/flow/Panne.tsx
index c0b18a21b..1d286100a 100644
--- a/src/frontend/src/pages/BuildPage/flow/Panne.tsx
+++ b/src/frontend/src/pages/BuildPage/flow/Panne.tsx
@@ -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
-
setShowApiPage('api' === type)}>
+
setShowApiPage('api' === type)} preFlow={preFlow}>
{
// 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) => (
{ 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 }) {
};
-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(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 离开页面保存提示
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/frontend/src/pages/BuildPage/flow/Sidebar.tsx b/src/frontend/src/pages/BuildPage/flow/Sidebar.tsx
index d3c7a77be..ab92c3542 100644
--- a/src/frontend/src/pages/BuildPage/flow/Sidebar.tsx
+++ b/src/frontend/src/pages/BuildPage/flow/Sidebar.tsx
@@ -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}
{item.name}
@@ -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 })
+ }}
>
{el.name}
diff --git a/src/frontend/src/pages/BuildPage/flow/flowStore.ts b/src/frontend/src/pages/BuildPage/flow/flowStore.ts
index 9cd87528c..0526c514f 100644
--- a/src/frontend/src/pages/BuildPage/flow/flowStore.ts
+++ b/src/frontend/src/pages/BuildPage/flow/flowStore.ts
@@ -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 set({ flow: newFlow }),
+ setFitView: () => set((state) => ({ fitView: !state.fitView })),
uploadFlow(file?: File) {
const input = document.createElement("input");
input.type = "file";
diff --git a/src/frontend/src/pages/BuildPage/flow/index.tsx b/src/frontend/src/pages/BuildPage/flow/index.tsx
index 1ca296ebd..dcc9a762e 100644
--- a/src/frontend/src/pages/BuildPage/flow/index.tsx
+++ b/src/frontend/src/pages/BuildPage/flow/index.tsx
@@ -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 (
- {flow &&
}
+ {flow &&
}
);
}
diff --git a/src/frontend/src/pages/ChatAppPage/components/ChatHome.tsx b/src/frontend/src/pages/ChatAppPage/components/ChatHome.tsx
index e57ec782c..e9cb616b8 100644
--- a/src/frontend/src/pages/ChatAppPage/components/ChatHome.tsx
+++ b/src/frontend/src/pages/ChatAppPage/components/ChatHome.tsx
@@ -119,7 +119,7 @@ export default function HomePage({ onSelect }) {
type="sheet"
icon={flow.flow_type === 'flow' ? SkillIcon : flow.flow_type === 'assistant' ? AssistantIcon : FlowIcon}
footer={
-
+
{typeCnNames[flow.flow_type]}
}
diff --git a/src/frontend/src/pages/ChatAppPage/components/ChatPanne.tsx b/src/frontend/src/pages/ChatAppPage/components/ChatPanne.tsx
index 8cefca209..5657ace57 100644
--- a/src/frontend/src/pages/ChatAppPage/components/ChatPanne.tsx
+++ b/src/frontend/src/pages/ChatAppPage/components/ChatPanne.tsx
@@ -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()
diff --git a/src/frontend/src/pages/ChatAppPage/index.tsx b/src/frontend/src/pages/ChatAppPage/index.tsx
index 3d6a7ca71..8ee158cb5 100755
--- a/src/frontend/src/pages/ChatAppPage/index.tsx
+++ b/src/frontend/src/pages/ChatAppPage/index.tsx
@@ -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 (
+ handleSelectChat(chat)}
+ >
+
+
+
+ {chat.flow_type === 'assistant' ? : }
+
+
+
{chat.flow_name}
+
+
+ {chat.latest_message?.message || ''}
+
+
+
+ {formatStrTime(chat.update_time, 'MM 月 dd 日')}
+
+ handleDeleteChat(e, chat.chat_id)}
+ />
+
+
+ );
+};
+
+
export default function SkillChatPage() {
const { t } = useTranslation()
const [selectChat, setSelelctChat] = useState({
@@ -95,30 +136,14 @@ export default function SkillChatPage() {
{
chatList.map((chat, i) => (
-
handleSelectChat(chat)}>
-
-
-
- {chat.flow_type === 'assistant' ? : }
-
-
-
{chat.flow_name}
-
-
{chat.latest_message?.message || ''}
-
- {formatStrTime(chat.update_time, 'MM 月 dd 日')}
- handleDeleteChat(e, chat.chat_id)}>
-
-
+
))
}
diff --git a/src/frontend/src/pages/LogPage/utils/index.ts b/src/frontend/src/pages/LogPage/utils/index.ts
index 76e4172c0..4b0769581 100644
--- a/src/frontend/src/pages/LogPage/utils/index.ts
+++ b/src/frontend/src/pages/LogPage/utils/index.ts
@@ -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 '知识库'
diff --git a/src/frontend/src/pages/ModelPage/components/CreateTask.tsx b/src/frontend/src/pages/ModelPage/components/CreateTask.tsx
index c63b76bca..549982a02 100644
--- a/src/frontend/src/pages/ModelPage/components/CreateTask.tsx
+++ b/src/frontend/src/pages/ModelPage/components/CreateTask.tsx
@@ -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";
diff --git a/src/frontend/src/pages/ModelPage/manage/SystemModelConfig.tsx b/src/frontend/src/pages/ModelPage/manage/SystemModelConfig.tsx
index d2f9bd1cd..095dd61d9 100644
--- a/src/frontend/src/pages/ModelPage/manage/SystemModelConfig.tsx
+++ b/src/frontend/src/pages/ModelPage/manage/SystemModelConfig.tsx
@@ -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 }) {
{t('model.knowledgeBaseModel')}
{t('model.assistantModel')}
{t('model.evaluationModel')}
+
工作流模型
@@ -59,6 +61,9 @@ export default function SystemModelConfig({ data, onBack }) {
+
+
+
diff --git a/src/frontend/src/pages/ModelPage/manage/tabs/WorkflowModel.tsx b/src/frontend/src/pages/ModelPage/manage/tabs/WorkflowModel.tsx
new file mode 100644
index 000000000..55a78885c
--- /dev/null
+++ b/src/frontend/src/pages/ModelPage/manage/tabs/WorkflowModel.tsx
@@ -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
+
+
+
+ return (
+
+
+ 大模型及RAG节点*
+ setSelectedModel(val)}
+ />
+
+
+ {t('model.cancel')}
+ {t('model.save')}
+
+
+ );
+}
diff --git a/src/frontend/src/pages/SystemPage/components/EditUserGroup.tsx b/src/frontend/src/pages/SystemPage/components/EditUserGroup.tsx
index f10fd8bb0..56869e463 100644
--- a/src/frontend/src/pages/SystemPage/components/EditUserGroup.tsx
+++ b/src/frontend/src/pages/SystemPage/components/EditUserGroup.tsx
@@ -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 })}
>
+
+ setForm({ ...form, workFlows: vals })}
+ >
+
>}
{t('cancel')}
diff --git a/src/frontend/src/types/flow/index.ts b/src/frontend/src/types/flow/index.ts
index 713754e51..0576b1108 100644
--- a/src/frontend/src/types/flow/index.ts
+++ b/src/frontend/src/types/flow/index.ts
@@ -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: {
diff --git a/src/frontend/src/util/flowUtils.ts b/src/frontend/src/util/flowUtils.ts
index 8b52fd4dc..18c3ae160 100644
--- a/src/frontend/src/util/flowUtils.ts
+++ b/src/frontend/src/util/flowUtils.ts
@@ -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]);
+}
\ No newline at end of file
diff --git a/src/frontend/src/util/hook.ts b/src/frontend/src/util/hook.ts
index f1a62477d..bc7f992c0 100644
--- a/src/frontend/src/util/hook.ts
+++ b/src/frontend/src/util/hook.ts
@@ -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(data, undoCall, redoCall, maxHistorySize = 100) {
+export function useUndoRedo(maxHistorySize = 100) {
const [past, setPast] = useState([]); // 过去的历史记录(past)
const [future, setFuture] = useState([]); // 和未来的历史记录(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(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(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(() => {