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 ( + <> + + - + ); }; 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 ( + + + + + + + {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,