### What problem does this PR solve? feat: add custom edge feat: add flow card feat: add store for canvas #918 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.8.0
| "@ant-design/pro-components": "^2.6.46", | "@ant-design/pro-components": "^2.6.46", | ||||
| "@ant-design/pro-layout": "^7.17.16", | "@ant-design/pro-layout": "^7.17.16", | ||||
| "@js-preview/excel": "^1.7.8", | "@js-preview/excel": "^1.7.8", | ||||
| "@tanstack/react-query": "^5.40.0", | |||||
| "ahooks": "^3.7.10", | "ahooks": "^3.7.10", | ||||
| "antd": "^5.12.7", | "antd": "^5.12.7", | ||||
| "axios": "^1.6.3", | "axios": "^1.6.3", | ||||
| "umi": "^4.0.90", | "umi": "^4.0.90", | ||||
| "umi-request": "^1.4.0", | "umi-request": "^1.4.0", | ||||
| "unist-util-visit-parents": "^6.0.1", | "unist-util-visit-parents": "^6.0.1", | ||||
| "uuid": "^9.0.1" | |||||
| "uuid": "^9.0.1", | |||||
| "zustand": "^4.5.2" | |||||
| }, | }, | ||||
| "devDependencies": { | "devDependencies": { | ||||
| "@react-dev-inspector/umi4-plugin": "^2.0.1", | "@react-dev-inspector/umi4-plugin": "^2.0.1", | ||||
| "@redux-devtools/extension": "^3.3.0", | |||||
| "@testing-library/jest-dom": "^6.4.5", | "@testing-library/jest-dom": "^6.4.5", | ||||
| "@testing-library/react": "^15.0.7", | "@testing-library/react": "^15.0.7", | ||||
| "@types/dagre": "^0.7.52", | "@types/dagre": "^0.7.52", | ||||
| "react-dom": ">=17" | "react-dom": ">=17" | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/@reactflow/background/node_modules/immer": { | |||||
| "version": "10.1.1", | |||||
| "resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz", | |||||
| "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", | |||||
| "optional": true, | |||||
| "peer": true | |||||
| }, | |||||
| "node_modules/@reactflow/background/node_modules/zustand": { | |||||
| "version": "4.5.2", | |||||
| "resolved": "https://registry.npmmirror.com/zustand/-/zustand-4.5.2.tgz", | |||||
| "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==", | |||||
| "dependencies": { | |||||
| "use-sync-external-store": "1.2.0" | |||||
| }, | |||||
| "engines": { | |||||
| "node": ">=12.7.0" | |||||
| }, | |||||
| "peerDependencies": { | |||||
| "@types/react": ">=16.8", | |||||
| "immer": ">=9.0.6", | |||||
| "react": ">=16.8" | |||||
| }, | |||||
| "peerDependenciesMeta": { | |||||
| "@types/react": { | |||||
| "optional": true | |||||
| }, | |||||
| "immer": { | |||||
| "optional": true | |||||
| }, | |||||
| "react": { | |||||
| "optional": true | |||||
| } | |||||
| } | |||||
| }, | |||||
| "node_modules/@reactflow/controls": { | "node_modules/@reactflow/controls": { | ||||
| "version": "11.2.12", | "version": "11.2.12", | ||||
| "resolved": "https://registry.npmmirror.com/@reactflow/controls/-/controls-11.2.12.tgz", | "resolved": "https://registry.npmmirror.com/@reactflow/controls/-/controls-11.2.12.tgz", | ||||
| "react-dom": ">=17" | "react-dom": ">=17" | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/@reactflow/controls/node_modules/immer": { | |||||
| "version": "10.1.1", | |||||
| "resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz", | |||||
| "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", | |||||
| "optional": true, | |||||
| "peer": true | |||||
| }, | |||||
| "node_modules/@reactflow/controls/node_modules/zustand": { | |||||
| "version": "4.5.2", | |||||
| "resolved": "https://registry.npmmirror.com/zustand/-/zustand-4.5.2.tgz", | |||||
| "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==", | |||||
| "dependencies": { | |||||
| "use-sync-external-store": "1.2.0" | |||||
| }, | |||||
| "engines": { | |||||
| "node": ">=12.7.0" | |||||
| }, | |||||
| "peerDependencies": { | |||||
| "@types/react": ">=16.8", | |||||
| "immer": ">=9.0.6", | |||||
| "react": ">=16.8" | |||||
| }, | |||||
| "peerDependenciesMeta": { | |||||
| "@types/react": { | |||||
| "optional": true | |||||
| }, | |||||
| "immer": { | |||||
| "optional": true | |||||
| }, | |||||
| "react": { | |||||
| "optional": true | |||||
| } | |||||
| } | |||||
| }, | |||||
| "node_modules/@reactflow/core": { | "node_modules/@reactflow/core": { | ||||
| "version": "11.11.2", | "version": "11.11.2", | ||||
| "resolved": "https://registry.npmmirror.com/@reactflow/core/-/core-11.11.2.tgz", | "resolved": "https://registry.npmmirror.com/@reactflow/core/-/core-11.11.2.tgz", | ||||
| "react-dom": ">=17" | "react-dom": ">=17" | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/@reactflow/core/node_modules/immer": { | |||||
| "version": "10.1.1", | |||||
| "resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz", | |||||
| "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", | |||||
| "optional": true, | |||||
| "peer": true | |||||
| }, | |||||
| "node_modules/@reactflow/core/node_modules/zustand": { | |||||
| "version": "4.5.2", | |||||
| "resolved": "https://registry.npmmirror.com/zustand/-/zustand-4.5.2.tgz", | |||||
| "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==", | |||||
| "dependencies": { | |||||
| "use-sync-external-store": "1.2.0" | |||||
| }, | |||||
| "engines": { | |||||
| "node": ">=12.7.0" | |||||
| }, | |||||
| "peerDependencies": { | |||||
| "@types/react": ">=16.8", | |||||
| "immer": ">=9.0.6", | |||||
| "react": ">=16.8" | |||||
| }, | |||||
| "peerDependenciesMeta": { | |||||
| "@types/react": { | |||||
| "optional": true | |||||
| }, | |||||
| "immer": { | |||||
| "optional": true | |||||
| }, | |||||
| "react": { | |||||
| "optional": true | |||||
| } | |||||
| } | |||||
| }, | |||||
| "node_modules/@reactflow/minimap": { | "node_modules/@reactflow/minimap": { | ||||
| "version": "11.7.12", | "version": "11.7.12", | ||||
| "resolved": "https://registry.npmmirror.com/@reactflow/minimap/-/minimap-11.7.12.tgz", | "resolved": "https://registry.npmmirror.com/@reactflow/minimap/-/minimap-11.7.12.tgz", | ||||
| "react-dom": ">=17" | "react-dom": ">=17" | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/@reactflow/minimap/node_modules/immer": { | |||||
| "version": "10.1.1", | |||||
| "resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz", | |||||
| "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", | |||||
| "optional": true, | |||||
| "peer": true | |||||
| }, | |||||
| "node_modules/@reactflow/minimap/node_modules/zustand": { | |||||
| "version": "4.5.2", | |||||
| "resolved": "https://registry.npmmirror.com/zustand/-/zustand-4.5.2.tgz", | |||||
| "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==", | |||||
| "dependencies": { | |||||
| "use-sync-external-store": "1.2.0" | |||||
| }, | |||||
| "engines": { | |||||
| "node": ">=12.7.0" | |||||
| }, | |||||
| "peerDependencies": { | |||||
| "@types/react": ">=16.8", | |||||
| "immer": ">=9.0.6", | |||||
| "react": ">=16.8" | |||||
| }, | |||||
| "peerDependenciesMeta": { | |||||
| "@types/react": { | |||||
| "optional": true | |||||
| }, | |||||
| "immer": { | |||||
| "optional": true | |||||
| }, | |||||
| "react": { | |||||
| "optional": true | |||||
| } | |||||
| } | |||||
| }, | |||||
| "node_modules/@reactflow/node-resizer": { | "node_modules/@reactflow/node-resizer": { | ||||
| "version": "2.2.12", | "version": "2.2.12", | ||||
| "resolved": "https://registry.npmmirror.com/@reactflow/node-resizer/-/node-resizer-2.2.12.tgz", | "resolved": "https://registry.npmmirror.com/@reactflow/node-resizer/-/node-resizer-2.2.12.tgz", | ||||
| "react-dom": ">=17" | "react-dom": ">=17" | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/@reactflow/node-resizer/node_modules/immer": { | |||||
| "version": "10.1.1", | |||||
| "resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz", | |||||
| "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", | |||||
| "optional": true, | |||||
| "peer": true | |||||
| }, | |||||
| "node_modules/@reactflow/node-resizer/node_modules/zustand": { | |||||
| "version": "4.5.2", | |||||
| "resolved": "https://registry.npmmirror.com/zustand/-/zustand-4.5.2.tgz", | |||||
| "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==", | |||||
| "dependencies": { | |||||
| "use-sync-external-store": "1.2.0" | |||||
| }, | |||||
| "engines": { | |||||
| "node": ">=12.7.0" | |||||
| }, | |||||
| "peerDependencies": { | |||||
| "@types/react": ">=16.8", | |||||
| "immer": ">=9.0.6", | |||||
| "react": ">=16.8" | |||||
| }, | |||||
| "peerDependenciesMeta": { | |||||
| "@types/react": { | |||||
| "optional": true | |||||
| }, | |||||
| "immer": { | |||||
| "optional": true | |||||
| }, | |||||
| "react": { | |||||
| "optional": true | |||||
| } | |||||
| } | |||||
| }, | |||||
| "node_modules/@reactflow/node-toolbar": { | "node_modules/@reactflow/node-toolbar": { | ||||
| "version": "1.3.12", | "version": "1.3.12", | ||||
| "resolved": "https://registry.npmmirror.com/@reactflow/node-toolbar/-/node-toolbar-1.3.12.tgz", | "resolved": "https://registry.npmmirror.com/@reactflow/node-toolbar/-/node-toolbar-1.3.12.tgz", | ||||
| "react-dom": ">=17" | "react-dom": ">=17" | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/@reactflow/node-toolbar/node_modules/immer": { | |||||
| "version": "10.1.1", | |||||
| "resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz", | |||||
| "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", | |||||
| "optional": true, | |||||
| "peer": true | |||||
| }, | |||||
| "node_modules/@reactflow/node-toolbar/node_modules/zustand": { | |||||
| "version": "4.5.2", | |||||
| "resolved": "https://registry.npmmirror.com/zustand/-/zustand-4.5.2.tgz", | |||||
| "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==", | |||||
| "node_modules/@redux-devtools/extension": { | |||||
| "version": "3.3.0", | |||||
| "resolved": "https://registry.npmmirror.com/@redux-devtools/extension/-/extension-3.3.0.tgz", | |||||
| "integrity": "sha512-X34S/rC8S/M1BIrkYD1mJ5f8vlH0BDqxXrs96cvxSBo4FhMdbhU+GUGsmNYov1xjSyLMHgo8NYrUG8bNX7525g==", | |||||
| "dev": true, | |||||
| "dependencies": { | "dependencies": { | ||||
| "use-sync-external-store": "1.2.0" | |||||
| }, | |||||
| "engines": { | |||||
| "node": ">=12.7.0" | |||||
| "@babel/runtime": "^7.23.2", | |||||
| "immutable": "^4.3.4" | |||||
| }, | }, | ||||
| "peerDependencies": { | "peerDependencies": { | ||||
| "@types/react": ">=16.8", | |||||
| "immer": ">=9.0.6", | |||||
| "react": ">=16.8" | |||||
| }, | |||||
| "peerDependenciesMeta": { | |||||
| "@types/react": { | |||||
| "optional": true | |||||
| }, | |||||
| "immer": { | |||||
| "optional": true | |||||
| }, | |||||
| "react": { | |||||
| "optional": true | |||||
| } | |||||
| "redux": "^3.1.0 || ^4.0.0 || ^5.0.0" | |||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/@rgrove/parse-xml": { | "node_modules/@rgrove/parse-xml": { | ||||
| "integrity": "sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA==", | "integrity": "sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA==", | ||||
| "dev": true | "dev": true | ||||
| }, | }, | ||||
| "node_modules/@tanstack/react-query": { | |||||
| "version": "5.40.0", | |||||
| "resolved": "https://registry.npmmirror.com/@tanstack/react-query/-/react-query-5.40.0.tgz", | |||||
| "integrity": "sha512-iv/W0Axc4aXhFzkrByToE1JQqayxTPNotCoSCnarR/A1vDIHaoKpg7FTIfP3Ev2mbKn1yrxq0ZKYUdLEJxs6Tg==", | |||||
| "dependencies": { | |||||
| "@tanstack/query-core": "5.40.0" | |||||
| }, | |||||
| "funding": { | |||||
| "type": "github", | |||||
| "url": "https://github.com/sponsors/tannerlinsley" | |||||
| }, | |||||
| "peerDependencies": { | |||||
| "react": "^18.0.0" | |||||
| } | |||||
| }, | |||||
| "node_modules/@tanstack/react-query/node_modules/@tanstack/query-core": { | |||||
| "version": "5.40.0", | |||||
| "resolved": "https://registry.npmmirror.com/@tanstack/query-core/-/query-core-5.40.0.tgz", | |||||
| "integrity": "sha512-eD8K8jsOIq0Z5u/QbvOmfvKKE/XC39jA7yv4hgpl/1SRiU+J8QCIwgM/mEHuunQsL87dcvnHqSVLmf9pD4CiaA==", | |||||
| "funding": { | |||||
| "type": "github", | |||||
| "url": "https://github.com/sponsors/tannerlinsley" | |||||
| } | |||||
| }, | |||||
| "node_modules/@testing-library/dom": { | "node_modules/@testing-library/dom": { | ||||
| "version": "10.1.0", | "version": "10.1.0", | ||||
| "resolved": "https://registry.npmmirror.com/@testing-library/dom/-/dom-10.1.0.tgz", | "resolved": "https://registry.npmmirror.com/@testing-library/dom/-/dom-10.1.0.tgz", | ||||
| "value-equal": "^1.0.1" | "value-equal": "^1.0.1" | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/@umijs/plugins/node_modules/immer": { | |||||
| "version": "8.0.4", | |||||
| "resolved": "https://registry.npmmirror.com/immer/-/immer-8.0.4.tgz", | |||||
| "integrity": "sha512-jMfL18P+/6P6epANRvRk6q8t+3gGhqsJ9EuJ25AXE+9bNTYtssvzeYbEd0mXRYWCmmXSIbnlpz6vd6iJlmGGGQ==", | |||||
| "dev": true, | |||||
| "funding": { | |||||
| "type": "opencollective", | |||||
| "url": "https://opencollective.com/immer" | |||||
| } | |||||
| }, | |||||
| "node_modules/@umijs/plugins/node_modules/isarray": { | "node_modules/@umijs/plugins/node_modules/isarray": { | ||||
| "version": "0.0.1", | "version": "0.0.1", | ||||
| "resolved": "https://registry.npmmirror.com/isarray/-/isarray-0.0.1.tgz", | "resolved": "https://registry.npmmirror.com/isarray/-/isarray-0.0.1.tgz", | ||||
| "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" | "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" | ||||
| }, | }, | ||||
| "node_modules/immer": { | "node_modules/immer": { | ||||
| "version": "8.0.4", | |||||
| "resolved": "https://registry.npmmirror.com/immer/-/immer-8.0.4.tgz", | |||||
| "integrity": "sha512-jMfL18P+/6P6epANRvRk6q8t+3gGhqsJ9EuJ25AXE+9bNTYtssvzeYbEd0mXRYWCmmXSIbnlpz6vd6iJlmGGGQ==", | |||||
| "version": "10.1.1", | |||||
| "resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz", | |||||
| "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", | |||||
| "optional": true, | |||||
| "peer": true, | |||||
| "funding": { | |||||
| "type": "opencollective", | |||||
| "url": "https://opencollective.com/immer" | |||||
| } | |||||
| }, | |||||
| "node_modules/immutable": { | |||||
| "version": "4.3.6", | |||||
| "resolved": "https://registry.npmmirror.com/immutable/-/immutable-4.3.6.tgz", | |||||
| "integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==", | |||||
| "dev": true | "dev": true | ||||
| }, | }, | ||||
| "node_modules/import-fresh": { | "node_modules/import-fresh": { | ||||
| "node": ">=10" | "node": ">=10" | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/zustand": { | |||||
| "version": "4.5.2", | |||||
| "resolved": "https://registry.npmmirror.com/zustand/-/zustand-4.5.2.tgz", | |||||
| "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==", | |||||
| "dependencies": { | |||||
| "use-sync-external-store": "1.2.0" | |||||
| }, | |||||
| "engines": { | |||||
| "node": ">=12.7.0" | |||||
| }, | |||||
| "peerDependencies": { | |||||
| "@types/react": ">=16.8", | |||||
| "immer": ">=9.0.6", | |||||
| "react": ">=16.8" | |||||
| }, | |||||
| "peerDependenciesMeta": { | |||||
| "@types/react": { | |||||
| "optional": true | |||||
| }, | |||||
| "immer": { | |||||
| "optional": true | |||||
| }, | |||||
| "react": { | |||||
| "optional": true | |||||
| } | |||||
| } | |||||
| }, | |||||
| "node_modules/zwitch": { | "node_modules/zwitch": { | ||||
| "version": "2.0.4", | "version": "2.0.4", | ||||
| "resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz", | "resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz", |
| "@ant-design/pro-components": "^2.6.46", | "@ant-design/pro-components": "^2.6.46", | ||||
| "@ant-design/pro-layout": "^7.17.16", | "@ant-design/pro-layout": "^7.17.16", | ||||
| "@js-preview/excel": "^1.7.8", | "@js-preview/excel": "^1.7.8", | ||||
| "@tanstack/react-query": "^5.40.0", | |||||
| "ahooks": "^3.7.10", | "ahooks": "^3.7.10", | ||||
| "antd": "^5.12.7", | "antd": "^5.12.7", | ||||
| "axios": "^1.6.3", | "axios": "^1.6.3", | ||||
| "umi": "^4.0.90", | "umi": "^4.0.90", | ||||
| "umi-request": "^1.4.0", | "umi-request": "^1.4.0", | ||||
| "unist-util-visit-parents": "^6.0.1", | "unist-util-visit-parents": "^6.0.1", | ||||
| "uuid": "^9.0.1" | |||||
| "uuid": "^9.0.1", | |||||
| "zustand": "^4.5.2" | |||||
| }, | }, | ||||
| "devDependencies": { | "devDependencies": { | ||||
| "@react-dev-inspector/umi4-plugin": "^2.0.1", | "@react-dev-inspector/umi4-plugin": "^2.0.1", | ||||
| "@redux-devtools/extension": "^3.3.0", | |||||
| "@testing-library/jest-dom": "^6.4.5", | "@testing-library/jest-dom": "^6.4.5", | ||||
| "@testing-library/react": "^15.0.7", | "@testing-library/react": "^15.0.7", | ||||
| "@types/dagre": "^0.7.52", | "@types/dagre": "^0.7.52", |
| import React, { ReactNode, useEffect, useState } from 'react'; | import React, { ReactNode, useEffect, useState } from 'react'; | ||||
| import storage from './utils/authorizationUtil'; | import storage from './utils/authorizationUtil'; | ||||
| import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; | |||||
| import dayjs from 'dayjs'; | import dayjs from 'dayjs'; | ||||
| import advancedFormat from 'dayjs/plugin/advancedFormat'; | import advancedFormat from 'dayjs/plugin/advancedFormat'; | ||||
| import customParseFormat from 'dayjs/plugin/customParseFormat'; | import customParseFormat from 'dayjs/plugin/customParseFormat'; | ||||
| import localeData from 'dayjs/plugin/localeData'; | import localeData from 'dayjs/plugin/localeData'; | ||||
| import weekday from 'dayjs/plugin/weekday'; | |||||
| import weekOfYear from 'dayjs/plugin/weekOfYear'; | import weekOfYear from 'dayjs/plugin/weekOfYear'; | ||||
| import weekYear from 'dayjs/plugin/weekYear'; | import weekYear from 'dayjs/plugin/weekYear'; | ||||
| import weekday from 'dayjs/plugin/weekday'; | |||||
| dayjs.extend(customParseFormat); | dayjs.extend(customParseFormat); | ||||
| dayjs.extend(advancedFormat); | dayjs.extend(advancedFormat); | ||||
| 'zh-TRADITIONAL': zh_HK, | 'zh-TRADITIONAL': zh_HK, | ||||
| }; | }; | ||||
| const queryClient = new QueryClient(); | |||||
| type Locale = ConfigProviderProps['locale']; | type Locale = ConfigProviderProps['locale']; | ||||
| const RootProvider = ({ children }: React.PropsWithChildren) => { | const RootProvider = ({ children }: React.PropsWithChildren) => { | ||||
| }, []); | }, []); | ||||
| return ( | return ( | ||||
| <ConfigProvider | |||||
| theme={{ | |||||
| token: { | |||||
| fontFamily: 'Inter', | |||||
| }, | |||||
| }} | |||||
| locale={locale} | |||||
| > | |||||
| <App> {children}</App> | |||||
| </ConfigProvider> | |||||
| <QueryClientProvider client={queryClient}> | |||||
| <ConfigProvider | |||||
| theme={{ | |||||
| token: { | |||||
| fontFamily: 'Inter', | |||||
| }, | |||||
| }} | |||||
| locale={locale} | |||||
| > | |||||
| <App> {children}</App> | |||||
| </ConfigProvider> | |||||
| </QueryClientProvider> | |||||
| ); | ); | ||||
| }; | }; | ||||
| import { useTranslate } from '@/hooks/commonHooks'; | |||||
| import { useFetchKnowledgeList } from '@/hooks/knowledgeHook'; | |||||
| import { Form, Select } from 'antd'; | |||||
| const KnowledgeBaseItem = () => { | |||||
| const { t } = useTranslate('chat'); | |||||
| const { list: knowledgeList } = useFetchKnowledgeList(true); | |||||
| const knowledgeOptions = knowledgeList.map((x) => ({ | |||||
| label: x.name, | |||||
| value: x.id, | |||||
| })); | |||||
| return ( | |||||
| <Form.Item | |||||
| label={t('knowledgeBases')} | |||||
| name="kb_ids" | |||||
| tooltip={t('knowledgeBasesTip')} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: t('knowledgeBasesMessage'), | |||||
| type: 'array', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Select | |||||
| mode="multiple" | |||||
| options={knowledgeOptions} | |||||
| placeholder={t('knowledgeBasesMessage')} | |||||
| ></Select> | |||||
| </Form.Item> | |||||
| ); | |||||
| }; | |||||
| export default KnowledgeBaseItem; |
| .sliderInputNumber { | |||||
| width: 80px; | |||||
| } | |||||
| .variableSlider { | |||||
| width: 100%; | |||||
| } |
| import { LlmModelType, ModelVariableType } from '@/constants/knowledge'; | |||||
| import { Divider, Flex, Form, InputNumber, Select, Slider, Switch } from 'antd'; | |||||
| import camelCase from 'lodash/camelCase'; | |||||
| import { useTranslate } from '@/hooks/commonHooks'; | |||||
| import { useSelectLlmOptionsByModelType } from '@/hooks/llmHooks'; | |||||
| import { useMemo } from 'react'; | |||||
| import styles from './index.less'; | |||||
| interface IProps { | |||||
| prefix?: string; | |||||
| handleParametersChange(value: ModelVariableType): void; | |||||
| } | |||||
| const LlmSettingItems = ({ prefix, handleParametersChange }: IProps) => { | |||||
| const { t } = useTranslate('chat'); | |||||
| const parameterOptions = Object.values(ModelVariableType).map((x) => ({ | |||||
| label: t(camelCase(x)), | |||||
| value: x, | |||||
| })); | |||||
| const memorizedPrefix = useMemo(() => (prefix ? [prefix] : []), [prefix]); | |||||
| const modelOptions = useSelectLlmOptionsByModelType(); | |||||
| return ( | |||||
| <> | |||||
| <Form.Item | |||||
| label={t('model')} | |||||
| name="llm_id" | |||||
| tooltip={t('modelTip')} | |||||
| rules={[{ required: true, message: t('modelMessage') }]} | |||||
| > | |||||
| <Select options={modelOptions[LlmModelType.Chat]} showSearch /> | |||||
| </Form.Item> | |||||
| <Divider></Divider> | |||||
| <Form.Item | |||||
| label={t('freedom')} | |||||
| name="parameters" | |||||
| tooltip={t('freedomTip')} | |||||
| initialValue={ModelVariableType.Precise} | |||||
| > | |||||
| <Select<ModelVariableType> | |||||
| options={parameterOptions} | |||||
| onChange={handleParametersChange} | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item label={t('temperature')} tooltip={t('temperatureTip')}> | |||||
| <Flex gap={20} align="center"> | |||||
| <Form.Item | |||||
| name={'temperatureEnabled'} | |||||
| valuePropName="checked" | |||||
| noStyle | |||||
| > | |||||
| <Switch size="small" /> | |||||
| </Form.Item> | |||||
| <Form.Item noStyle dependencies={['temperatureEnabled']}> | |||||
| {({ getFieldValue }) => { | |||||
| const disabled = !getFieldValue('temperatureEnabled'); | |||||
| return ( | |||||
| <> | |||||
| <Flex flex={1}> | |||||
| <Form.Item | |||||
| name={[...memorizedPrefix, 'temperature']} | |||||
| noStyle | |||||
| > | |||||
| <Slider | |||||
| className={styles.variableSlider} | |||||
| max={1} | |||||
| step={0.01} | |||||
| disabled={disabled} | |||||
| /> | |||||
| </Form.Item> | |||||
| </Flex> | |||||
| <Form.Item name={[...memorizedPrefix, 'temperature']} noStyle> | |||||
| <InputNumber | |||||
| className={styles.sliderInputNumber} | |||||
| max={1} | |||||
| min={0} | |||||
| step={0.01} | |||||
| disabled={disabled} | |||||
| /> | |||||
| </Form.Item> | |||||
| </> | |||||
| ); | |||||
| }} | |||||
| </Form.Item> | |||||
| </Flex> | |||||
| </Form.Item> | |||||
| <Form.Item label={t('topP')} tooltip={t('topPTip')}> | |||||
| <Flex gap={20} align="center"> | |||||
| <Form.Item name={'topPEnabled'} valuePropName="checked" noStyle> | |||||
| <Switch size="small" /> | |||||
| </Form.Item> | |||||
| <Form.Item noStyle dependencies={['topPEnabled']}> | |||||
| {({ getFieldValue }) => { | |||||
| const disabled = !getFieldValue('topPEnabled'); | |||||
| return ( | |||||
| <> | |||||
| <Flex flex={1}> | |||||
| <Form.Item name={[...memorizedPrefix, 'top_p']} noStyle> | |||||
| <Slider | |||||
| className={styles.variableSlider} | |||||
| max={1} | |||||
| step={0.01} | |||||
| disabled={disabled} | |||||
| /> | |||||
| </Form.Item> | |||||
| </Flex> | |||||
| <Form.Item name={[...memorizedPrefix, 'top_p']} noStyle> | |||||
| <InputNumber | |||||
| className={styles.sliderInputNumber} | |||||
| max={1} | |||||
| min={0} | |||||
| step={0.01} | |||||
| disabled={disabled} | |||||
| /> | |||||
| </Form.Item> | |||||
| </> | |||||
| ); | |||||
| }} | |||||
| </Form.Item> | |||||
| </Flex> | |||||
| </Form.Item> | |||||
| <Form.Item label={t('presencePenalty')} tooltip={t('presencePenaltyTip')}> | |||||
| <Flex gap={20} align="center"> | |||||
| <Form.Item | |||||
| name={'presencePenaltyEnabled'} | |||||
| valuePropName="checked" | |||||
| noStyle | |||||
| > | |||||
| <Switch size="small" /> | |||||
| </Form.Item> | |||||
| <Form.Item noStyle dependencies={['presencePenaltyEnabled']}> | |||||
| {({ getFieldValue }) => { | |||||
| const disabled = !getFieldValue('presencePenaltyEnabled'); | |||||
| return ( | |||||
| <> | |||||
| <Flex flex={1}> | |||||
| <Form.Item | |||||
| name={[...memorizedPrefix, 'presence_penalty']} | |||||
| noStyle | |||||
| > | |||||
| <Slider | |||||
| className={styles.variableSlider} | |||||
| max={1} | |||||
| step={0.01} | |||||
| disabled={disabled} | |||||
| /> | |||||
| </Form.Item> | |||||
| </Flex> | |||||
| <Form.Item | |||||
| name={[...memorizedPrefix, 'presence_penalty']} | |||||
| noStyle | |||||
| > | |||||
| <InputNumber | |||||
| className={styles.sliderInputNumber} | |||||
| max={1} | |||||
| min={0} | |||||
| step={0.01} | |||||
| disabled={disabled} | |||||
| /> | |||||
| </Form.Item> | |||||
| </> | |||||
| ); | |||||
| }} | |||||
| </Form.Item> | |||||
| </Flex> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label={t('frequencyPenalty')} | |||||
| tooltip={t('frequencyPenaltyTip')} | |||||
| > | |||||
| <Flex gap={20} align="center"> | |||||
| <Form.Item | |||||
| name={'frequencyPenaltyEnabled'} | |||||
| valuePropName="checked" | |||||
| noStyle | |||||
| > | |||||
| <Switch size="small" /> | |||||
| </Form.Item> | |||||
| <Form.Item noStyle dependencies={['frequencyPenaltyEnabled']}> | |||||
| {({ getFieldValue }) => { | |||||
| const disabled = !getFieldValue('frequencyPenaltyEnabled'); | |||||
| return ( | |||||
| <> | |||||
| <Flex flex={1}> | |||||
| <Form.Item | |||||
| name={[...memorizedPrefix, 'frequency_penalty']} | |||||
| noStyle | |||||
| > | |||||
| <Slider | |||||
| className={styles.variableSlider} | |||||
| max={1} | |||||
| step={0.01} | |||||
| disabled={disabled} | |||||
| /> | |||||
| </Form.Item> | |||||
| </Flex> | |||||
| <Form.Item | |||||
| name={[...memorizedPrefix, 'frequency_penalty']} | |||||
| noStyle | |||||
| > | |||||
| <InputNumber | |||||
| className={styles.sliderInputNumber} | |||||
| max={1} | |||||
| min={0} | |||||
| step={0.01} | |||||
| disabled={disabled} | |||||
| /> | |||||
| </Form.Item> | |||||
| </> | |||||
| ); | |||||
| }} | |||||
| </Form.Item> | |||||
| </Flex> | |||||
| </Form.Item> | |||||
| <Form.Item label={t('maxTokens')} tooltip={t('maxTokensTip')}> | |||||
| <Flex gap={20} align="center"> | |||||
| <Form.Item name={'maxTokensEnabled'} valuePropName="checked" noStyle> | |||||
| <Switch size="small" /> | |||||
| </Form.Item> | |||||
| <Form.Item noStyle dependencies={['maxTokensEnabled']}> | |||||
| {({ getFieldValue }) => { | |||||
| const disabled = !getFieldValue('maxTokensEnabled'); | |||||
| return ( | |||||
| <> | |||||
| <Flex flex={1}> | |||||
| <Form.Item | |||||
| name={[...memorizedPrefix, 'max_tokens']} | |||||
| noStyle | |||||
| > | |||||
| <Slider | |||||
| className={styles.variableSlider} | |||||
| max={2048} | |||||
| disabled={disabled} | |||||
| /> | |||||
| </Form.Item> | |||||
| </Flex> | |||||
| <Form.Item name={[...memorizedPrefix, 'max_tokens']} noStyle> | |||||
| <InputNumber | |||||
| disabled={disabled} | |||||
| className={styles.sliderInputNumber} | |||||
| max={2048} | |||||
| min={0} | |||||
| /> | |||||
| </Form.Item> | |||||
| </> | |||||
| ); | |||||
| }} | |||||
| </Form.Item> | |||||
| </Flex> | |||||
| </Form.Item> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default LlmSettingItems; |
| import { useTranslate } from '@/hooks/commonHooks'; | |||||
| import { Form, Slider } from 'antd'; | |||||
| type FieldType = { | |||||
| top_n?: number; | |||||
| }; | |||||
| const TopNItem = () => { | |||||
| const { t } = useTranslate('chat'); | |||||
| return ( | |||||
| <Form.Item<FieldType> | |||||
| label={t('topN')} | |||||
| name={'top_n'} | |||||
| initialValue={8} | |||||
| tooltip={t('topNTip')} | |||||
| > | |||||
| <Slider max={30} /> | |||||
| </Form.Item> | |||||
| ); | |||||
| }; | |||||
| export default TopNItem; |
| import flowService from '@/services/flow-service'; | |||||
| import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; | |||||
| export const useFetchFlowTemplates = () => { | |||||
| const { data } = useQuery({ | |||||
| queryKey: ['fetchFlowTemplates'], | |||||
| initialData: [], | |||||
| queryFn: async () => { | |||||
| const { data } = await flowService.listTemplates(); | |||||
| return data; | |||||
| }, | |||||
| }); | |||||
| return data; | |||||
| }; | |||||
| export const useFetchFlowList = () => { | |||||
| const { data, isFetching: loading } = useQuery({ | |||||
| queryKey: ['fetchFlowList'], | |||||
| initialData: [], | |||||
| queryFn: async () => { | |||||
| const { data } = await flowService.listCanvas(); | |||||
| return data?.data ?? []; | |||||
| }, | |||||
| }); | |||||
| return { data, loading }; | |||||
| }; | |||||
| export const useSetFlow = () => { | |||||
| const queryClient = useQueryClient(); | |||||
| const { | |||||
| data, | |||||
| isPending: loading, | |||||
| mutateAsync, | |||||
| } = useMutation({ | |||||
| mutationKey: ['setFlow'], | |||||
| mutationFn: async (params: any) => { | |||||
| const { data } = await flowService.setCanvas(params); | |||||
| if (data.retcode === 0) { | |||||
| queryClient.invalidateQueries({ queryKey: ['fetchFlowList'] }); | |||||
| } | |||||
| return data?.retcode; | |||||
| }, | |||||
| }); | |||||
| return { data, loading, setFlow: mutateAsync }; | |||||
| }; | |||||
| export const useDeleteFlow = () => { | |||||
| const queryClient = useQueryClient(); | |||||
| const { | |||||
| data, | |||||
| isPending: loading, | |||||
| mutateAsync, | |||||
| } = useMutation({ | |||||
| mutationKey: ['deleteFlow'], | |||||
| mutationFn: async (canvasIds: string[]) => { | |||||
| const { data } = await flowService.removeCanvas({ canvasIds }); | |||||
| if (data.retcode === 0) { | |||||
| queryClient.invalidateQueries({ queryKey: ['fetchFlowList'] }); | |||||
| } | |||||
| return data?.data ?? []; | |||||
| }, | |||||
| }); | |||||
| return { data, loading, deleteFlow: mutateAsync }; | |||||
| }; |
| const [loading, setLoading] = useState(false); | const [loading, setLoading] = useState(false); | ||||
| const fetchSystemVersion = useCallback(async () => { | const fetchSystemVersion = useCallback(async () => { | ||||
| setLoading(true); | |||||
| const { data } = await userService.getSystemVersion(); | |||||
| if (data.retcode === 0) { | |||||
| setVersion(data.data); | |||||
| try { | |||||
| setLoading(true); | |||||
| const { data } = await userService.getSystemVersion(); | |||||
| if (data.retcode === 0) { | |||||
| setVersion(data.data); | |||||
| setLoading(false); | |||||
| } | |||||
| } catch (error) { | |||||
| setLoading(false); | setLoading(false); | ||||
| } | } | ||||
| }, []); | }, []); |
| export type DSLComponents = Record<string, Operator>; | |||||
| export type DSLComponents = Record<string, IOperator>; | |||||
| export interface DSL { | export interface DSL { | ||||
| components: DSLComponents; | components: DSLComponents; | ||||
| answer: any[]; | answer: any[]; | ||||
| } | } | ||||
| export interface Operator { | |||||
| obj: OperatorNode; | |||||
| export interface IOperator { | |||||
| obj: IOperatorNode; | |||||
| downstream: string[]; | downstream: string[]; | ||||
| upstream: string[]; | upstream: string[]; | ||||
| } | } | ||||
| export interface OperatorNode { | |||||
| export interface IOperatorNode { | |||||
| component_name: string; | component_name: string; | ||||
| params: Record<string, unknown>; | params: Record<string, unknown>; | ||||
| } | } |
| preview: 'Preview', | preview: 'Preview', | ||||
| fileError: 'File error', | fileError: 'File error', | ||||
| }, | }, | ||||
| flow: { cite: 'Cite', citeTip: 'citeTip' }, | |||||
| footer: { | footer: { | ||||
| profile: 'All rights reserved @ React', | profile: 'All rights reserved @ React', | ||||
| }, | }, |
| import { useFetchKnowledgeList } from '@/hooks/knowledgeHook'; | |||||
| import { PlusOutlined } from '@ant-design/icons'; | import { PlusOutlined } from '@ant-design/icons'; | ||||
| import { Form, Input, Select, Switch, Upload } from 'antd'; | import { Form, Input, Select, Switch, Upload } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { ISegmentedContentProps } from '../interface'; | import { ISegmentedContentProps } from '../interface'; | ||||
| import KnowledgeBaseItem from '@/components/knowledge-base-item'; | |||||
| import { useTranslate } from '@/hooks/commonHooks'; | import { useTranslate } from '@/hooks/commonHooks'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| const AssistantSetting = ({ show }: ISegmentedContentProps) => { | const AssistantSetting = ({ show }: ISegmentedContentProps) => { | ||||
| const { list: knowledgeList } = useFetchKnowledgeList(true); | |||||
| const knowledgeOptions = knowledgeList.map((x) => ({ | |||||
| label: x.name, | |||||
| value: x.id, | |||||
| })); | |||||
| const { t } = useTranslate('chat'); | const { t } = useTranslate('chat'); | ||||
| const normFile = (e: any) => { | const normFile = (e: any) => { | ||||
| > | > | ||||
| <Switch /> | <Switch /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | |||||
| label={t('knowledgeBases')} | |||||
| name="kb_ids" | |||||
| tooltip={t('knowledgeBasesTip')} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: t('knowledgeBasesMessage'), | |||||
| type: 'array', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Select | |||||
| mode="multiple" | |||||
| options={knowledgeOptions} | |||||
| placeholder={t('knowledgeBasesMessage')} | |||||
| ></Select> | |||||
| </Form.Item> | |||||
| <KnowledgeBaseItem></KnowledgeBaseItem> | |||||
| </section> | </section> | ||||
| ); | ); | ||||
| }; | }; |
| import { | import { | ||||
| LlmModelType, | |||||
| ModelVariableType, | ModelVariableType, | ||||
| settledModelVariableMap, | settledModelVariableMap, | ||||
| } from '@/constants/knowledge'; | } from '@/constants/knowledge'; | ||||
| import { Divider, Flex, Form, InputNumber, Select, Slider, Switch } from 'antd'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import camelCase from 'lodash/camelCase'; | |||||
| import { useEffect } from 'react'; | import { useEffect } from 'react'; | ||||
| import { ISegmentedContentProps } from '../interface'; | import { ISegmentedContentProps } from '../interface'; | ||||
| import { useTranslate } from '@/hooks/commonHooks'; | |||||
| import { useSelectLlmOptionsByModelType } from '@/hooks/llmHooks'; | |||||
| import LlmSettingItems from '@/components/llm-setting-items'; | |||||
| import { Variable } from '@/interfaces/database/chat'; | import { Variable } from '@/interfaces/database/chat'; | ||||
| import { variableEnabledFieldMap } from '../constants'; | import { variableEnabledFieldMap } from '../constants'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| initialLlmSetting?: Variable; | initialLlmSetting?: Variable; | ||||
| visible?: boolean; | visible?: boolean; | ||||
| }) => { | }) => { | ||||
| const { t } = useTranslate('chat'); | |||||
| const parameterOptions = Object.values(ModelVariableType).map((x) => ({ | |||||
| label: t(camelCase(x)), | |||||
| value: x, | |||||
| })); | |||||
| const modelOptions = useSelectLlmOptionsByModelType(); | |||||
| const handleParametersChange = (value: ModelVariableType) => { | const handleParametersChange = (value: ModelVariableType) => { | ||||
| const variable = settledModelVariableMap[value]; | const variable = settledModelVariableMap[value]; | ||||
| form.setFieldsValue({ llm_setting: variable }); | form.setFieldsValue({ llm_setting: variable }); | ||||
| [styles.segmentedHidden]: !show, | [styles.segmentedHidden]: !show, | ||||
| })} | })} | ||||
| > | > | ||||
| <Form.Item | |||||
| {visible && ( | |||||
| <LlmSettingItems | |||||
| prefix="llm_setting" | |||||
| handleParametersChange={handleParametersChange} | |||||
| ></LlmSettingItems> | |||||
| )} | |||||
| {/* <Form.Item | |||||
| label={t('model')} | label={t('model')} | ||||
| name="llm_id" | name="llm_id" | ||||
| tooltip={t('modelTip')} | tooltip={t('modelTip')} | ||||
| }} | }} | ||||
| </Form.Item> | </Form.Item> | ||||
| </Flex> | </Flex> | ||||
| </Form.Item> | |||||
| </Form.Item> */} | |||||
| </section> | </section> | ||||
| ); | ); | ||||
| }; | }; |
| Form, | Form, | ||||
| Input, | Input, | ||||
| Row, | Row, | ||||
| Slider, | |||||
| Switch, | Switch, | ||||
| Table, | Table, | ||||
| TableProps, | TableProps, | ||||
| import { EditableCell, EditableRow } from './editable-cell'; | import { EditableCell, EditableRow } from './editable-cell'; | ||||
| import Rerank from '@/components/rerank'; | import Rerank from '@/components/rerank'; | ||||
| import TopNItem from '@/components/top-n-item'; | |||||
| import { useTranslate } from '@/hooks/commonHooks'; | import { useTranslate } from '@/hooks/commonHooks'; | ||||
| import { useSelectPromptConfigParameters } from '../hooks'; | import { useSelectPromptConfigParameters } from '../hooks'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type FieldType = { | |||||
| similarity_threshold?: number; | |||||
| vector_similarity_weight?: number; | |||||
| top_n?: number; | |||||
| }; | |||||
| const PromptEngine = ( | const PromptEngine = ( | ||||
| { show }: ISegmentedContentProps, | { show }: ISegmentedContentProps, | ||||
| ref: ForwardedRef<Array<IPromptConfigParameters>>, | ref: ForwardedRef<Array<IPromptConfigParameters>>, | ||||
| </Form.Item> | </Form.Item> | ||||
| <Divider></Divider> | <Divider></Divider> | ||||
| <SimilaritySlider isTooltipShown></SimilaritySlider> | <SimilaritySlider isTooltipShown></SimilaritySlider> | ||||
| <Form.Item<FieldType> | |||||
| label={t('topN')} | |||||
| name={'top_n'} | |||||
| initialValue={8} | |||||
| tooltip={t('topNTip')} | |||||
| > | |||||
| <Slider max={30} /> | |||||
| </Form.Item> | |||||
| <TopNItem></TopNItem> | |||||
| <Rerank></Rerank> | <Rerank></Rerank> | ||||
| <section className={classNames(styles.variableContainer)}> | <section className={classNames(styles.variableContainer)}> | ||||
| <Row align={'middle'} justify="end"> | <Row align={'middle'} justify="end"> |
| const AnswerForm = () => { | |||||
| return <div>AnswerForm</div>; | |||||
| }; | |||||
| export default AnswerForm; |
| import { useTranslate } from '@/hooks/commonHooks'; | |||||
| import type { FormProps } from 'antd'; | |||||
| import { Form, Input } from 'antd'; | |||||
| import { IOperatorForm } from '../interface'; | |||||
| type FieldType = { | |||||
| prologue?: string; | |||||
| }; | |||||
| const onFinish: FormProps<FieldType>['onFinish'] = (values) => { | |||||
| console.log('Success:', values); | |||||
| }; | |||||
| const onFinishFailed: FormProps<FieldType>['onFinishFailed'] = (errorInfo) => { | |||||
| console.log('Failed:', errorInfo); | |||||
| }; | |||||
| const BeginForm = ({ onValuesChange }: IOperatorForm) => { | |||||
| const { t } = useTranslate('chat'); | |||||
| const [form] = Form.useForm(); | |||||
| return ( | |||||
| <Form | |||||
| name="basic" | |||||
| labelCol={{ span: 8 }} | |||||
| wrapperCol={{ span: 16 }} | |||||
| style={{ maxWidth: 600 }} | |||||
| initialValues={{ remember: true }} | |||||
| onFinish={onFinish} | |||||
| onFinishFailed={onFinishFailed} | |||||
| onValuesChange={onValuesChange} | |||||
| autoComplete="off" | |||||
| form={form} | |||||
| > | |||||
| <Form.Item<FieldType> | |||||
| name={'prologue'} | |||||
| label={t('setAnOpener')} | |||||
| tooltip={t('setAnOpenerTip')} | |||||
| initialValue={t('setAnOpenerInitial')} | |||||
| > | |||||
| <Input.TextArea autoSize={{ minRows: 5 }} /> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| ); | |||||
| }; | |||||
| export default BeginForm; |
| setMenu({ | setMenu({ | ||||
| id: node.id, | id: node.id, | ||||
| top: event.clientY - 72, | |||||
| top: event.clientY - 144, | |||||
| left: event.clientX - sideWidth, | left: event.clientX - sideWidth, | ||||
| // top: event.clientY < pane.height - 200 ? event.clientY - 72 : 0, | // top: event.clientY < pane.height - 200 ? event.clientY - 72 : 0, | ||||
| // left: event.clientX < pane.width - 200 ? event.clientX : 0, | // left: event.clientX < pane.width - 200 ? event.clientX : 0, |
| .edgeButton { | |||||
| width: 14px; | |||||
| height: 14px; | |||||
| background: #eee; | |||||
| border: 1px solid #fff; | |||||
| padding: 0; | |||||
| cursor: pointer; | |||||
| border-radius: 50%; | |||||
| font-size: 10px; | |||||
| line-height: 1; | |||||
| } | |||||
| .edgeButton:hover { | |||||
| box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.08); | |||||
| } |
| import { | |||||
| BaseEdge, | |||||
| EdgeLabelRenderer, | |||||
| EdgeProps, | |||||
| getBezierPath, | |||||
| } from 'reactflow'; | |||||
| import useStore from '../../store'; | |||||
| import { useMemo } from 'react'; | |||||
| import styles from './index.less'; | |||||
| export function ButtonEdge({ | |||||
| id, | |||||
| sourceX, | |||||
| sourceY, | |||||
| targetX, | |||||
| targetY, | |||||
| sourcePosition, | |||||
| targetPosition, | |||||
| style = {}, | |||||
| markerEnd, | |||||
| selected, | |||||
| }: EdgeProps) { | |||||
| const deleteEdgeById = useStore((state) => state.deleteEdgeById); | |||||
| const [edgePath, labelX, labelY] = getBezierPath({ | |||||
| sourceX, | |||||
| sourceY, | |||||
| sourcePosition, | |||||
| targetX, | |||||
| targetY, | |||||
| targetPosition, | |||||
| }); | |||||
| const selectedStyle = useMemo(() => { | |||||
| return selected ? { strokeWidth: 1, stroke: '#1677ff' } : {}; | |||||
| }, [selected]); | |||||
| const onEdgeClick = () => { | |||||
| deleteEdgeById(id); | |||||
| }; | |||||
| return ( | |||||
| <> | |||||
| <BaseEdge | |||||
| path={edgePath} | |||||
| markerEnd={markerEnd} | |||||
| style={{ ...style, ...selectedStyle }} | |||||
| /> | |||||
| <EdgeLabelRenderer> | |||||
| <div | |||||
| style={{ | |||||
| position: 'absolute', | |||||
| transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`, | |||||
| fontSize: 12, | |||||
| // everything inside EdgeLabelRenderer has no pointer events by default | |||||
| // if you have an interactive element, set pointer-events: all | |||||
| pointerEvents: 'all', | |||||
| }} | |||||
| className="nodrag nopan" | |||||
| > | |||||
| <button | |||||
| className={styles.edgeButton} | |||||
| type="button" | |||||
| onClick={onEdgeClick} | |||||
| > | |||||
| × | |||||
| </button> | |||||
| </div> | |||||
| </EdgeLabelRenderer> | |||||
| </> | |||||
| ); | |||||
| } |
| .canvasWrapper { | |||||
| position: relative; | |||||
| height: 100%; | |||||
| } |
| import { useCallback, useEffect, useState } from 'react'; | |||||
| import { useCallback } from 'react'; | |||||
| import ReactFlow, { | import ReactFlow, { | ||||
| Background, | Background, | ||||
| Controls, | Controls, | ||||
| Edge, | |||||
| Node, | |||||
| MarkerType, | |||||
| NodeMouseHandler, | NodeMouseHandler, | ||||
| OnConnect, | |||||
| OnEdgesChange, | |||||
| OnNodesChange, | |||||
| addEdge, | |||||
| applyEdgeChanges, | |||||
| applyNodeChanges, | |||||
| } from 'reactflow'; | } from 'reactflow'; | ||||
| import 'reactflow/dist/style.css'; | import 'reactflow/dist/style.css'; | ||||
| import { NodeContextMenu, useHandleNodeContextMenu } from './context-menu'; | import { NodeContextMenu, useHandleNodeContextMenu } from './context-menu'; | ||||
| import { ButtonEdge } from './edge'; | |||||
| import FlowDrawer from '../flow-drawer'; | import FlowDrawer from '../flow-drawer'; | ||||
| import { | import { | ||||
| useHandleDrop, | useHandleDrop, | ||||
| useHandleKeyUp, | useHandleKeyUp, | ||||
| useHandleSelectionChange, | |||||
| useSelectCanvasData, | |||||
| useShowDrawer, | useShowDrawer, | ||||
| } from '../hooks'; | } from '../hooks'; | ||||
| import { dsl } from '../mock'; | |||||
| import { TextUpdaterNode } from './node'; | import { TextUpdaterNode } from './node'; | ||||
| import styles from './index.less'; | |||||
| const nodeTypes = { textUpdater: TextUpdaterNode }; | const nodeTypes = { textUpdater: TextUpdaterNode }; | ||||
| const edgeTypes = { | |||||
| buttonEdge: ButtonEdge, | |||||
| }; | |||||
| interface IProps { | interface IProps { | ||||
| sideWidth: number; | sideWidth: number; | ||||
| } | } | ||||
| function FlowCanvas({ sideWidth }: IProps) { | function FlowCanvas({ sideWidth }: IProps) { | ||||
| const [nodes, setNodes] = useState<Node[]>(dsl.graph.nodes); | |||||
| const [edges, setEdges] = useState<Edge[]>(dsl.graph.edges); | |||||
| const { selectedEdges, selectedNodes } = useHandleSelectionChange(); | |||||
| const { | |||||
| nodes, | |||||
| edges, | |||||
| onConnect, | |||||
| onEdgesChange, | |||||
| onNodesChange, | |||||
| onSelectionChange, | |||||
| } = useSelectCanvasData(); | |||||
| const { ref, menu, onNodeContextMenu, onPaneClick } = | const { ref, menu, onNodeContextMenu, onPaneClick } = | ||||
| useHandleNodeContextMenu(sideWidth); | useHandleNodeContextMenu(sideWidth); | ||||
| const { drawerVisible, hideDrawer, showDrawer } = useShowDrawer(); | |||||
| const onNodesChange: OnNodesChange = useCallback( | |||||
| (changes) => setNodes((nds) => applyNodeChanges(changes, nds)), | |||||
| [], | |||||
| ); | |||||
| const onEdgesChange: OnEdgesChange = useCallback( | |||||
| (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)), | |||||
| [], | |||||
| ); | |||||
| const { drawerVisible, hideDrawer, showDrawer, clickedNode } = | |||||
| useShowDrawer(); | |||||
| const onConnect: OnConnect = useCallback( | |||||
| (params) => setEdges((eds) => addEdge(params, eds)), | |||||
| [], | |||||
| const onNodeClick: NodeMouseHandler = useCallback( | |||||
| (e, node) => { | |||||
| showDrawer(node); | |||||
| }, | |||||
| [showDrawer], | |||||
| ); | ); | ||||
| const onNodeClick: NodeMouseHandler = useCallback(() => { | |||||
| showDrawer(); | |||||
| }, [showDrawer]); | |||||
| const { onDrop, onDragOver, setReactFlowInstance } = useHandleDrop(setNodes); | |||||
| const { handleKeyUp } = useHandleKeyUp(selectedEdges, selectedNodes); | |||||
| const { onDrop, onDragOver, setReactFlowInstance } = useHandleDrop(); | |||||
| useEffect(() => { | |||||
| console.info('nodes:', nodes); | |||||
| console.info('edges:', edges); | |||||
| }, [nodes, edges]); | |||||
| const { handleKeyUp } = useHandleKeyUp(); | |||||
| return ( | return ( | ||||
| <div style={{ height: '100%', width: '100%' }}> | |||||
| <div className={styles.canvasWrapper}> | |||||
| <ReactFlow | <ReactFlow | ||||
| ref={ref} | ref={ref} | ||||
| nodes={nodes} | nodes={nodes} | ||||
| fitView | fitView | ||||
| onConnect={onConnect} | onConnect={onConnect} | ||||
| nodeTypes={nodeTypes} | nodeTypes={nodeTypes} | ||||
| edgeTypes={edgeTypes} | |||||
| onPaneClick={onPaneClick} | onPaneClick={onPaneClick} | ||||
| onDrop={onDrop} | onDrop={onDrop} | ||||
| onDragOver={onDragOver} | onDragOver={onDragOver} | ||||
| onNodeClick={onNodeClick} | onNodeClick={onNodeClick} | ||||
| onInit={setReactFlowInstance} | onInit={setReactFlowInstance} | ||||
| onKeyUp={handleKeyUp} | onKeyUp={handleKeyUp} | ||||
| onSelectionChange={onSelectionChange} | |||||
| nodeOrigin={[0.5, 0]} | |||||
| defaultEdgeOptions={{ | |||||
| type: 'buttonEdge', | |||||
| markerEnd: { | |||||
| type: MarkerType.ArrowClosed, | |||||
| }, | |||||
| }} | |||||
| > | > | ||||
| <Background /> | <Background /> | ||||
| <Controls /> | <Controls /> | ||||
| <NodeContextMenu onClick={onPaneClick} {...(menu as any)} /> | <NodeContextMenu onClick={onPaneClick} {...(menu as any)} /> | ||||
| )} | )} | ||||
| </ReactFlow> | </ReactFlow> | ||||
| <FlowDrawer visible={drawerVisible} hideModal={hideDrawer}></FlowDrawer> | |||||
| <FlowDrawer | |||||
| node={clickedNode} | |||||
| visible={drawerVisible} | |||||
| hideModal={hideDrawer} | |||||
| ></FlowDrawer> | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } |
| .textUpdaterNode { | .textUpdaterNode { | ||||
| // height: 50px; | // height: 50px; | ||||
| border: 1px solid black; | |||||
| border: 1px solid gray; | |||||
| padding: 5px; | padding: 5px; | ||||
| border-radius: 5px; | border-radius: 5px; | ||||
| background: white; | background: white; | ||||
| font-size: 12px; | font-size: 12px; | ||||
| } | } | ||||
| } | } | ||||
| .selectedNode { | |||||
| border-color: #1677ff; | |||||
| } | |||||
| .handle { | |||||
| display: inline-flex; | |||||
| text-align: center; | |||||
| // align-items: center; | |||||
| } |
| import classNames from 'classnames'; | |||||
| import { Handle, NodeProps, Position } from 'reactflow'; | import { Handle, NodeProps, Position } from 'reactflow'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| export function TextUpdaterNode({ | export function TextUpdaterNode({ | ||||
| data, | data, | ||||
| isConnectable = true, | isConnectable = true, | ||||
| selected, | |||||
| }: NodeProps<{ label: string }>) { | }: NodeProps<{ label: string }>) { | ||||
| return ( | return ( | ||||
| <div className={styles.textUpdaterNode}> | |||||
| <div | |||||
| className={classNames(styles.textUpdaterNode, { | |||||
| [styles.selectedNode]: selected, | |||||
| })} | |||||
| > | |||||
| <Handle | <Handle | ||||
| type="target" | type="target" | ||||
| position={Position.Left} | position={Position.Left} | ||||
| isConnectable={isConnectable} | isConnectable={isConnectable} | ||||
| /> | |||||
| className={styles.handle} | |||||
| > | |||||
| {/* <PlusCircleOutlined style={{ fontSize: 10 }} /> */} | |||||
| </Handle> | |||||
| <Handle | <Handle | ||||
| type="source" | type="source" | ||||
| position={Position.Right} | position={Position.Right} | ||||
| isConnectable={isConnectable} | isConnectable={isConnectable} | ||||
| /> | |||||
| className={styles.handle} | |||||
| > | |||||
| {/* <PlusCircleOutlined style={{ fontSize: 10 }} /> */} | |||||
| </Handle> | |||||
| <div>{data.label}</div> | <div>{data.label}</div> | ||||
| </div> | </div> | ||||
| ); | ); |
| export enum Operator { | |||||
| Begin = 'Begin', | |||||
| Retrieval = 'Retrieval', | |||||
| Generate = 'Generate', | |||||
| Answer = 'Answer', | |||||
| } |
| import { IModalProps } from '@/interfaces/common'; | import { IModalProps } from '@/interfaces/common'; | ||||
| import { Drawer } from 'antd'; | import { Drawer } from 'antd'; | ||||
| import { Node } from 'reactflow'; | |||||
| import AnswerForm from '../answer-form'; | |||||
| import BeginForm from '../begin-form'; | |||||
| import { Operator } from '../constant'; | |||||
| import GenerateForm from '../generate-form'; | |||||
| import { useHandleFormValuesChange } from '../hooks'; | |||||
| import RetrievalForm from '../retrieval-form'; | |||||
| interface IProps { | |||||
| node?: Node; | |||||
| } | |||||
| const FormMap = { | |||||
| [Operator.Begin]: BeginForm, | |||||
| [Operator.Retrieval]: RetrievalForm, | |||||
| [Operator.Generate]: GenerateForm, | |||||
| [Operator.Answer]: AnswerForm, | |||||
| }; | |||||
| const FlowDrawer = ({ | |||||
| visible, | |||||
| hideModal, | |||||
| node, | |||||
| }: IModalProps<any> & IProps) => { | |||||
| const operatorName: Operator = node?.data.label; | |||||
| const OperatorForm = FormMap[operatorName]; | |||||
| const { handleValuesChange } = useHandleFormValuesChange(node?.id); | |||||
| const FlowDrawer = ({ visible, hideModal }: IModalProps<any>) => { | |||||
| return ( | return ( | ||||
| <Drawer | <Drawer | ||||
| title="Basic Drawer" | |||||
| title={node?.data.label} | |||||
| placement="right" | placement="right" | ||||
| // closable={false} | |||||
| onClose={hideModal} | onClose={hideModal} | ||||
| open={visible} | open={visible} | ||||
| getContainer={false} | getContainer={false} | ||||
| mask={false} | mask={false} | ||||
| width={470} | |||||
| > | > | ||||
| <p>Some contents...</p> | |||||
| {visible && ( | |||||
| <OperatorForm onValuesChange={handleValuesChange}></OperatorForm> | |||||
| )} | |||||
| </Drawer> | </Drawer> | ||||
| ); | ); | ||||
| }; | }; |
| import LlmSettingItems from '@/components/llm-setting-items'; | |||||
| import { | |||||
| ModelVariableType, | |||||
| settledModelVariableMap, | |||||
| } from '@/constants/knowledge'; | |||||
| import { useTranslate } from '@/hooks/commonHooks'; | |||||
| import { Variable } from '@/interfaces/database/chat'; | |||||
| import { variableEnabledFieldMap } from '@/pages/chat/constants'; | |||||
| import { Form, Input, Switch } from 'antd'; | |||||
| import { useCallback, useEffect } from 'react'; | |||||
| import { IOperatorForm } from '../interface'; | |||||
| const GenerateForm = ({ onValuesChange }: IOperatorForm) => { | |||||
| const { t } = useTranslate('flow'); | |||||
| const [form] = Form.useForm(); | |||||
| const initialLlmSetting = undefined; | |||||
| const handleParametersChange = useCallback( | |||||
| (value: ModelVariableType) => { | |||||
| const variable = settledModelVariableMap[value]; | |||||
| form.setFieldsValue(variable); | |||||
| }, | |||||
| [form], | |||||
| ); | |||||
| useEffect(() => { | |||||
| const switchBoxValues = Object.keys(variableEnabledFieldMap).reduce< | |||||
| Record<string, boolean> | |||||
| >((pre, field) => { | |||||
| pre[field] = | |||||
| initialLlmSetting === undefined | |||||
| ? true | |||||
| : !!initialLlmSetting[ | |||||
| variableEnabledFieldMap[ | |||||
| field as keyof typeof variableEnabledFieldMap | |||||
| ] as keyof Variable | |||||
| ]; | |||||
| return pre; | |||||
| }, {}); | |||||
| const otherValues = settledModelVariableMap[ModelVariableType.Precise]; | |||||
| form.setFieldsValue({ ...switchBoxValues, ...otherValues }); | |||||
| }, [form, initialLlmSetting]); | |||||
| return ( | |||||
| <Form | |||||
| name="basic" | |||||
| labelCol={{ span: 9 }} | |||||
| wrapperCol={{ span: 15 }} | |||||
| autoComplete="off" | |||||
| form={form} | |||||
| onValuesChange={onValuesChange} | |||||
| > | |||||
| <LlmSettingItems | |||||
| handleParametersChange={handleParametersChange} | |||||
| ></LlmSettingItems> | |||||
| <Form.Item | |||||
| name={['prompt']} | |||||
| label={t('prompt', { keyPrefix: 'knowledgeConfiguration' })} | |||||
| initialValue={t('promptText', { keyPrefix: 'knowledgeConfiguration' })} | |||||
| tooltip={t('promptTip', { keyPrefix: 'knowledgeConfiguration' })} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: t('promptMessage'), | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input.TextArea rows={8} /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| name={['cite']} | |||||
| label={t('cite')} | |||||
| initialValue={true} | |||||
| valuePropName="checked" | |||||
| tooltip={t('citeTip')} | |||||
| > | |||||
| <Switch /> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| ); | |||||
| }; | |||||
| export default GenerateForm; |
| import { useSetModalState } from '@/hooks/commonHooks'; | import { useSetModalState } from '@/hooks/commonHooks'; | ||||
| import React, { | |||||
| Dispatch, | |||||
| KeyboardEventHandler, | |||||
| SetStateAction, | |||||
| useCallback, | |||||
| useState, | |||||
| } from 'react'; | |||||
| import { | |||||
| Node, | |||||
| Position, | |||||
| ReactFlowInstance, | |||||
| useOnSelectionChange, | |||||
| useReactFlow, | |||||
| } from 'reactflow'; | |||||
| import { useFetchFlowTemplates } from '@/hooks/flow-hooks'; | |||||
| import { useFetchLlmList } from '@/hooks/llmHooks'; | |||||
| import React, { KeyboardEventHandler, useCallback, useState } from 'react'; | |||||
| import { Node, Position, ReactFlowInstance } from 'reactflow'; | |||||
| import { v4 as uuidv4 } from 'uuid'; | import { v4 as uuidv4 } from 'uuid'; | ||||
| import useStore, { RFState } from './store'; | |||||
| import { buildDslComponentsByGraph } from './utils'; | |||||
| const selector = (state: RFState) => ({ | |||||
| nodes: state.nodes, | |||||
| edges: state.edges, | |||||
| onNodesChange: state.onNodesChange, | |||||
| onEdgesChange: state.onEdgesChange, | |||||
| onConnect: state.onConnect, | |||||
| setNodes: state.setNodes, | |||||
| onSelectionChange: state.onSelectionChange, | |||||
| }); | |||||
| export const useSelectCanvasData = () => { | |||||
| // return useStore(useShallow(selector)); throw error | |||||
| return useStore(selector); | |||||
| }; | |||||
| export const useHandleDrag = () => { | export const useHandleDrag = () => { | ||||
| const handleDragStart = useCallback( | const handleDragStart = useCallback( | ||||
| return { handleDragStart }; | return { handleDragStart }; | ||||
| }; | }; | ||||
| export const useHandleDrop = (setNodes: Dispatch<SetStateAction<Node[]>>) => { | |||||
| export const useHandleDrop = () => { | |||||
| const addNode = useStore((state) => state.addNode); | |||||
| const [reactFlowInstance, setReactFlowInstance] = | const [reactFlowInstance, setReactFlowInstance] = | ||||
| useState<ReactFlowInstance<any, any>>(); | useState<ReactFlowInstance<any, any>>(); | ||||
| targetPosition: Position.Left, | targetPosition: Position.Left, | ||||
| }; | }; | ||||
| setNodes((nds) => nds.concat(newNode)); | |||||
| addNode(newNode); | |||||
| }, | }, | ||||
| [reactFlowInstance, setNodes], | |||||
| [reactFlowInstance, addNode], | |||||
| ); | ); | ||||
| return { onDrop, onDragOver, setReactFlowInstance }; | return { onDrop, onDragOver, setReactFlowInstance }; | ||||
| }; | }; | ||||
| export const useShowDrawer = () => { | export const useShowDrawer = () => { | ||||
| const [clickedNode, setClickedNode] = useState<Node>(); | |||||
| const { | const { | ||||
| visible: drawerVisible, | visible: drawerVisible, | ||||
| hideModal: hideDrawer, | hideModal: hideDrawer, | ||||
| showModal: showDrawer, | showModal: showDrawer, | ||||
| } = useSetModalState(); | } = useSetModalState(); | ||||
| const handleShow = useCallback( | |||||
| (node: Node) => { | |||||
| setClickedNode(node); | |||||
| showDrawer(); | |||||
| }, | |||||
| [showDrawer], | |||||
| ); | |||||
| return { | return { | ||||
| drawerVisible, | drawerVisible, | ||||
| hideDrawer, | hideDrawer, | ||||
| showDrawer, | |||||
| showDrawer: handleShow, | |||||
| clickedNode, | |||||
| }; | }; | ||||
| }; | }; | ||||
| export const useHandleSelectionChange = () => { | |||||
| const [selectedNodes, setSelectedNodes] = useState<string[]>([]); | |||||
| const [selectedEdges, setSelectedEdges] = useState<string[]>([]); | |||||
| useOnSelectionChange({ | |||||
| onChange: ({ nodes, edges }) => { | |||||
| setSelectedNodes(nodes.map((node) => node.id)); | |||||
| setSelectedEdges(edges.map((edge) => edge.id)); | |||||
| }, | |||||
| }); | |||||
| return { selectedEdges, selectedNodes }; | |||||
| }; | |||||
| export const useDeleteEdge = (selectedEdges: string[]) => { | |||||
| const { setEdges } = useReactFlow(); | |||||
| const deleteEdge = useCallback(() => { | |||||
| setEdges((edges) => | |||||
| edges.filter((edge) => selectedEdges.every((x) => x !== edge.id)), | |||||
| ); | |||||
| }, [setEdges, selectedEdges]); | |||||
| return deleteEdge; | |||||
| }; | |||||
| export const useHandleKeyUp = ( | |||||
| selectedEdges: string[], | |||||
| selectedNodes: string[], | |||||
| ) => { | |||||
| const deleteEdge = useDeleteEdge(selectedEdges); | |||||
| export const useHandleKeyUp = () => { | |||||
| const deleteEdge = useStore((state) => state.deleteEdge); | |||||
| const handleKeyUp: KeyboardEventHandler = useCallback( | const handleKeyUp: KeyboardEventHandler = useCallback( | ||||
| (e) => { | (e) => { | ||||
| if (e.code === 'Delete') { | if (e.code === 'Delete') { | ||||
| }; | }; | ||||
| export const useSaveGraph = () => { | export const useSaveGraph = () => { | ||||
| const saveGraph = useCallback(() => {}, []); | |||||
| const { nodes, edges } = useStore((state) => state); | |||||
| const saveGraph = useCallback(() => { | |||||
| const x = buildDslComponentsByGraph(nodes, edges); | |||||
| console.info('components:', x); | |||||
| }, [nodes, edges]); | |||||
| return { saveGraph }; | return { saveGraph }; | ||||
| }; | }; | ||||
| export const useHandleFormValuesChange = (id?: string) => { | |||||
| const updateNodeForm = useStore((state) => state.updateNodeForm); | |||||
| const handleValuesChange = useCallback( | |||||
| (changedValues: any, values: any) => { | |||||
| console.info(changedValues, values); | |||||
| if (id) { | |||||
| updateNodeForm(id, values); | |||||
| } | |||||
| }, | |||||
| [updateNodeForm, id], | |||||
| ); | |||||
| return { handleValuesChange }; | |||||
| }; | |||||
| export const useFetchDataOnMount = () => { | |||||
| useFetchFlowTemplates(); | |||||
| useFetchLlmList(); | |||||
| }; |
| import FlowCanvas from './canvas'; | import FlowCanvas from './canvas'; | ||||
| import Sider from './flow-sider'; | import Sider from './flow-sider'; | ||||
| import FlowHeader from './header'; | import FlowHeader from './header'; | ||||
| import { useFetchDataOnMount } from './hooks'; | |||||
| const { Content } = Layout; | const { Content } = Layout; | ||||
| function RagFlow() { | function RagFlow() { | ||||
| const [collapsed, setCollapsed] = useState(false); | const [collapsed, setCollapsed] = useState(false); | ||||
| useFetchDataOnMount(); | |||||
| return ( | return ( | ||||
| <Layout> | <Layout> | ||||
| <ReactFlowProvider> | <ReactFlowProvider> | ||||
| <Sider setCollapsed={setCollapsed} collapsed={collapsed}></Sider> | <Sider setCollapsed={setCollapsed} collapsed={collapsed}></Sider> | ||||
| <Layout> | <Layout> | ||||
| <FlowHeader></FlowHeader> | <FlowHeader></FlowHeader> | ||||
| <Content style={{ margin: '0 16px' }}> | |||||
| <Content style={{ margin: 0 }}> | |||||
| <FlowCanvas sideWidth={collapsed ? 0 : 200}></FlowCanvas> | <FlowCanvas sideWidth={collapsed ? 0 : 200}></FlowCanvas> | ||||
| </Content> | </Content> | ||||
| </Layout> | </Layout> |
| import { Edge, Node } from 'reactflow'; | |||||
| export interface DSLComponentList { | export interface DSLComponentList { | ||||
| id: string; | id: string; | ||||
| name: string; | name: string; | ||||
| } | } | ||||
| export interface IOperatorForm { | |||||
| onValuesChange?(changedValues: any, values: any): void; | |||||
| } | |||||
| export interface IBeginForm { | |||||
| prologue?: string; | |||||
| } | |||||
| export interface IRetrievalForm { | |||||
| similarity_threshold?: number; | |||||
| keywords_similarity_weight?: number; | |||||
| top_n?: number; | |||||
| top_k?: number; | |||||
| rerank_id?: string; | |||||
| empty_response?: string; | |||||
| kb_ids: string[]; | |||||
| } | |||||
| export interface IGenerateForm { | |||||
| max_tokens?: number; | |||||
| temperature?: number; | |||||
| top_p?: number; | |||||
| presence_penalty?: number; | |||||
| frequency_penalty?: number; | |||||
| cite?: boolean; | |||||
| prompt: number; | |||||
| llm_id: string; | |||||
| parameters: { key: string; component_id: string }; | |||||
| } | |||||
| export type NodeData = { | |||||
| label: string; | |||||
| color: string; | |||||
| form: IBeginForm | IRetrievalForm | IGenerateForm; | |||||
| }; | |||||
| export interface IFlow { | |||||
| avatar: null; | |||||
| canvas_type: null; | |||||
| create_date: string; | |||||
| create_time: number; | |||||
| description: null; | |||||
| dsl: { | |||||
| answer: any[]; | |||||
| components: DSLComponentList; | |||||
| graph: { nodes: Node[]; edges: Edge[] }; | |||||
| history: any[]; | |||||
| path: string[]; | |||||
| }; | |||||
| id: string; | |||||
| title: string; | |||||
| update_date: string; | |||||
| update_time: number; | |||||
| user_id: string; | |||||
| } |
| .container { | |||||
| height: 251px; | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| justify-content: space-between; | |||||
| .delete { | |||||
| height: 24px; | |||||
| } | |||||
| .content { | |||||
| display: flex; | |||||
| justify-content: space-between; | |||||
| .context { | |||||
| flex: 1; | |||||
| } | |||||
| } | |||||
| .footer { | |||||
| // text-align: left; | |||||
| } | |||||
| .footerTop { | |||||
| padding-bottom: 2px; | |||||
| } | |||||
| } | |||||
| .card { | |||||
| border-radius: 12px; | |||||
| border: 1px solid rgba(234, 236, 240, 1); | |||||
| box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05); | |||||
| padding: 24px; | |||||
| width: 300px; | |||||
| cursor: pointer; | |||||
| .titleWrapper { | |||||
| // flex: 1; | |||||
| .title { | |||||
| font-size: 24px; | |||||
| line-height: 32px; | |||||
| font-weight: 600; | |||||
| color: rgba(0, 0, 0, 0.88); | |||||
| word-break: break-all; | |||||
| } | |||||
| .description { | |||||
| font-size: 12px; | |||||
| font-weight: 600; | |||||
| line-height: 20px; | |||||
| color: rgba(0, 0, 0, 0.45); | |||||
| } | |||||
| } | |||||
| :global { | |||||
| .ant-card-body { | |||||
| padding: 0; | |||||
| margin: 0; | |||||
| } | |||||
| } | |||||
| .bottom { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| } | |||||
| .bottomLeft { | |||||
| vertical-align: middle; | |||||
| } | |||||
| .leftIcon { | |||||
| margin-right: 10px; | |||||
| font-size: 18px; | |||||
| vertical-align: middle; | |||||
| } | |||||
| .rightText { | |||||
| font-size: 12px; | |||||
| font-weight: 600; | |||||
| color: rgba(0, 0, 0, 0.65); | |||||
| vertical-align: middle; | |||||
| } | |||||
| } |
| import { ReactComponent as MoreIcon } from '@/assets/svg/more.svg'; | |||||
| import { useShowDeleteConfirm } from '@/hooks/commonHooks'; | |||||
| import { formatDate } from '@/utils/date'; | |||||
| import { | |||||
| CalendarOutlined, | |||||
| DeleteOutlined, | |||||
| UserOutlined, | |||||
| } from '@ant-design/icons'; | |||||
| import { Avatar, Card, Dropdown, MenuProps, Space } from 'antd'; | |||||
| import { useTranslation } from 'react-i18next'; | |||||
| import { useNavigate } from 'umi'; | |||||
| import { useDeleteFlow } from '@/hooks/flow-hooks'; | |||||
| import { IFlow } from '../../interface'; | |||||
| import styles from './index.less'; | |||||
| interface IProps { | |||||
| item: IFlow; | |||||
| } | |||||
| const FlowCard = ({ item }: IProps) => { | |||||
| const navigate = useNavigate(); | |||||
| const showDeleteConfirm = useShowDeleteConfirm(); | |||||
| const { t } = useTranslation(); | |||||
| const { deleteFlow } = useDeleteFlow(); | |||||
| const removeKnowledge = () => { | |||||
| return deleteFlow([item.id]); | |||||
| }; | |||||
| const handleDelete = () => { | |||||
| showDeleteConfirm({ onOk: removeKnowledge }); | |||||
| }; | |||||
| const items: MenuProps['items'] = [ | |||||
| { | |||||
| key: '1', | |||||
| label: ( | |||||
| <Space> | |||||
| {t('common.delete')} | |||||
| <DeleteOutlined /> | |||||
| </Space> | |||||
| ), | |||||
| }, | |||||
| ]; | |||||
| const handleDropdownMenuClick: MenuProps['onClick'] = ({ domEvent, key }) => { | |||||
| domEvent.preventDefault(); | |||||
| domEvent.stopPropagation(); | |||||
| if (key === '1') { | |||||
| handleDelete(); | |||||
| } | |||||
| }; | |||||
| const handleCardClick = () => { | |||||
| navigate(`/flow/${item.id}`); | |||||
| }; | |||||
| return ( | |||||
| <Card className={styles.card} onClick={handleCardClick}> | |||||
| <div className={styles.container}> | |||||
| <div className={styles.content}> | |||||
| <Avatar size={34} icon={<UserOutlined />} src={item.avatar} /> | |||||
| <Dropdown | |||||
| menu={{ | |||||
| items, | |||||
| onClick: handleDropdownMenuClick, | |||||
| }} | |||||
| > | |||||
| <span className={styles.delete}> | |||||
| <MoreIcon /> | |||||
| </span> | |||||
| </Dropdown> | |||||
| </div> | |||||
| <div className={styles.titleWrapper}> | |||||
| <span className={styles.title}>{item.title}</span> | |||||
| <p>{item.description}</p> | |||||
| </div> | |||||
| <div className={styles.footer}> | |||||
| <div className={styles.bottom}> | |||||
| <div className={styles.bottomLeft}> | |||||
| <CalendarOutlined className={styles.leftIcon} /> | |||||
| <span className={styles.rightText}> | |||||
| {formatDate(item.update_time)} | |||||
| </span> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </Card> | |||||
| ); | |||||
| }; | |||||
| export default FlowCard; |
| import { useSetModalState } from '@/hooks/commonHooks'; | |||||
| import { useFetchFlowList, useSetFlow } from '@/hooks/flow-hooks'; | |||||
| import { useCallback, useState } from 'react'; | |||||
| import { dsl } from '../mock'; | |||||
| export const useFetchDataOnMount = () => { | |||||
| const { data, loading } = useFetchFlowList(); | |||||
| return { list: data, loading }; | |||||
| }; | |||||
| export const useSaveFlow = () => { | |||||
| const [currentFlow, setCurrentFlow] = useState({}); | |||||
| const { | |||||
| visible: flowSettingVisible, | |||||
| hideModal: hideFlowSettingModal, | |||||
| showModal: showFileRenameModal, | |||||
| } = useSetModalState(); | |||||
| const { loading, setFlow } = useSetFlow(); | |||||
| const onFlowOk = useCallback( | |||||
| async (title: string) => { | |||||
| const ret = await setFlow({ title, dsl }); | |||||
| if (ret === 0) { | |||||
| hideFlowSettingModal(); | |||||
| } | |||||
| }, | |||||
| [setFlow, hideFlowSettingModal], | |||||
| ); | |||||
| const handleShowFlowSettingModal = useCallback( | |||||
| async (record: any) => { | |||||
| setCurrentFlow(record); | |||||
| showFileRenameModal(); | |||||
| }, | |||||
| [showFileRenameModal], | |||||
| ); | |||||
| return { | |||||
| flowSettingLoading: loading, | |||||
| initialFlowName: '', | |||||
| onFlowOk, | |||||
| flowSettingVisible, | |||||
| hideFlowSettingModal, | |||||
| showFlowSettingModal: handleShowFlowSettingModal, | |||||
| }; | |||||
| }; |
| .flowListWrapper { | |||||
| padding: 48px; | |||||
| } | |||||
| .topWrapper { | |||||
| display: flex; | |||||
| justify-content: space-between; | |||||
| align-items: flex-start; | |||||
| padding: 0 60px 72px; | |||||
| .title { | |||||
| font-family: Inter; | |||||
| font-size: 30px; | |||||
| font-style: normal; | |||||
| font-weight: @fontWeight600; | |||||
| line-height: 38px; | |||||
| color: rgba(16, 24, 40, 1); | |||||
| } | |||||
| .description { | |||||
| font-family: Inter; | |||||
| font-size: 16px; | |||||
| font-style: normal; | |||||
| font-weight: 400; | |||||
| line-height: 24px; | |||||
| color: rgba(71, 84, 103, 1); | |||||
| } | |||||
| .topButton { | |||||
| font-family: Inter; | |||||
| font-size: 14px; | |||||
| font-style: normal; | |||||
| font-weight: @fontWeight600; | |||||
| line-height: 20px; | |||||
| } | |||||
| .filterButton { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| .topButton(); | |||||
| } | |||||
| } | |||||
| .flowCardContainer { | |||||
| padding: 0 60px; | |||||
| overflow: auto; | |||||
| .knowledgeEmpty { | |||||
| width: 100%; | |||||
| } | |||||
| } |
| import RenameModal from '@/components/rename-modal'; | |||||
| import { PlusOutlined } from '@ant-design/icons'; | |||||
| import { Button, Empty, Flex, Spin } from 'antd'; | |||||
| import FlowCard from './flow-card'; | |||||
| import { useFetchDataOnMount, useSaveFlow } from './hooks'; | |||||
| import styles from './index.less'; | |||||
| const FlowList = () => { | |||||
| const { | |||||
| showFlowSettingModal, | |||||
| hideFlowSettingModal, | |||||
| flowSettingVisible, | |||||
| flowSettingLoading, | |||||
| onFlowOk, | |||||
| } = useSaveFlow(); | |||||
| const { list, loading } = useFetchDataOnMount(); | |||||
| return ( | |||||
| <Flex className={styles.flowListWrapper} vertical flex={1} gap={'large'}> | |||||
| <Flex justify={'end'}> | |||||
| <Button | |||||
| type="primary" | |||||
| icon={<PlusOutlined />} | |||||
| onClick={showFlowSettingModal} | |||||
| > | |||||
| create canvas | |||||
| </Button> | |||||
| </Flex> | |||||
| <Spin spinning={loading}> | |||||
| <Flex gap={'large'} wrap="wrap" className={styles.flowCardContainer}> | |||||
| {list.length > 0 ? ( | |||||
| list.map((item: any) => { | |||||
| return <FlowCard item={item} key={item.name}></FlowCard>; | |||||
| }) | |||||
| ) : ( | |||||
| <Empty className={styles.knowledgeEmpty}></Empty> | |||||
| )} | |||||
| </Flex> | |||||
| </Spin> | |||||
| <RenameModal | |||||
| visible={flowSettingVisible} | |||||
| onOk={onFlowOk} | |||||
| loading={flowSettingLoading} | |||||
| hideModal={hideFlowSettingModal} | |||||
| initialName="" | |||||
| ></RenameModal> | |||||
| </Flex> | |||||
| ); | |||||
| }; | |||||
| export default FlowList; |
| import { | |||||
| MergeCellsOutlined, | |||||
| RocketOutlined, | |||||
| SendOutlined, | |||||
| } from '@ant-design/icons'; | |||||
| import { MergeCellsOutlined, RocketOutlined } from '@ant-design/icons'; | |||||
| import { Position } from 'reactflow'; | import { Position } from 'reactflow'; | ||||
| export const componentList = [ | export const componentList = [ | ||||
| { name: 'Begin', icon: <SendOutlined />, description: '' }, | |||||
| { name: 'Retrieval', icon: <RocketOutlined />, description: '' }, | { name: 'Retrieval', icon: <RocketOutlined />, description: '' }, | ||||
| { name: 'Generate', icon: <MergeCellsOutlined />, description: '' }, | { name: 'Generate', icon: <MergeCellsOutlined />, description: '' }, | ||||
| ]; | ]; | ||||
| 'Retrieval:China': { | 'Retrieval:China': { | ||||
| obj: { | obj: { | ||||
| component_name: 'Retrieval', | component_name: 'Retrieval', | ||||
| params: {}, | |||||
| params: { | |||||
| similarity_threshold: 0.2, | |||||
| keywords_similarity_weight: 0.3, | |||||
| top_n: 6, | |||||
| top_k: 1024, | |||||
| rerank_id: 'BAAI/bge-reranker-v2-m3', | |||||
| kb_ids: ['568aa82603b611efa9d9fa163e197198'], | |||||
| }, | |||||
| }, | }, | ||||
| downstream: ['Generate:China'], | downstream: ['Generate:China'], | ||||
| upstream: ['Answer:China'], | upstream: ['Answer:China'], | ||||
| 'Generate:China': { | 'Generate:China': { | ||||
| obj: { | obj: { | ||||
| component_name: 'Generate', | component_name: 'Generate', | ||||
| params: {}, | |||||
| params: { | |||||
| llm_id: 'deepseek-chat', | |||||
| prompt: | |||||
| 'You are an intelligent assistant. Please summarize the content of the knowledge base to answer the question. Please list the data in the knowledge base and answer in detail. When all knowledge base content is irrelevant to the question, your answer must include the sentence "The answer you are looking for is not found in the knowledge base!" Answers need to consider chat history.\n Here is the knowledge base:\n {input}\n The above is the knowledge base.', | |||||
| temperature: 0.2, | |||||
| }, | |||||
| }, | }, | ||||
| downstream: ['Answer:China'], | downstream: ['Answer:China'], | ||||
| upstream: ['Retrieval:China'], | upstream: ['Retrieval:China'], |
| import KnowledgeBaseItem from '@/components/knowledge-base-item'; | |||||
| import Rerank from '@/components/rerank'; | |||||
| import SimilaritySlider from '@/components/similarity-slider'; | |||||
| import TopNItem from '@/components/top-n-item'; | |||||
| import type { FormProps } from 'antd'; | |||||
| import { Form } from 'antd'; | |||||
| import { IOperatorForm } from '../interface'; | |||||
| type FieldType = { | |||||
| top_n?: number; | |||||
| }; | |||||
| const onFinish: FormProps<FieldType>['onFinish'] = (values) => { | |||||
| console.log('Success:', values); | |||||
| }; | |||||
| const onFinishFailed: FormProps<FieldType>['onFinishFailed'] = (errorInfo) => { | |||||
| console.log('Failed:', errorInfo); | |||||
| }; | |||||
| const RetrievalForm = ({ onValuesChange }: IOperatorForm) => { | |||||
| const [form] = Form.useForm(); | |||||
| return ( | |||||
| <Form | |||||
| name="basic" | |||||
| labelCol={{ span: 12 }} | |||||
| wrapperCol={{ span: 12 }} | |||||
| onFinish={onFinish} | |||||
| onFinishFailed={onFinishFailed} | |||||
| autoComplete="off" | |||||
| onValuesChange={onValuesChange} | |||||
| form={form} | |||||
| > | |||||
| <SimilaritySlider isTooltipShown></SimilaritySlider> | |||||
| <TopNItem></TopNItem> | |||||
| <Rerank></Rerank> | |||||
| <KnowledgeBaseItem></KnowledgeBaseItem> | |||||
| </Form> | |||||
| ); | |||||
| }; | |||||
| export default RetrievalForm; |
| import type {} from '@redux-devtools/extension'; | |||||
| import { | |||||
| Connection, | |||||
| Edge, | |||||
| EdgeChange, | |||||
| Node, | |||||
| NodeChange, | |||||
| OnConnect, | |||||
| OnEdgesChange, | |||||
| OnNodesChange, | |||||
| OnSelectionChangeFunc, | |||||
| OnSelectionChangeParams, | |||||
| addEdge, | |||||
| applyEdgeChanges, | |||||
| applyNodeChanges, | |||||
| } from 'reactflow'; | |||||
| import { create } from 'zustand'; | |||||
| import { devtools } from 'zustand/middleware'; | |||||
| import { NodeData } from './interface'; | |||||
| import { dsl } from './mock'; | |||||
| const { nodes: initialNodes, edges: initialEdges } = dsl.graph; | |||||
| export type RFState = { | |||||
| nodes: Node<NodeData>[]; | |||||
| edges: Edge[]; | |||||
| selectedNodeIds: string[]; | |||||
| selectedEdgeIds: string[]; | |||||
| onNodesChange: OnNodesChange; | |||||
| onEdgesChange: OnEdgesChange; | |||||
| onConnect: OnConnect; | |||||
| setNodes: (nodes: Node[]) => void; | |||||
| setEdges: (edges: Edge[]) => void; | |||||
| updateNodeForm: (nodeId: string, values: any) => void; | |||||
| onSelectionChange: OnSelectionChangeFunc; | |||||
| addNode: (nodes: Node) => void; | |||||
| deleteEdge: () => void; | |||||
| deleteEdgeById: (id: string) => void; | |||||
| }; | |||||
| // this is our useStore hook that we can use in our components to get parts of the store and call actions | |||||
| const useStore = create<RFState>()( | |||||
| devtools((set, get) => ({ | |||||
| nodes: initialNodes as Node[], | |||||
| edges: initialEdges as Edge[], | |||||
| selectedNodeIds: [], | |||||
| selectedEdgeIds: [], | |||||
| onNodesChange: (changes: NodeChange[]) => { | |||||
| set({ | |||||
| nodes: applyNodeChanges(changes, get().nodes), | |||||
| }); | |||||
| }, | |||||
| onEdgesChange: (changes: EdgeChange[]) => { | |||||
| set({ | |||||
| edges: applyEdgeChanges(changes, get().edges), | |||||
| }); | |||||
| }, | |||||
| onConnect: (connection: Connection) => { | |||||
| set({ | |||||
| edges: addEdge(connection, get().edges), | |||||
| }); | |||||
| }, | |||||
| onSelectionChange: ({ nodes, edges }: OnSelectionChangeParams) => { | |||||
| set({ | |||||
| selectedEdgeIds: edges.map((x) => x.id), | |||||
| selectedNodeIds: nodes.map((x) => x.id), | |||||
| }); | |||||
| }, | |||||
| setNodes: (nodes: Node[]) => { | |||||
| set({ nodes }); | |||||
| }, | |||||
| setEdges: (edges: Edge[]) => { | |||||
| set({ edges }); | |||||
| }, | |||||
| addNode: (node: Node) => { | |||||
| set({ nodes: get().nodes.concat(node) }); | |||||
| }, | |||||
| deleteEdge: () => { | |||||
| const { edges, selectedEdgeIds } = get(); | |||||
| set({ | |||||
| edges: edges.filter((edge) => | |||||
| selectedEdgeIds.every((x) => x !== edge.id), | |||||
| ), | |||||
| }); | |||||
| }, | |||||
| deleteEdgeById: (id: string) => { | |||||
| const { edges } = get(); | |||||
| set({ | |||||
| edges: edges.filter((edge) => edge.id !== id), | |||||
| }); | |||||
| }, | |||||
| updateNodeForm: (nodeId: string, values: any) => { | |||||
| set({ | |||||
| nodes: get().nodes.map((node) => { | |||||
| if (node.id === nodeId) { | |||||
| node.data = { ...node.data, form: values }; | |||||
| } | |||||
| return node; | |||||
| }), | |||||
| }); | |||||
| }, | |||||
| })), | |||||
| ); | |||||
| export default useStore; |
| import dagre from 'dagre'; | import dagre from 'dagre'; | ||||
| import { Edge, MarkerType, Node, Position } from 'reactflow'; | import { Edge, MarkerType, Node, Position } from 'reactflow'; | ||||
| import { v4 as uuidv4 } from 'uuid'; | import { v4 as uuidv4 } from 'uuid'; | ||||
| import { NodeData } from './interface'; | |||||
| const buildEdges = ( | const buildEdges = ( | ||||
| operatorIds: string[], | operatorIds: string[], | ||||
| return { nodes, edges }; | return { nodes, edges }; | ||||
| }; | }; | ||||
| const buildComponentDownstreamOrUpstream = ( | |||||
| edges: Edge[], | |||||
| nodeId: string, | |||||
| isBuildDownstream = true, | |||||
| ) => { | |||||
| return edges | |||||
| .filter((y) => y[isBuildDownstream ? 'source' : 'target'] === nodeId) | |||||
| .map((y) => y[isBuildDownstream ? 'target' : 'source']); | |||||
| }; | |||||
| // construct a dsl based on the node information of the graph | |||||
| export const buildDslComponentsByGraph = ( | |||||
| nodes: Node<NodeData>[], | |||||
| edges: Edge[], | |||||
| ): DSLComponents => { | |||||
| const components: DSLComponents = {}; | |||||
| nodes.forEach((x) => { | |||||
| const id = x.id; | |||||
| components[id] = { | |||||
| obj: { | |||||
| component_name: x.data.label, | |||||
| params: x.data.form as Record<string, unknown>, | |||||
| }, | |||||
| downstream: buildComponentDownstreamOrUpstream(edges, id, true), | |||||
| upstream: buildComponentDownstreamOrUpstream(edges, id, false), | |||||
| }; | |||||
| }); | |||||
| return components; | |||||
| }; |
| }, | }, | ||||
| { | { | ||||
| path: '/flow', | path: '/flow', | ||||
| component: '@/pages/flow/list', | |||||
| }, | |||||
| { | |||||
| path: '/flow/:id', | |||||
| component: '@/pages/flow', | component: '@/pages/flow', | ||||
| }, | }, | ||||
| ], | ], |
| import api from '@/utils/api'; | |||||
| import registerServer from '@/utils/registerServer'; | |||||
| import request from '@/utils/request'; | |||||
| const { | |||||
| getCanvas, | |||||
| setCanvas, | |||||
| listCanvas, | |||||
| resetCanvas, | |||||
| removeCanvas, | |||||
| listTemplates, | |||||
| } = api; | |||||
| const methods = { | |||||
| getCanvas: { | |||||
| url: getCanvas, | |||||
| method: 'get', | |||||
| }, | |||||
| setCanvas: { | |||||
| url: setCanvas, | |||||
| method: 'post', | |||||
| }, | |||||
| listCanvas: { | |||||
| url: listCanvas, | |||||
| method: 'get', | |||||
| }, | |||||
| resetCanvas: { | |||||
| url: resetCanvas, | |||||
| method: 'post', | |||||
| }, | |||||
| removeCanvas: { | |||||
| url: removeCanvas, | |||||
| method: 'post', | |||||
| }, | |||||
| listTemplates: { | |||||
| url: listTemplates, | |||||
| method: 'get', | |||||
| }, | |||||
| } as const; | |||||
| const chatService = registerServer<keyof typeof methods>(methods, request); | |||||
| export default chatService; |
| // system | // system | ||||
| getSystemVersion: `${api_host}/system/version`, | getSystemVersion: `${api_host}/system/version`, | ||||
| getSystemStatus: `${api_host}/system/status`, | getSystemStatus: `${api_host}/system/status`, | ||||
| // flow | |||||
| listTemplates: `${api_host}/canvas/templates`, | |||||
| listCanvas: `${api_host}/canvas/list`, | |||||
| getCanvas: `${api_host}/canvas/get`, | |||||
| removeCanvas: `${api_host}/canvas/rm`, | |||||
| setCanvas: `${api_host}/canvas/set`, | |||||
| resetCanvas: `${api_host}/canvas/reset`, | |||||
| }; | }; |
| import omit from 'lodash/omit'; | import omit from 'lodash/omit'; | ||||
| import { RequestMethod } from 'umi-request'; | import { RequestMethod } from 'umi-request'; | ||||
| type Service<T extends string> = Record<T, (params: any) => any>; | |||||
| type Service<T extends string> = Record<T, (params?: any) => any>; | |||||
| const registerServer = <T extends string>( | const registerServer = <T extends string>( | ||||
| opt: Record<T, { url: string; method: string }>, | opt: Record<T, { url: string; method: string }>, |