diff --git a/web/package-lock.json b/web/package-lock.json
index bf88f374d29..b1e23daf94e 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 f8e8f3b6402..ca6470af10b 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 ff532b1d359..9cbacdae2ea 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 00000000000..01cecee9a03
--- /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 00000000000..03928e740a8
--- /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 00000000000..398d1c4cade
--- /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 00000000000..2a83b8c440f
--- /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 00000000000..64007b118e0
--- /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 d2499828db4..b344d37fbc9 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 00f17cf78ef..80d24eba603 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 6a4254f6ef7..7b4e2a706a6 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 b15d6fdf97f..8efcaa3505c 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 8f4502a1129..ca0f3c52b55 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 f49faa4bf78..e988f851740 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 00000000000..5f720a0bfa6
--- /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 00000000000..125d31e2f87
--- /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 1edc2d23410..83c3654505e 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 00000000000..fd6bae7f075
--- /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 00000000000..4aa123b44a8
--- /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 00000000000..3f3245a1c4b
--- /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 6db3c81bc2f..dc1d4757961 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 onNodesChange: OnNodesChange = useCallback(
- (changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
- [],
- );
- const onEdgesChange: OnEdgesChange = useCallback(
- (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
- [],
- );
+ const { drawerVisible, hideDrawer, showDrawer, clickedNode } =
+ useShowDrawer();
- const onConnect: OnConnect = useCallback(
- (params) => setEdges((eds) => addEdge(params, eds)),
- [],
+ const onNodeClick: NodeMouseHandler = useCallback(
+ (e, node) => {
+ showDrawer(node);
+ },
+ [showDrawer],
);
- const onNodeClick: NodeMouseHandler = useCallback(() => {
- showDrawer();
- }, [showDrawer]);
-
- const { onDrop, onDragOver, setReactFlowInstance } = useHandleDrop(setNodes);
-
- const { handleKeyUp } = useHandleKeyUp(selectedEdges, selectedNodes);
+ const { onDrop, onDragOver, setReactFlowInstance } = useHandleDrop();
- useEffect(() => {
- console.info('nodes:', nodes);
- console.info('edges:', edges);
- }, [nodes, edges]);
+ const { handleKeyUp } = useHandleKeyUp();
return (
-
+
@@ -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 43a78ca33b8..92996613c51 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 13801cace59..bd501799ddf 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 00000000000..8f246dce222
--- /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 7e395b2574b..01f3338dd13 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 00000000000..c5d63d1927f
--- /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 23e5216bcba..52f65b882eb 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 b1775b526bf..9bd1a1b0527 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 2d17dd48e40..ffb882c2c8f 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 00000000000..1c7d174e0fb
--- /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 00000000000..643496e23f3
--- /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 00000000000..1e0a2702624
--- /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 00000000000..6c171193f25
--- /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 00000000000..5b4982695ee
--- /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 4b891646009..1e393bf8d26 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 00000000000..afbea85228e
--- /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 00000000000..1d6b8d5fa86
--- /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 eadd8e53e5a..7cd5810eb9a 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 965ddf63204..551fcb8baa5 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 00000000000..00bd9cc835a
--- /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 fe083d67515..52b37660f02 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 02ae2b1f583..046b863d31e 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,