diff --git a/web/package-lock.json b/web/package-lock.json
index bf88f374..b1e23daf 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -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",
diff --git a/web/package.json b/web/package.json
index f8e8f3b6..ca6470af 100644
--- a/web/package.json
+++ b/web/package.json
@@ -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",
diff --git a/web/src/app.tsx b/web/src/app.tsx
index ff532b1d..9cbacdae 100644
--- a/web/src/app.tsx
+++ b/web/src/app.tsx
@@ -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 (
-
- {children}
-
+
+
+ {children}
+
+
);
};
diff --git a/web/src/components/knowledge-base-item.tsx b/web/src/components/knowledge-base-item.tsx
new file mode 100644
index 00000000..01cecee9
--- /dev/null
+++ b/web/src/components/knowledge-base-item.tsx
@@ -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 (
+
+
+
+ );
+};
+
+export default KnowledgeBaseItem;
diff --git a/web/src/components/llm-setting-items/index.less b/web/src/components/llm-setting-items/index.less
new file mode 100644
index 00000000..03928e74
--- /dev/null
+++ b/web/src/components/llm-setting-items/index.less
@@ -0,0 +1,6 @@
+.sliderInputNumber {
+ width: 80px;
+}
+.variableSlider {
+ width: 100%;
+}
diff --git a/web/src/components/llm-setting-items/index.tsx b/web/src/components/llm-setting-items/index.tsx
new file mode 100644
index 00000000..398d1c4c
--- /dev/null
+++ b/web/src/components/llm-setting-items/index.tsx
@@ -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 (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ {({ getFieldValue }) => {
+ const disabled = !getFieldValue('temperatureEnabled');
+ return (
+ <>
+
+
+
+
+
+
+
+
+ >
+ );
+ }}
+
+
+
+
+
+
+
+
+
+ {({ getFieldValue }) => {
+ const disabled = !getFieldValue('topPEnabled');
+ return (
+ <>
+
+
+
+
+
+
+
+
+ >
+ );
+ }}
+
+
+
+
+
+
+
+
+
+ {({ getFieldValue }) => {
+ const disabled = !getFieldValue('presencePenaltyEnabled');
+ return (
+ <>
+
+
+
+
+
+
+
+
+ >
+ );
+ }}
+
+
+
+
+
+
+
+
+
+ {({ getFieldValue }) => {
+ const disabled = !getFieldValue('frequencyPenaltyEnabled');
+ return (
+ <>
+
+
+
+
+
+
+
+
+ >
+ );
+ }}
+
+
+
+
+
+
+
+
+
+ {({ getFieldValue }) => {
+ const disabled = !getFieldValue('maxTokensEnabled');
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+ >
+ );
+ }}
+
+
+
+ >
+ );
+};
+
+export default LlmSettingItems;
diff --git a/web/src/components/top-n-item.tsx b/web/src/components/top-n-item.tsx
new file mode 100644
index 00000000..2a83b8c4
--- /dev/null
+++ b/web/src/components/top-n-item.tsx
@@ -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 (
+
+ label={t('topN')}
+ name={'top_n'}
+ initialValue={8}
+ tooltip={t('topNTip')}
+ >
+
+
+ );
+};
+
+export default TopNItem;
diff --git a/web/src/hooks/flow-hooks.ts b/web/src/hooks/flow-hooks.ts
new file mode 100644
index 00000000..64007b11
--- /dev/null
+++ b/web/src/hooks/flow-hooks.ts
@@ -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 };
+};
diff --git a/web/src/hooks/userSettingHook.ts b/web/src/hooks/userSettingHook.ts
index d2499828..b344d37f 100644
--- a/web/src/hooks/userSettingHook.ts
+++ b/web/src/hooks/userSettingHook.ts
@@ -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);
}
}, []);
diff --git a/web/src/interfaces/database/flow.ts b/web/src/interfaces/database/flow.ts
index 00f17cf7..80d24eba 100644
--- a/web/src/interfaces/database/flow.ts
+++ b/web/src/interfaces/database/flow.ts
@@ -1,4 +1,4 @@
-export type DSLComponents = Record;
+export type DSLComponents = Record;
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;
}
diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts
index 6a4254f6..7b4e2a70 100644
--- a/web/src/locales/en.ts
+++ b/web/src/locales/en.ts
@@ -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',
},
diff --git a/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx b/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx
index b15d6fdf..8efcaa35 100644
--- a/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx
+++ b/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx
@@ -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) => {
>
-
-
-
+
);
};
diff --git a/web/src/pages/chat/chat-configuration-modal/model-setting.tsx b/web/src/pages/chat/chat-configuration-modal/model-setting.tsx
index 8f4502a1..ca0f3c52 100644
--- a/web/src/pages/chat/chat-configuration-modal/model-setting.tsx
+++ b/web/src/pages/chat/chat-configuration-modal/model-setting.tsx
@@ -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,
})}
>
-
+ )}
+ {/*
-
+ */}
);
};
diff --git a/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx b/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx
index f49faa4b..e988f851 100644
--- a/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx
+++ b/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx
@@ -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>,
@@ -165,14 +159,7 @@ const PromptEngine = (
-
- label={t('topN')}
- name={'top_n'}
- initialValue={8}
- tooltip={t('topNTip')}
- >
-
-
+
diff --git a/web/src/pages/flow/answer-form/index.tsx b/web/src/pages/flow/answer-form/index.tsx
new file mode 100644
index 00000000..5f720a0b
--- /dev/null
+++ b/web/src/pages/flow/answer-form/index.tsx
@@ -0,0 +1,5 @@
+const AnswerForm = () => {
+ return AnswerForm
;
+};
+
+export default AnswerForm;
diff --git a/web/src/pages/flow/begin-form/index.tsx b/web/src/pages/flow/begin-form/index.tsx
new file mode 100644
index 00000000..125d31e2
--- /dev/null
+++ b/web/src/pages/flow/begin-form/index.tsx
@@ -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['onFinish'] = (values) => {
+ console.log('Success:', values);
+};
+
+const onFinishFailed: FormProps['onFinishFailed'] = (errorInfo) => {
+ console.log('Failed:', errorInfo);
+};
+
+const BeginForm = ({ onValuesChange }: IOperatorForm) => {
+ const { t } = useTranslate('chat');
+ const [form] = Form.useForm();
+
+ return (
+
+ name={'prologue'}
+ label={t('setAnOpener')}
+ tooltip={t('setAnOpenerTip')}
+ initialValue={t('setAnOpenerInitial')}
+ >
+
+
+
+ );
+};
+
+export default BeginForm;
diff --git a/web/src/pages/flow/canvas/context-menu/index.tsx b/web/src/pages/flow/canvas/context-menu/index.tsx
index 1edc2d23..83c36545 100644
--- a/web/src/pages/flow/canvas/context-menu/index.tsx
+++ b/web/src/pages/flow/canvas/context-menu/index.tsx
@@ -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,
diff --git a/web/src/pages/flow/canvas/edge/index.less b/web/src/pages/flow/canvas/edge/index.less
new file mode 100644
index 00000000..fd6bae7f
--- /dev/null
+++ b/web/src/pages/flow/canvas/edge/index.less
@@ -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);
+}
diff --git a/web/src/pages/flow/canvas/edge/index.tsx b/web/src/pages/flow/canvas/edge/index.tsx
new file mode 100644
index 00000000..4aa123b4
--- /dev/null
+++ b/web/src/pages/flow/canvas/edge/index.tsx
@@ -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 (
+ <>
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/web/src/pages/flow/canvas/index.less b/web/src/pages/flow/canvas/index.less
new file mode 100644
index 00000000..3f3245a1
--- /dev/null
+++ b/web/src/pages/flow/canvas/index.less
@@ -0,0 +1,4 @@
+.canvasWrapper {
+ position: relative;
+ height: 100%;
+}
diff --git a/web/src/pages/flow/canvas/index.tsx b/web/src/pages/flow/canvas/index.tsx
index 6db3c81b..dc1d4757 100644
--- a/web/src/pages/flow/canvas/index.tsx
+++ b/web/src/pages/flow/canvas/index.tsx
@@ -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(dsl.graph.nodes);
- const [edges, setEdges] = useState(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 (
-
+
@@ -94,7 +91,11 @@ function FlowCanvas({ sideWidth }: IProps) {
)}
-
+
);
}
diff --git a/web/src/pages/flow/canvas/node/index.less b/web/src/pages/flow/canvas/node/index.less
index 43a78ca3..92996613 100644
--- a/web/src/pages/flow/canvas/node/index.less
+++ b/web/src/pages/flow/canvas/node/index.less
@@ -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;
+}
diff --git a/web/src/pages/flow/canvas/node/index.tsx b/web/src/pages/flow/canvas/node/index.tsx
index 13801cac..bd501799 100644
--- a/web/src/pages/flow/canvas/node/index.tsx
+++ b/web/src/pages/flow/canvas/node/index.tsx
@@ -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 (
-
+
+ className={styles.handle}
+ >
+ {/*
*/}
+
+ className={styles.handle}
+ >
+ {/*
*/}
+
{data.label}
);
diff --git a/web/src/pages/flow/constant.ts b/web/src/pages/flow/constant.ts
new file mode 100644
index 00000000..8f246dce
--- /dev/null
+++ b/web/src/pages/flow/constant.ts
@@ -0,0 +1,6 @@
+export enum Operator {
+ Begin = 'Begin',
+ Retrieval = 'Retrieval',
+ Generate = 'Generate',
+ Answer = 'Answer',
+}
diff --git a/web/src/pages/flow/flow-drawer/index.tsx b/web/src/pages/flow/flow-drawer/index.tsx
index 7e395b25..01f3338d 100644
--- a/web/src/pages/flow/flow-drawer/index.tsx
+++ b/web/src/pages/flow/flow-drawer/index.tsx
@@ -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
& IProps) => {
+ const operatorName: Operator = node?.data.label;
+ const OperatorForm = FormMap[operatorName];
+ const { handleValuesChange } = useHandleFormValuesChange(node?.id);
-const FlowDrawer = ({ visible, hideModal }: IModalProps) => {
return (
- Some contents...
+ {visible && (
+
+ )}
);
};
diff --git a/web/src/pages/flow/generate-form/index.tsx b/web/src/pages/flow/generate-form/index.tsx
new file mode 100644
index 00000000..c5d63d19
--- /dev/null
+++ b/web/src/pages/flow/generate-form/index.tsx
@@ -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
+ >((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 (
+
+
+
+
+
+
+
+ );
+};
+
+export default GenerateForm;
diff --git a/web/src/pages/flow/hooks.ts b/web/src/pages/flow/hooks.ts
index 23e5216b..52f65b88 100644
--- a/web/src/pages/flow/hooks.ts
+++ b/web/src/pages/flow/hooks.ts
@@ -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>) => {
+export const useHandleDrop = () => {
+ const addNode = useStore((state) => state.addNode);
const [reactFlowInstance, setReactFlowInstance] =
useState>();
@@ -66,59 +74,40 @@ export const useHandleDrop = (setNodes: Dispatch>) => {
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();
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([]);
- const [selectedEdges, setSelectedEdges] = useState([]);
-
- 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();
+};
diff --git a/web/src/pages/flow/index.tsx b/web/src/pages/flow/index.tsx
index b1775b52..9bd1a1b0 100644
--- a/web/src/pages/flow/index.tsx
+++ b/web/src/pages/flow/index.tsx
@@ -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 (
-
+
diff --git a/web/src/pages/flow/interface.ts b/web/src/pages/flow/interface.ts
index 2d17dd48..ffb882c2 100644
--- a/web/src/pages/flow/interface.ts
+++ b/web/src/pages/flow/interface.ts
@@ -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;
+}
diff --git a/web/src/pages/flow/list/flow-card/index.less b/web/src/pages/flow/list/flow-card/index.less
new file mode 100644
index 00000000..1c7d174e
--- /dev/null
+++ b/web/src/pages/flow/list/flow-card/index.less
@@ -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;
+ }
+}
diff --git a/web/src/pages/flow/list/flow-card/index.tsx b/web/src/pages/flow/list/flow-card/index.tsx
new file mode 100644
index 00000000..643496e2
--- /dev/null
+++ b/web/src/pages/flow/list/flow-card/index.tsx
@@ -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: (
+
+ {t('common.delete')}
+
+
+ ),
+ },
+ ];
+
+ const handleDropdownMenuClick: MenuProps['onClick'] = ({ domEvent, key }) => {
+ domEvent.preventDefault();
+ domEvent.stopPropagation();
+ if (key === '1') {
+ handleDelete();
+ }
+ };
+
+ const handleCardClick = () => {
+ navigate(`/flow/${item.id}`);
+ };
+
+ return (
+
+
+
+
} src={item.avatar} />
+
+
+
+
+
+
+
+
{item.title}
+
{item.description}
+
+
+
+
+
+
+ {formatDate(item.update_time)}
+
+
+
+
+
+
+ );
+};
+
+export default FlowCard;
diff --git a/web/src/pages/flow/list/hooks.ts b/web/src/pages/flow/list/hooks.ts
new file mode 100644
index 00000000..1e0a2702
--- /dev/null
+++ b/web/src/pages/flow/list/hooks.ts
@@ -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,
+ };
+};
diff --git a/web/src/pages/flow/list/index.less b/web/src/pages/flow/list/index.less
new file mode 100644
index 00000000..6c171193
--- /dev/null
+++ b/web/src/pages/flow/list/index.less
@@ -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%;
+ }
+}
diff --git a/web/src/pages/flow/list/index.tsx b/web/src/pages/flow/list/index.tsx
new file mode 100644
index 00000000..5b498269
--- /dev/null
+++ b/web/src/pages/flow/list/index.tsx
@@ -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 (
+
+
+ }
+ onClick={showFlowSettingModal}
+ >
+ create canvas
+
+
+
+
+ {list.length > 0 ? (
+ list.map((item: any) => {
+ return ;
+ })
+ ) : (
+
+ )}
+
+
+
+
+ );
+};
+
+export default FlowList;
diff --git a/web/src/pages/flow/mock.tsx b/web/src/pages/flow/mock.tsx
index 4b891646..1e393bf8 100644
--- a/web/src/pages/flow/mock.tsx
+++ b/web/src/pages/flow/mock.tsx
@@ -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: , description: '' },
{ name: 'Retrieval', icon: , description: '' },
{ name: 'Generate', icon: , 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'],
diff --git a/web/src/pages/flow/retrieval-form/index.tsx b/web/src/pages/flow/retrieval-form/index.tsx
new file mode 100644
index 00000000..afbea852
--- /dev/null
+++ b/web/src/pages/flow/retrieval-form/index.tsx
@@ -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['onFinish'] = (values) => {
+ console.log('Success:', values);
+};
+
+const onFinishFailed: FormProps['onFinishFailed'] = (errorInfo) => {
+ console.log('Failed:', errorInfo);
+};
+
+const RetrievalForm = ({ onValuesChange }: IOperatorForm) => {
+ const [form] = Form.useForm();
+
+ return (
+
+ );
+};
+
+export default RetrievalForm;
diff --git a/web/src/pages/flow/store.ts b/web/src/pages/flow/store.ts
new file mode 100644
index 00000000..1d6b8d5f
--- /dev/null
+++ b/web/src/pages/flow/store.ts
@@ -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[];
+ 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()(
+ 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;
diff --git a/web/src/pages/flow/utils.ts b/web/src/pages/flow/utils.ts
index eadd8e53..7cd5810e 100644
--- a/web/src/pages/flow/utils.ts
+++ b/web/src/pages/flow/utils.ts
@@ -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[],
+ 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,
+ },
+ downstream: buildComponentDownstreamOrUpstream(edges, id, true),
+ upstream: buildComponentDownstreamOrUpstream(edges, id, false),
+ };
+ });
+
+ return components;
+};
diff --git a/web/src/routes.ts b/web/src/routes.ts
index 965ddf63..551fcb8b 100644
--- a/web/src/routes.ts
+++ b/web/src/routes.ts
@@ -90,6 +90,10 @@ const routes = [
},
{
path: '/flow',
+ component: '@/pages/flow/list',
+ },
+ {
+ path: '/flow/:id',
component: '@/pages/flow',
},
],
diff --git a/web/src/services/flow-service.ts b/web/src/services/flow-service.ts
new file mode 100644
index 00000000..00bd9cc8
--- /dev/null
+++ b/web/src/services/flow-service.ts
@@ -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(methods, request);
+
+export default chatService;
diff --git a/web/src/utils/api.ts b/web/src/utils/api.ts
index fe083d67..52b37660 100644
--- a/web/src/utils/api.ts
+++ b/web/src/utils/api.ts
@@ -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`,
};
diff --git a/web/src/utils/registerServer.ts b/web/src/utils/registerServer.ts
index 02ae2b1f..046b863d 100644
--- a/web/src/utils/registerServer.ts
+++ b/web/src/utils/registerServer.ts
@@ -1,7 +1,7 @@
import omit from 'lodash/omit';
import { RequestMethod } from 'umi-request';
-type Service = Record any>;
+type Service = Record any>;
const registerServer = (
opt: Record,