mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-08-13 00:28:59 +08:00
feat: add custom edge (#1061)
### 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)
This commit is contained in:
parent
b8eedbdd86
commit
39ac3b1e60
290
web/package-lock.json
generated
290
web/package-lock.json
generated
@ -10,6 +10,7 @@
|
||||
"@ant-design/pro-components": "^2.6.46",
|
||||
"@ant-design/pro-layout": "^7.17.16",
|
||||
"@js-preview/excel": "^1.7.8",
|
||||
"@tanstack/react-query": "^5.40.0",
|
||||
"ahooks": "^3.7.10",
|
||||
"antd": "^5.12.7",
|
||||
"axios": "^1.6.3",
|
||||
@ -39,10 +40,12 @@
|
||||
"umi": "^4.0.90",
|
||||
"umi-request": "^1.4.0",
|
||||
"unist-util-visit-parents": "^6.0.1",
|
||||
"uuid": "^9.0.1"
|
||||
"uuid": "^9.0.1",
|
||||
"zustand": "^4.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-dev-inspector/umi4-plugin": "^2.0.1",
|
||||
"@redux-devtools/extension": "^3.3.0",
|
||||
"@testing-library/jest-dom": "^6.4.5",
|
||||
"@testing-library/react": "^15.0.7",
|
||||
"@types/dagre": "^0.7.52",
|
||||
@ -3915,40 +3918,6 @@
|
||||
"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": {
|
||||
"version": "11.2.12",
|
||||
"resolved": "https://registry.npmmirror.com/@reactflow/controls/-/controls-11.2.12.tgz",
|
||||
@ -3963,40 +3932,6 @@
|
||||
"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": {
|
||||
"version": "11.11.2",
|
||||
"resolved": "https://registry.npmmirror.com/@reactflow/core/-/core-11.11.2.tgz",
|
||||
@ -4017,40 +3952,6 @@
|
||||
"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": {
|
||||
"version": "11.7.12",
|
||||
"resolved": "https://registry.npmmirror.com/@reactflow/minimap/-/minimap-11.7.12.tgz",
|
||||
@ -4069,40 +3970,6 @@
|
||||
"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": {
|
||||
"version": "2.2.12",
|
||||
"resolved": "https://registry.npmmirror.com/@reactflow/node-resizer/-/node-resizer-2.2.12.tgz",
|
||||
@ -4119,40 +3986,6 @@
|
||||
"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": {
|
||||
"version": "1.3.12",
|
||||
"resolved": "https://registry.npmmirror.com/@reactflow/node-toolbar/-/node-toolbar-1.3.12.tgz",
|
||||
@ -4167,38 +4000,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": {
|
||||
"use-sync-external-store": "1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.7.0"
|
||||
"@babel/runtime": "^7.23.2",
|
||||
"immutable": "^4.3.4"
|
||||
},
|
||||
"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": {
|
||||
@ -4441,6 +4253,30 @@
|
||||
"integrity": "sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA==",
|
||||
"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": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@testing-library/dom/-/dom-10.1.0.tgz",
|
||||
@ -6692,6 +6528,16 @@
|
||||
"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": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/isarray/-/isarray-0.0.1.tgz",
|
||||
@ -13621,9 +13467,20 @@
|
||||
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
|
||||
},
|
||||
"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
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
@ -26064,6 +25921,33 @@
|
||||
"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": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz",
|
||||
|
@ -15,6 +15,7 @@
|
||||
"@ant-design/pro-components": "^2.6.46",
|
||||
"@ant-design/pro-layout": "^7.17.16",
|
||||
"@js-preview/excel": "^1.7.8",
|
||||
"@tanstack/react-query": "^5.40.0",
|
||||
"ahooks": "^3.7.10",
|
||||
"antd": "^5.12.7",
|
||||
"axios": "^1.6.3",
|
||||
@ -44,10 +45,12 @@
|
||||
"umi": "^4.0.90",
|
||||
"umi-request": "^1.4.0",
|
||||
"unist-util-visit-parents": "^6.0.1",
|
||||
"uuid": "^9.0.1"
|
||||
"uuid": "^9.0.1",
|
||||
"zustand": "^4.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-dev-inspector/umi4-plugin": "^2.0.1",
|
||||
"@redux-devtools/extension": "^3.3.0",
|
||||
"@testing-library/jest-dom": "^6.4.5",
|
||||
"@testing-library/react": "^15.0.7",
|
||||
"@types/dagre": "^0.7.52",
|
||||
|
@ -6,13 +6,14 @@ import zh_HK from 'antd/locale/zh_HK';
|
||||
import React, { ReactNode, useEffect, useState } from 'react';
|
||||
import storage from './utils/authorizationUtil';
|
||||
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import dayjs from 'dayjs';
|
||||
import advancedFormat from 'dayjs/plugin/advancedFormat';
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||
import localeData from 'dayjs/plugin/localeData';
|
||||
import weekday from 'dayjs/plugin/weekday';
|
||||
import weekOfYear from 'dayjs/plugin/weekOfYear';
|
||||
import weekYear from 'dayjs/plugin/weekYear';
|
||||
import weekday from 'dayjs/plugin/weekday';
|
||||
|
||||
dayjs.extend(customParseFormat);
|
||||
dayjs.extend(advancedFormat);
|
||||
@ -27,6 +28,8 @@ const AntLanguageMap = {
|
||||
'zh-TRADITIONAL': zh_HK,
|
||||
};
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
type Locale = ConfigProviderProps['locale'];
|
||||
|
||||
const RootProvider = ({ children }: React.PropsWithChildren) => {
|
||||
@ -49,16 +52,18 @@ const RootProvider = ({ children }: React.PropsWithChildren) => {
|
||||
}, []);
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
|
37
web/src/components/knowledge-base-item.tsx
Normal file
37
web/src/components/knowledge-base-item.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
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;
|
6
web/src/components/llm-setting-items/index.less
Normal file
6
web/src/components/llm-setting-items/index.less
Normal file
@ -0,0 +1,6 @@
|
||||
.sliderInputNumber {
|
||||
width: 80px;
|
||||
}
|
||||
.variableSlider {
|
||||
width: 100%;
|
||||
}
|
259
web/src/components/llm-setting-items/index.tsx
Normal file
259
web/src/components/llm-setting-items/index.tsx
Normal file
@ -0,0 +1,259 @@
|
||||
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;
|
23
web/src/components/top-n-item.tsx
Normal file
23
web/src/components/top-n-item.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
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;
|
70
web/src/hooks/flow-hooks.ts
Normal file
70
web/src/hooks/flow-hooks.ts
Normal file
@ -0,0 +1,70 @@
|
||||
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 };
|
||||
};
|
@ -99,10 +99,14 @@ export const useFetchSystemVersion = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
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);
|
||||
}
|
||||
}, []);
|
||||
|
@ -1,4 +1,4 @@
|
||||
export type DSLComponents = Record<string, Operator>;
|
||||
export type DSLComponents = Record<string, IOperator>;
|
||||
|
||||
export interface DSL {
|
||||
components: DSLComponents;
|
||||
@ -7,13 +7,13 @@ export interface DSL {
|
||||
answer: any[];
|
||||
}
|
||||
|
||||
export interface Operator {
|
||||
obj: OperatorNode;
|
||||
export interface IOperator {
|
||||
obj: IOperatorNode;
|
||||
downstream: string[];
|
||||
upstream: string[];
|
||||
}
|
||||
|
||||
export interface OperatorNode {
|
||||
export interface IOperatorNode {
|
||||
component_name: string;
|
||||
params: Record<string, unknown>;
|
||||
}
|
||||
|
@ -541,6 +541,7 @@ The above is the content you need to summarize.`,
|
||||
preview: 'Preview',
|
||||
fileError: 'File error',
|
||||
},
|
||||
flow: { cite: 'Cite', citeTip: 'citeTip' },
|
||||
footer: {
|
||||
profile: 'All rights reserved @ React',
|
||||
},
|
||||
|
@ -1,18 +1,13 @@
|
||||
import { useFetchKnowledgeList } from '@/hooks/knowledgeHook';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Form, Input, Select, Switch, Upload } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { ISegmentedContentProps } from '../interface';
|
||||
|
||||
import KnowledgeBaseItem from '@/components/knowledge-base-item';
|
||||
import { useTranslate } from '@/hooks/commonHooks';
|
||||
import styles from './index.less';
|
||||
|
||||
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 normFile = (e: any) => {
|
||||
@ -95,24 +90,7 @@ const AssistantSetting = ({ show }: ISegmentedContentProps) => {
|
||||
>
|
||||
<Switch />
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
@ -1,16 +1,12 @@
|
||||
import {
|
||||
LlmModelType,
|
||||
ModelVariableType,
|
||||
settledModelVariableMap,
|
||||
} from '@/constants/knowledge';
|
||||
import { Divider, Flex, Form, InputNumber, Select, Slider, Switch } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import camelCase from 'lodash/camelCase';
|
||||
import { useEffect } from 'react';
|
||||
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 { variableEnabledFieldMap } from '../constants';
|
||||
import styles from './index.less';
|
||||
@ -24,14 +20,6 @@ const ModelSetting = ({
|
||||
initialLlmSetting?: Variable;
|
||||
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 variable = settledModelVariableMap[value];
|
||||
form.setFieldsValue({ llm_setting: variable });
|
||||
@ -62,7 +50,13 @@ const ModelSetting = ({
|
||||
[styles.segmentedHidden]: !show,
|
||||
})}
|
||||
>
|
||||
<Form.Item
|
||||
{visible && (
|
||||
<LlmSettingItems
|
||||
prefix="llm_setting"
|
||||
handleParametersChange={handleParametersChange}
|
||||
></LlmSettingItems>
|
||||
)}
|
||||
{/* <Form.Item
|
||||
label={t('model')}
|
||||
name="llm_id"
|
||||
tooltip={t('modelTip')}
|
||||
@ -279,7 +273,7 @@ const ModelSetting = ({
|
||||
}}
|
||||
</Form.Item>
|
||||
</Flex>
|
||||
</Form.Item>
|
||||
</Form.Item> */}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
@ -7,7 +7,6 @@ import {
|
||||
Form,
|
||||
Input,
|
||||
Row,
|
||||
Slider,
|
||||
Switch,
|
||||
Table,
|
||||
TableProps,
|
||||
@ -30,16 +29,11 @@ import {
|
||||
import { EditableCell, EditableRow } from './editable-cell';
|
||||
|
||||
import Rerank from '@/components/rerank';
|
||||
import TopNItem from '@/components/top-n-item';
|
||||
import { useTranslate } from '@/hooks/commonHooks';
|
||||
import { useSelectPromptConfigParameters } from '../hooks';
|
||||
import styles from './index.less';
|
||||
|
||||
type FieldType = {
|
||||
similarity_threshold?: number;
|
||||
vector_similarity_weight?: number;
|
||||
top_n?: number;
|
||||
};
|
||||
|
||||
const PromptEngine = (
|
||||
{ show }: ISegmentedContentProps,
|
||||
ref: ForwardedRef<Array<IPromptConfigParameters>>,
|
||||
@ -165,14 +159,7 @@ const PromptEngine = (
|
||||
</Form.Item>
|
||||
<Divider></Divider>
|
||||
<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>
|
||||
<section className={classNames(styles.variableContainer)}>
|
||||
<Row align={'middle'} justify="end">
|
||||
|
5
web/src/pages/flow/answer-form/index.tsx
Normal file
5
web/src/pages/flow/answer-form/index.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
const AnswerForm = () => {
|
||||
return <div>AnswerForm</div>;
|
||||
};
|
||||
|
||||
export default AnswerForm;
|
47
web/src/pages/flow/begin-form/index.tsx
Normal file
47
web/src/pages/flow/begin-form/index.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
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;
|
@ -86,7 +86,7 @@ export const useHandleNodeContextMenu = (sideWidth: number) => {
|
||||
|
||||
setMenu({
|
||||
id: node.id,
|
||||
top: event.clientY - 72,
|
||||
top: event.clientY - 144,
|
||||
left: event.clientX - sideWidth,
|
||||
// top: event.clientY < pane.height - 200 ? event.clientY - 72 : 0,
|
||||
// left: event.clientX < pane.width - 200 ? event.clientX : 0,
|
||||
|
15
web/src/pages/flow/canvas/edge/index.less
Normal file
15
web/src/pages/flow/canvas/edge/index.less
Normal file
@ -0,0 +1,15 @@
|
||||
.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);
|
||||
}
|
72
web/src/pages/flow/canvas/edge/index.tsx
Normal file
72
web/src/pages/flow/canvas/edge/index.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
4
web/src/pages/flow/canvas/index.less
Normal file
4
web/src/pages/flow/canvas/index.less
Normal file
@ -0,0 +1,4 @@
|
||||
.canvasWrapper {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
@ -1,76 +1,64 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import ReactFlow, {
|
||||
Background,
|
||||
Controls,
|
||||
Edge,
|
||||
Node,
|
||||
MarkerType,
|
||||
NodeMouseHandler,
|
||||
OnConnect,
|
||||
OnEdgesChange,
|
||||
OnNodesChange,
|
||||
addEdge,
|
||||
applyEdgeChanges,
|
||||
applyNodeChanges,
|
||||
} from 'reactflow';
|
||||
import 'reactflow/dist/style.css';
|
||||
|
||||
import { NodeContextMenu, useHandleNodeContextMenu } from './context-menu';
|
||||
import { ButtonEdge } from './edge';
|
||||
|
||||
import FlowDrawer from '../flow-drawer';
|
||||
import {
|
||||
useHandleDrop,
|
||||
useHandleKeyUp,
|
||||
useHandleSelectionChange,
|
||||
useSelectCanvasData,
|
||||
useShowDrawer,
|
||||
} from '../hooks';
|
||||
import { dsl } from '../mock';
|
||||
import { TextUpdaterNode } from './node';
|
||||
|
||||
import styles from './index.less';
|
||||
|
||||
const nodeTypes = { textUpdater: TextUpdaterNode };
|
||||
|
||||
const edgeTypes = {
|
||||
buttonEdge: ButtonEdge,
|
||||
};
|
||||
|
||||
interface IProps {
|
||||
sideWidth: number;
|
||||
}
|
||||
|
||||
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 } =
|
||||
useHandleNodeContextMenu(sideWidth);
|
||||
const { drawerVisible, hideDrawer, showDrawer } = useShowDrawer();
|
||||
const { drawerVisible, hideDrawer, showDrawer, clickedNode } =
|
||||
useShowDrawer();
|
||||
|
||||
const onNodesChange: OnNodesChange = useCallback(
|
||||
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
|
||||
[],
|
||||
);
|
||||
const onEdgesChange: OnEdgesChange = useCallback(
|
||||
(changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
|
||||
[],
|
||||
const onNodeClick: NodeMouseHandler = useCallback(
|
||||
(e, node) => {
|
||||
showDrawer(node);
|
||||
},
|
||||
[showDrawer],
|
||||
);
|
||||
|
||||
const onConnect: OnConnect = useCallback(
|
||||
(params) => setEdges((eds) => addEdge(params, eds)),
|
||||
[],
|
||||
);
|
||||
const { onDrop, onDragOver, setReactFlowInstance } = useHandleDrop();
|
||||
|
||||
const onNodeClick: NodeMouseHandler = useCallback(() => {
|
||||
showDrawer();
|
||||
}, [showDrawer]);
|
||||
|
||||
const { onDrop, onDragOver, setReactFlowInstance } = useHandleDrop(setNodes);
|
||||
|
||||
const { handleKeyUp } = useHandleKeyUp(selectedEdges, selectedNodes);
|
||||
|
||||
useEffect(() => {
|
||||
console.info('nodes:', nodes);
|
||||
console.info('edges:', edges);
|
||||
}, [nodes, edges]);
|
||||
const { handleKeyUp } = useHandleKeyUp();
|
||||
|
||||
return (
|
||||
<div style={{ height: '100%', width: '100%' }}>
|
||||
<div className={styles.canvasWrapper}>
|
||||
<ReactFlow
|
||||
ref={ref}
|
||||
nodes={nodes}
|
||||
@ -81,12 +69,21 @@ function FlowCanvas({ sideWidth }: IProps) {
|
||||
fitView
|
||||
onConnect={onConnect}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
onPaneClick={onPaneClick}
|
||||
onDrop={onDrop}
|
||||
onDragOver={onDragOver}
|
||||
onNodeClick={onNodeClick}
|
||||
onInit={setReactFlowInstance}
|
||||
onKeyUp={handleKeyUp}
|
||||
onSelectionChange={onSelectionChange}
|
||||
nodeOrigin={[0.5, 0]}
|
||||
defaultEdgeOptions={{
|
||||
type: 'buttonEdge',
|
||||
markerEnd: {
|
||||
type: MarkerType.ArrowClosed,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Background />
|
||||
<Controls />
|
||||
@ -94,7 +91,11 @@ function FlowCanvas({ sideWidth }: IProps) {
|
||||
<NodeContextMenu onClick={onPaneClick} {...(menu as any)} />
|
||||
)}
|
||||
</ReactFlow>
|
||||
<FlowDrawer visible={drawerVisible} hideModal={hideDrawer}></FlowDrawer>
|
||||
<FlowDrawer
|
||||
node={clickedNode}
|
||||
visible={drawerVisible}
|
||||
hideModal={hideDrawer}
|
||||
></FlowDrawer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
.textUpdaterNode {
|
||||
// height: 50px;
|
||||
border: 1px solid black;
|
||||
border: 1px solid gray;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
background: white;
|
||||
@ -10,3 +10,12 @@
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.selectedNode {
|
||||
border-color: #1677ff;
|
||||
}
|
||||
|
||||
.handle {
|
||||
display: inline-flex;
|
||||
text-align: center;
|
||||
// align-items: center;
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import classNames from 'classnames';
|
||||
import { Handle, NodeProps, Position } from 'reactflow';
|
||||
|
||||
import styles from './index.less';
|
||||
@ -5,19 +6,30 @@ import styles from './index.less';
|
||||
export function TextUpdaterNode({
|
||||
data,
|
||||
isConnectable = true,
|
||||
selected,
|
||||
}: NodeProps<{ label: string }>) {
|
||||
return (
|
||||
<div className={styles.textUpdaterNode}>
|
||||
<div
|
||||
className={classNames(styles.textUpdaterNode, {
|
||||
[styles.selectedNode]: selected,
|
||||
})}
|
||||
>
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
isConnectable={isConnectable}
|
||||
/>
|
||||
className={styles.handle}
|
||||
>
|
||||
{/* <PlusCircleOutlined style={{ fontSize: 10 }} /> */}
|
||||
</Handle>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
isConnectable={isConnectable}
|
||||
/>
|
||||
className={styles.handle}
|
||||
>
|
||||
{/* <PlusCircleOutlined style={{ fontSize: 10 }} /> */}
|
||||
</Handle>
|
||||
<div>{data.label}</div>
|
||||
</div>
|
||||
);
|
||||
|
6
web/src/pages/flow/constant.ts
Normal file
6
web/src/pages/flow/constant.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export enum Operator {
|
||||
Begin = 'Begin',
|
||||
Retrieval = 'Retrieval',
|
||||
Generate = 'Generate',
|
||||
Answer = 'Answer',
|
||||
}
|
@ -1,18 +1,46 @@
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
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 (
|
||||
<Drawer
|
||||
title="Basic Drawer"
|
||||
title={node?.data.label}
|
||||
placement="right"
|
||||
// closable={false}
|
||||
onClose={hideModal}
|
||||
open={visible}
|
||||
getContainer={false}
|
||||
mask={false}
|
||||
width={470}
|
||||
>
|
||||
<p>Some contents...</p>
|
||||
{visible && (
|
||||
<OperatorForm onValuesChange={handleValuesChange}></OperatorForm>
|
||||
)}
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
83
web/src/pages/flow/generate-form/index.tsx
Normal file
83
web/src/pages/flow/generate-form/index.tsx
Normal file
@ -0,0 +1,83 @@
|
||||
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;
|
@ -1,19 +1,26 @@
|
||||
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 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 = () => {
|
||||
const handleDragStart = useCallback(
|
||||
@ -27,7 +34,8 @@ export const useHandleDrag = () => {
|
||||
return { handleDragStart };
|
||||
};
|
||||
|
||||
export const useHandleDrop = (setNodes: Dispatch<SetStateAction<Node[]>>) => {
|
||||
export const useHandleDrop = () => {
|
||||
const addNode = useStore((state) => state.addNode);
|
||||
const [reactFlowInstance, setReactFlowInstance] =
|
||||
useState<ReactFlowInstance<any, any>>();
|
||||
|
||||
@ -66,59 +74,40 @@ export const useHandleDrop = (setNodes: Dispatch<SetStateAction<Node[]>>) => {
|
||||
targetPosition: Position.Left,
|
||||
};
|
||||
|
||||
setNodes((nds) => nds.concat(newNode));
|
||||
addNode(newNode);
|
||||
},
|
||||
[reactFlowInstance, setNodes],
|
||||
[reactFlowInstance, addNode],
|
||||
);
|
||||
|
||||
return { onDrop, onDragOver, setReactFlowInstance };
|
||||
};
|
||||
|
||||
export const useShowDrawer = () => {
|
||||
const [clickedNode, setClickedNode] = useState<Node>();
|
||||
const {
|
||||
visible: drawerVisible,
|
||||
hideModal: hideDrawer,
|
||||
showModal: showDrawer,
|
||||
} = useSetModalState();
|
||||
|
||||
const handleShow = useCallback(
|
||||
(node: Node) => {
|
||||
setClickedNode(node);
|
||||
showDrawer();
|
||||
},
|
||||
[showDrawer],
|
||||
);
|
||||
|
||||
return {
|
||||
drawerVisible,
|
||||
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(
|
||||
(e) => {
|
||||
if (e.code === 'Delete') {
|
||||
@ -132,7 +121,31 @@ export const useHandleKeyUp = (
|
||||
};
|
||||
|
||||
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 };
|
||||
};
|
||||
|
||||
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();
|
||||
};
|
||||
|
@ -4,19 +4,22 @@ import { ReactFlowProvider } from 'reactflow';
|
||||
import FlowCanvas from './canvas';
|
||||
import Sider from './flow-sider';
|
||||
import FlowHeader from './header';
|
||||
import { useFetchDataOnMount } from './hooks';
|
||||
|
||||
const { Content } = Layout;
|
||||
|
||||
function RagFlow() {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
useFetchDataOnMount();
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<ReactFlowProvider>
|
||||
<Sider setCollapsed={setCollapsed} collapsed={collapsed}></Sider>
|
||||
<Layout>
|
||||
<FlowHeader></FlowHeader>
|
||||
<Content style={{ margin: '0 16px' }}>
|
||||
<Content style={{ margin: 0 }}>
|
||||
<FlowCanvas sideWidth={collapsed ? 0 : 200}></FlowCanvas>
|
||||
</Content>
|
||||
</Layout>
|
||||
|
@ -1,4 +1,62 @@
|
||||
import { Edge, Node } from 'reactflow';
|
||||
|
||||
export interface DSLComponentList {
|
||||
id: 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;
|
||||
}
|
||||
|
78
web/src/pages/flow/list/flow-card/index.less
Normal file
78
web/src/pages/flow/list/flow-card/index.less
Normal file
@ -0,0 +1,78 @@
|
||||
.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;
|
||||
}
|
||||
}
|
94
web/src/pages/flow/list/flow-card/index.tsx
Normal file
94
web/src/pages/flow/list/flow-card/index.tsx
Normal file
@ -0,0 +1,94 @@
|
||||
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;
|
48
web/src/pages/flow/list/hooks.ts
Normal file
48
web/src/pages/flow/list/hooks.ts
Normal file
@ -0,0 +1,48 @@
|
||||
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,
|
||||
};
|
||||
};
|
48
web/src/pages/flow/list/index.less
Normal file
48
web/src/pages/flow/list/index.less
Normal file
@ -0,0 +1,48 @@
|
||||
.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%;
|
||||
}
|
||||
}
|
53
web/src/pages/flow/list/index.tsx
Normal file
53
web/src/pages/flow/list/index.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
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;
|
@ -1,12 +1,7 @@
|
||||
import {
|
||||
MergeCellsOutlined,
|
||||
RocketOutlined,
|
||||
SendOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { MergeCellsOutlined, RocketOutlined } from '@ant-design/icons';
|
||||
import { Position } from 'reactflow';
|
||||
|
||||
export const componentList = [
|
||||
{ name: 'Begin', icon: <SendOutlined />, description: '' },
|
||||
{ name: 'Retrieval', icon: <RocketOutlined />, description: '' },
|
||||
{ name: 'Generate', icon: <MergeCellsOutlined />, description: '' },
|
||||
];
|
||||
@ -159,7 +154,14 @@ export const dsl = {
|
||||
'Retrieval:China': {
|
||||
obj: {
|
||||
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'],
|
||||
upstream: ['Answer:China'],
|
||||
@ -167,7 +169,12 @@ export const dsl = {
|
||||
'Generate:China': {
|
||||
obj: {
|
||||
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'],
|
||||
upstream: ['Retrieval:China'],
|
||||
|
43
web/src/pages/flow/retrieval-form/index.tsx
Normal file
43
web/src/pages/flow/retrieval-form/index.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
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;
|
106
web/src/pages/flow/store.ts
Normal file
106
web/src/pages/flow/store.ts
Normal file
@ -0,0 +1,106 @@
|
||||
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;
|
@ -2,6 +2,7 @@ import { DSLComponents } from '@/interfaces/database/flow';
|
||||
import dagre from 'dagre';
|
||||
import { Edge, MarkerType, Node, Position } from 'reactflow';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { NodeData } from './interface';
|
||||
|
||||
const buildEdges = (
|
||||
operatorIds: string[],
|
||||
@ -96,3 +97,35 @@ export const getLayoutedElements = (
|
||||
|
||||
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;
|
||||
};
|
||||
|
@ -90,6 +90,10 @@ const routes = [
|
||||
},
|
||||
{
|
||||
path: '/flow',
|
||||
component: '@/pages/flow/list',
|
||||
},
|
||||
{
|
||||
path: '/flow/:id',
|
||||
component: '@/pages/flow',
|
||||
},
|
||||
],
|
||||
|
43
web/src/services/flow-service.ts
Normal file
43
web/src/services/flow-service.ts
Normal file
@ -0,0 +1,43 @@
|
||||
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;
|
@ -81,4 +81,12 @@ export default {
|
||||
// system
|
||||
getSystemVersion: `${api_host}/system/version`,
|
||||
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`,
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import omit from 'lodash/omit';
|
||||
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>(
|
||||
opt: Record<T, { url: string; method: string }>,
|
||||
|
Loading…
x
Reference in New Issue
Block a user