diff --git a/web/package-lock.json b/web/package-lock.json index bf88f374..b1e23daf 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -10,6 +10,7 @@ "@ant-design/pro-components": "^2.6.46", "@ant-design/pro-layout": "^7.17.16", "@js-preview/excel": "^1.7.8", + "@tanstack/react-query": "^5.40.0", "ahooks": "^3.7.10", "antd": "^5.12.7", "axios": "^1.6.3", @@ -39,10 +40,12 @@ "umi": "^4.0.90", "umi-request": "^1.4.0", "unist-util-visit-parents": "^6.0.1", - "uuid": "^9.0.1" + "uuid": "^9.0.1", + "zustand": "^4.5.2" }, "devDependencies": { "@react-dev-inspector/umi4-plugin": "^2.0.1", + "@redux-devtools/extension": "^3.3.0", "@testing-library/jest-dom": "^6.4.5", "@testing-library/react": "^15.0.7", "@types/dagre": "^0.7.52", @@ -3915,40 +3918,6 @@ "react-dom": ">=17" } }, - "node_modules/@reactflow/background/node_modules/immer": { - "version": "10.1.1", - "resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz", - "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", - "optional": true, - "peer": true - }, - "node_modules/@reactflow/background/node_modules/zustand": { - "version": "4.5.2", - "resolved": "https://registry.npmmirror.com/zustand/-/zustand-4.5.2.tgz", - "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==", - "dependencies": { - "use-sync-external-store": "1.2.0" - }, - "engines": { - "node": ">=12.7.0" - }, - "peerDependencies": { - "@types/react": ">=16.8", - "immer": ">=9.0.6", - "react": ">=16.8" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - } - } - }, "node_modules/@reactflow/controls": { "version": "11.2.12", "resolved": "https://registry.npmmirror.com/@reactflow/controls/-/controls-11.2.12.tgz", @@ -3963,40 +3932,6 @@ "react-dom": ">=17" } }, - "node_modules/@reactflow/controls/node_modules/immer": { - "version": "10.1.1", - "resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz", - "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", - "optional": true, - "peer": true - }, - "node_modules/@reactflow/controls/node_modules/zustand": { - "version": "4.5.2", - "resolved": "https://registry.npmmirror.com/zustand/-/zustand-4.5.2.tgz", - "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==", - "dependencies": { - "use-sync-external-store": "1.2.0" - }, - "engines": { - "node": ">=12.7.0" - }, - "peerDependencies": { - "@types/react": ">=16.8", - "immer": ">=9.0.6", - "react": ">=16.8" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - } - } - }, "node_modules/@reactflow/core": { "version": "11.11.2", "resolved": "https://registry.npmmirror.com/@reactflow/core/-/core-11.11.2.tgz", @@ -4017,40 +3952,6 @@ "react-dom": ">=17" } }, - "node_modules/@reactflow/core/node_modules/immer": { - "version": "10.1.1", - "resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz", - "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", - "optional": true, - "peer": true - }, - "node_modules/@reactflow/core/node_modules/zustand": { - "version": "4.5.2", - "resolved": "https://registry.npmmirror.com/zustand/-/zustand-4.5.2.tgz", - "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==", - "dependencies": { - "use-sync-external-store": "1.2.0" - }, - "engines": { - "node": ">=12.7.0" - }, - "peerDependencies": { - "@types/react": ">=16.8", - "immer": ">=9.0.6", - "react": ">=16.8" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - } - } - }, "node_modules/@reactflow/minimap": { "version": "11.7.12", "resolved": "https://registry.npmmirror.com/@reactflow/minimap/-/minimap-11.7.12.tgz", @@ -4069,40 +3970,6 @@ "react-dom": ">=17" } }, - "node_modules/@reactflow/minimap/node_modules/immer": { - "version": "10.1.1", - "resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz", - "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", - "optional": true, - "peer": true - }, - "node_modules/@reactflow/minimap/node_modules/zustand": { - "version": "4.5.2", - "resolved": "https://registry.npmmirror.com/zustand/-/zustand-4.5.2.tgz", - "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==", - "dependencies": { - "use-sync-external-store": "1.2.0" - }, - "engines": { - "node": ">=12.7.0" - }, - "peerDependencies": { - "@types/react": ">=16.8", - "immer": ">=9.0.6", - "react": ">=16.8" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - } - } - }, "node_modules/@reactflow/node-resizer": { "version": "2.2.12", "resolved": "https://registry.npmmirror.com/@reactflow/node-resizer/-/node-resizer-2.2.12.tgz", @@ -4119,40 +3986,6 @@ "react-dom": ">=17" } }, - "node_modules/@reactflow/node-resizer/node_modules/immer": { - "version": "10.1.1", - "resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz", - "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", - "optional": true, - "peer": true - }, - "node_modules/@reactflow/node-resizer/node_modules/zustand": { - "version": "4.5.2", - "resolved": "https://registry.npmmirror.com/zustand/-/zustand-4.5.2.tgz", - "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==", - "dependencies": { - "use-sync-external-store": "1.2.0" - }, - "engines": { - "node": ">=12.7.0" - }, - "peerDependencies": { - "@types/react": ">=16.8", - "immer": ">=9.0.6", - "react": ">=16.8" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - } - } - }, "node_modules/@reactflow/node-toolbar": { "version": "1.3.12", "resolved": "https://registry.npmmirror.com/@reactflow/node-toolbar/-/node-toolbar-1.3.12.tgz", @@ -4167,38 +4000,17 @@ "react-dom": ">=17" } }, - "node_modules/@reactflow/node-toolbar/node_modules/immer": { - "version": "10.1.1", - "resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz", - "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", - "optional": true, - "peer": true - }, - "node_modules/@reactflow/node-toolbar/node_modules/zustand": { - "version": "4.5.2", - "resolved": "https://registry.npmmirror.com/zustand/-/zustand-4.5.2.tgz", - "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==", + "node_modules/@redux-devtools/extension": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/@redux-devtools/extension/-/extension-3.3.0.tgz", + "integrity": "sha512-X34S/rC8S/M1BIrkYD1mJ5f8vlH0BDqxXrs96cvxSBo4FhMdbhU+GUGsmNYov1xjSyLMHgo8NYrUG8bNX7525g==", + "dev": true, "dependencies": { - "use-sync-external-store": "1.2.0" - }, - "engines": { - "node": ">=12.7.0" + "@babel/runtime": "^7.23.2", + "immutable": "^4.3.4" }, "peerDependencies": { - "@types/react": ">=16.8", - "immer": ">=9.0.6", - "react": ">=16.8" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - } + "redux": "^3.1.0 || ^4.0.0 || ^5.0.0" } }, "node_modules/@rgrove/parse-xml": { @@ -4441,6 +4253,30 @@ "integrity": "sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA==", "dev": true }, + "node_modules/@tanstack/react-query": { + "version": "5.40.0", + "resolved": "https://registry.npmmirror.com/@tanstack/react-query/-/react-query-5.40.0.tgz", + "integrity": "sha512-iv/W0Axc4aXhFzkrByToE1JQqayxTPNotCoSCnarR/A1vDIHaoKpg7FTIfP3Ev2mbKn1yrxq0ZKYUdLEJxs6Tg==", + "dependencies": { + "@tanstack/query-core": "5.40.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/@tanstack/react-query/node_modules/@tanstack/query-core": { + "version": "5.40.0", + "resolved": "https://registry.npmmirror.com/@tanstack/query-core/-/query-core-5.40.0.tgz", + "integrity": "sha512-eD8K8jsOIq0Z5u/QbvOmfvKKE/XC39jA7yv4hgpl/1SRiU+J8QCIwgM/mEHuunQsL87dcvnHqSVLmf9pD4CiaA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@testing-library/dom": { "version": "10.1.0", "resolved": "https://registry.npmmirror.com/@testing-library/dom/-/dom-10.1.0.tgz", @@ -6692,6 +6528,16 @@ "value-equal": "^1.0.1" } }, + "node_modules/@umijs/plugins/node_modules/immer": { + "version": "8.0.4", + "resolved": "https://registry.npmmirror.com/immer/-/immer-8.0.4.tgz", + "integrity": "sha512-jMfL18P+/6P6epANRvRk6q8t+3gGhqsJ9EuJ25AXE+9bNTYtssvzeYbEd0mXRYWCmmXSIbnlpz6vd6iJlmGGGQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/@umijs/plugins/node_modules/isarray": { "version": "0.0.1", "resolved": "https://registry.npmmirror.com/isarray/-/isarray-0.0.1.tgz", @@ -13621,9 +13467,20 @@ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" }, "node_modules/immer": { - "version": "8.0.4", - "resolved": "https://registry.npmmirror.com/immer/-/immer-8.0.4.tgz", - "integrity": "sha512-jMfL18P+/6P6epANRvRk6q8t+3gGhqsJ9EuJ25AXE+9bNTYtssvzeYbEd0mXRYWCmmXSIbnlpz6vd6iJlmGGGQ==", + "version": "10.1.1", + "resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "optional": true, + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/immutable": { + "version": "4.3.6", + "resolved": "https://registry.npmmirror.com/immutable/-/immutable-4.3.6.tgz", + "integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==", "dev": true }, "node_modules/import-fresh": { @@ -26064,6 +25921,33 @@ "node": ">=10" } }, + "node_modules/zustand": { + "version": "4.5.2", + "resolved": "https://registry.npmmirror.com/zustand/-/zustand-4.5.2.tgz", + "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==", + "dependencies": { + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz", diff --git a/web/package.json b/web/package.json index f8e8f3b6..ca6470af 100644 --- a/web/package.json +++ b/web/package.json @@ -15,6 +15,7 @@ "@ant-design/pro-components": "^2.6.46", "@ant-design/pro-layout": "^7.17.16", "@js-preview/excel": "^1.7.8", + "@tanstack/react-query": "^5.40.0", "ahooks": "^3.7.10", "antd": "^5.12.7", "axios": "^1.6.3", @@ -44,10 +45,12 @@ "umi": "^4.0.90", "umi-request": "^1.4.0", "unist-util-visit-parents": "^6.0.1", - "uuid": "^9.0.1" + "uuid": "^9.0.1", + "zustand": "^4.5.2" }, "devDependencies": { "@react-dev-inspector/umi4-plugin": "^2.0.1", + "@redux-devtools/extension": "^3.3.0", "@testing-library/jest-dom": "^6.4.5", "@testing-library/react": "^15.0.7", "@types/dagre": "^0.7.52", diff --git a/web/src/app.tsx b/web/src/app.tsx index ff532b1d..9cbacdae 100644 --- a/web/src/app.tsx +++ b/web/src/app.tsx @@ -6,13 +6,14 @@ import zh_HK from 'antd/locale/zh_HK'; import React, { ReactNode, useEffect, useState } from 'react'; import storage from './utils/authorizationUtil'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import dayjs from 'dayjs'; import advancedFormat from 'dayjs/plugin/advancedFormat'; import customParseFormat from 'dayjs/plugin/customParseFormat'; import localeData from 'dayjs/plugin/localeData'; -import weekday from 'dayjs/plugin/weekday'; import weekOfYear from 'dayjs/plugin/weekOfYear'; import weekYear from 'dayjs/plugin/weekYear'; +import weekday from 'dayjs/plugin/weekday'; dayjs.extend(customParseFormat); dayjs.extend(advancedFormat); @@ -27,6 +28,8 @@ const AntLanguageMap = { 'zh-TRADITIONAL': zh_HK, }; +const queryClient = new QueryClient(); + type Locale = ConfigProviderProps['locale']; const RootProvider = ({ children }: React.PropsWithChildren) => { @@ -49,16 +52,18 @@ const RootProvider = ({ children }: React.PropsWithChildren) => { }, []); return ( - - {children} - + + + {children} + + ); }; diff --git a/web/src/components/knowledge-base-item.tsx b/web/src/components/knowledge-base-item.tsx new file mode 100644 index 00000000..01cecee9 --- /dev/null +++ b/web/src/components/knowledge-base-item.tsx @@ -0,0 +1,37 @@ +import { useTranslate } from '@/hooks/commonHooks'; +import { useFetchKnowledgeList } from '@/hooks/knowledgeHook'; +import { Form, Select } from 'antd'; + +const KnowledgeBaseItem = () => { + const { t } = useTranslate('chat'); + + const { list: knowledgeList } = useFetchKnowledgeList(true); + + const knowledgeOptions = knowledgeList.map((x) => ({ + label: x.name, + value: x.id, + })); + + return ( + + + + ); +}; + +export default KnowledgeBaseItem; diff --git a/web/src/components/llm-setting-items/index.less b/web/src/components/llm-setting-items/index.less new file mode 100644 index 00000000..03928e74 --- /dev/null +++ b/web/src/components/llm-setting-items/index.less @@ -0,0 +1,6 @@ +.sliderInputNumber { + width: 80px; +} +.variableSlider { + width: 100%; +} diff --git a/web/src/components/llm-setting-items/index.tsx b/web/src/components/llm-setting-items/index.tsx new file mode 100644 index 00000000..398d1c4c --- /dev/null +++ b/web/src/components/llm-setting-items/index.tsx @@ -0,0 +1,259 @@ +import { LlmModelType, ModelVariableType } from '@/constants/knowledge'; +import { Divider, Flex, Form, InputNumber, Select, Slider, Switch } from 'antd'; +import camelCase from 'lodash/camelCase'; + +import { useTranslate } from '@/hooks/commonHooks'; +import { useSelectLlmOptionsByModelType } from '@/hooks/llmHooks'; +import { useMemo } from 'react'; +import styles from './index.less'; + +interface IProps { + prefix?: string; + handleParametersChange(value: ModelVariableType): void; +} + +const LlmSettingItems = ({ prefix, handleParametersChange }: IProps) => { + const { t } = useTranslate('chat'); + const parameterOptions = Object.values(ModelVariableType).map((x) => ({ + label: t(camelCase(x)), + value: x, + })); + + const memorizedPrefix = useMemo(() => (prefix ? [prefix] : []), [prefix]); + + const modelOptions = useSelectLlmOptionsByModelType(); + + return ( + <> + + - + ); }; diff --git a/web/src/pages/chat/chat-configuration-modal/model-setting.tsx b/web/src/pages/chat/chat-configuration-modal/model-setting.tsx index 8f4502a1..ca0f3c52 100644 --- a/web/src/pages/chat/chat-configuration-modal/model-setting.tsx +++ b/web/src/pages/chat/chat-configuration-modal/model-setting.tsx @@ -1,16 +1,12 @@ import { - LlmModelType, ModelVariableType, settledModelVariableMap, } from '@/constants/knowledge'; -import { Divider, Flex, Form, InputNumber, Select, Slider, Switch } from 'antd'; import classNames from 'classnames'; -import camelCase from 'lodash/camelCase'; import { useEffect } from 'react'; import { ISegmentedContentProps } from '../interface'; -import { useTranslate } from '@/hooks/commonHooks'; -import { useSelectLlmOptionsByModelType } from '@/hooks/llmHooks'; +import LlmSettingItems from '@/components/llm-setting-items'; import { Variable } from '@/interfaces/database/chat'; import { variableEnabledFieldMap } from '../constants'; import styles from './index.less'; @@ -24,14 +20,6 @@ const ModelSetting = ({ initialLlmSetting?: Variable; visible?: boolean; }) => { - const { t } = useTranslate('chat'); - const parameterOptions = Object.values(ModelVariableType).map((x) => ({ - label: t(camelCase(x)), - value: x, - })); - - const modelOptions = useSelectLlmOptionsByModelType(); - const handleParametersChange = (value: ModelVariableType) => { const variable = settledModelVariableMap[value]; form.setFieldsValue({ llm_setting: variable }); @@ -62,7 +50,13 @@ const ModelSetting = ({ [styles.segmentedHidden]: !show, })} > - + )} + {/* - + */} ); }; diff --git a/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx b/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx index f49faa4b..e988f851 100644 --- a/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx +++ b/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx @@ -7,7 +7,6 @@ import { Form, Input, Row, - Slider, Switch, Table, TableProps, @@ -30,16 +29,11 @@ import { import { EditableCell, EditableRow } from './editable-cell'; import Rerank from '@/components/rerank'; +import TopNItem from '@/components/top-n-item'; import { useTranslate } from '@/hooks/commonHooks'; import { useSelectPromptConfigParameters } from '../hooks'; import styles from './index.less'; -type FieldType = { - similarity_threshold?: number; - vector_similarity_weight?: number; - top_n?: number; -}; - const PromptEngine = ( { show }: ISegmentedContentProps, ref: ForwardedRef>, @@ -165,14 +159,7 @@ const PromptEngine = ( - - label={t('topN')} - name={'top_n'} - initialValue={8} - tooltip={t('topNTip')} - > - - +
diff --git a/web/src/pages/flow/answer-form/index.tsx b/web/src/pages/flow/answer-form/index.tsx new file mode 100644 index 00000000..5f720a0b --- /dev/null +++ b/web/src/pages/flow/answer-form/index.tsx @@ -0,0 +1,5 @@ +const AnswerForm = () => { + return
AnswerForm
; +}; + +export default AnswerForm; diff --git a/web/src/pages/flow/begin-form/index.tsx b/web/src/pages/flow/begin-form/index.tsx new file mode 100644 index 00000000..125d31e2 --- /dev/null +++ b/web/src/pages/flow/begin-form/index.tsx @@ -0,0 +1,47 @@ +import { useTranslate } from '@/hooks/commonHooks'; +import type { FormProps } from 'antd'; +import { Form, Input } from 'antd'; +import { IOperatorForm } from '../interface'; + +type FieldType = { + prologue?: string; +}; + +const onFinish: FormProps['onFinish'] = (values) => { + console.log('Success:', values); +}; + +const onFinishFailed: FormProps['onFinishFailed'] = (errorInfo) => { + console.log('Failed:', errorInfo); +}; + +const BeginForm = ({ onValuesChange }: IOperatorForm) => { + const { t } = useTranslate('chat'); + const [form] = Form.useForm(); + + return ( +
+ + name={'prologue'} + label={t('setAnOpener')} + tooltip={t('setAnOpenerTip')} + initialValue={t('setAnOpenerInitial')} + > + + + + ); +}; + +export default BeginForm; diff --git a/web/src/pages/flow/canvas/context-menu/index.tsx b/web/src/pages/flow/canvas/context-menu/index.tsx index 1edc2d23..83c36545 100644 --- a/web/src/pages/flow/canvas/context-menu/index.tsx +++ b/web/src/pages/flow/canvas/context-menu/index.tsx @@ -86,7 +86,7 @@ export const useHandleNodeContextMenu = (sideWidth: number) => { setMenu({ id: node.id, - top: event.clientY - 72, + top: event.clientY - 144, left: event.clientX - sideWidth, // top: event.clientY < pane.height - 200 ? event.clientY - 72 : 0, // left: event.clientX < pane.width - 200 ? event.clientX : 0, diff --git a/web/src/pages/flow/canvas/edge/index.less b/web/src/pages/flow/canvas/edge/index.less new file mode 100644 index 00000000..fd6bae7f --- /dev/null +++ b/web/src/pages/flow/canvas/edge/index.less @@ -0,0 +1,15 @@ +.edgeButton { + width: 14px; + height: 14px; + background: #eee; + border: 1px solid #fff; + padding: 0; + cursor: pointer; + border-radius: 50%; + font-size: 10px; + line-height: 1; +} + +.edgeButton:hover { + box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.08); +} diff --git a/web/src/pages/flow/canvas/edge/index.tsx b/web/src/pages/flow/canvas/edge/index.tsx new file mode 100644 index 00000000..4aa123b4 --- /dev/null +++ b/web/src/pages/flow/canvas/edge/index.tsx @@ -0,0 +1,72 @@ +import { + BaseEdge, + EdgeLabelRenderer, + EdgeProps, + getBezierPath, +} from 'reactflow'; +import useStore from '../../store'; + +import { useMemo } from 'react'; +import styles from './index.less'; + +export function ButtonEdge({ + id, + sourceX, + sourceY, + targetX, + targetY, + sourcePosition, + targetPosition, + style = {}, + markerEnd, + selected, +}: EdgeProps) { + const deleteEdgeById = useStore((state) => state.deleteEdgeById); + const [edgePath, labelX, labelY] = getBezierPath({ + sourceX, + sourceY, + sourcePosition, + targetX, + targetY, + targetPosition, + }); + + const selectedStyle = useMemo(() => { + return selected ? { strokeWidth: 1, stroke: '#1677ff' } : {}; + }, [selected]); + + const onEdgeClick = () => { + deleteEdgeById(id); + }; + + return ( + <> + + +
+ +
+
+ + ); +} diff --git a/web/src/pages/flow/canvas/index.less b/web/src/pages/flow/canvas/index.less new file mode 100644 index 00000000..3f3245a1 --- /dev/null +++ b/web/src/pages/flow/canvas/index.less @@ -0,0 +1,4 @@ +.canvasWrapper { + position: relative; + height: 100%; +} diff --git a/web/src/pages/flow/canvas/index.tsx b/web/src/pages/flow/canvas/index.tsx index 6db3c81b..dc1d4757 100644 --- a/web/src/pages/flow/canvas/index.tsx +++ b/web/src/pages/flow/canvas/index.tsx @@ -1,76 +1,64 @@ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback } from 'react'; import ReactFlow, { Background, Controls, - Edge, - Node, + MarkerType, NodeMouseHandler, - OnConnect, - OnEdgesChange, - OnNodesChange, - addEdge, - applyEdgeChanges, - applyNodeChanges, } from 'reactflow'; import 'reactflow/dist/style.css'; import { NodeContextMenu, useHandleNodeContextMenu } from './context-menu'; +import { ButtonEdge } from './edge'; import FlowDrawer from '../flow-drawer'; import { useHandleDrop, useHandleKeyUp, - useHandleSelectionChange, + useSelectCanvasData, useShowDrawer, } from '../hooks'; -import { dsl } from '../mock'; import { TextUpdaterNode } from './node'; +import styles from './index.less'; + const nodeTypes = { textUpdater: TextUpdaterNode }; +const edgeTypes = { + buttonEdge: ButtonEdge, +}; + interface IProps { sideWidth: number; } function FlowCanvas({ sideWidth }: IProps) { - const [nodes, setNodes] = useState(dsl.graph.nodes); - const [edges, setEdges] = useState(dsl.graph.edges); - - const { selectedEdges, selectedNodes } = useHandleSelectionChange(); + const { + nodes, + edges, + onConnect, + onEdgesChange, + onNodesChange, + onSelectionChange, + } = useSelectCanvasData(); const { ref, menu, onNodeContextMenu, onPaneClick } = useHandleNodeContextMenu(sideWidth); - const { drawerVisible, hideDrawer, showDrawer } = useShowDrawer(); + const { drawerVisible, hideDrawer, showDrawer, clickedNode } = + useShowDrawer(); - const onNodesChange: OnNodesChange = useCallback( - (changes) => setNodes((nds) => applyNodeChanges(changes, nds)), - [], - ); - const onEdgesChange: OnEdgesChange = useCallback( - (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)), - [], + const onNodeClick: NodeMouseHandler = useCallback( + (e, node) => { + showDrawer(node); + }, + [showDrawer], ); - const onConnect: OnConnect = useCallback( - (params) => setEdges((eds) => addEdge(params, eds)), - [], - ); + const { onDrop, onDragOver, setReactFlowInstance } = useHandleDrop(); - const onNodeClick: NodeMouseHandler = useCallback(() => { - showDrawer(); - }, [showDrawer]); - - const { onDrop, onDragOver, setReactFlowInstance } = useHandleDrop(setNodes); - - const { handleKeyUp } = useHandleKeyUp(selectedEdges, selectedNodes); - - useEffect(() => { - console.info('nodes:', nodes); - console.info('edges:', edges); - }, [nodes, edges]); + const { handleKeyUp } = useHandleKeyUp(); return ( -
+
@@ -94,7 +91,11 @@ function FlowCanvas({ sideWidth }: IProps) { )} - +
); } diff --git a/web/src/pages/flow/canvas/node/index.less b/web/src/pages/flow/canvas/node/index.less index 43a78ca3..92996613 100644 --- a/web/src/pages/flow/canvas/node/index.less +++ b/web/src/pages/flow/canvas/node/index.less @@ -1,6 +1,6 @@ .textUpdaterNode { // height: 50px; - border: 1px solid black; + border: 1px solid gray; padding: 5px; border-radius: 5px; background: white; @@ -10,3 +10,12 @@ font-size: 12px; } } +.selectedNode { + border-color: #1677ff; +} + +.handle { + display: inline-flex; + text-align: center; + // align-items: center; +} diff --git a/web/src/pages/flow/canvas/node/index.tsx b/web/src/pages/flow/canvas/node/index.tsx index 13801cac..bd501799 100644 --- a/web/src/pages/flow/canvas/node/index.tsx +++ b/web/src/pages/flow/canvas/node/index.tsx @@ -1,3 +1,4 @@ +import classNames from 'classnames'; import { Handle, NodeProps, Position } from 'reactflow'; import styles from './index.less'; @@ -5,19 +6,30 @@ import styles from './index.less'; export function TextUpdaterNode({ data, isConnectable = true, + selected, }: NodeProps<{ label: string }>) { return ( -
+
+ className={styles.handle} + > + {/* */} + + className={styles.handle} + > + {/* */} +
{data.label}
); diff --git a/web/src/pages/flow/constant.ts b/web/src/pages/flow/constant.ts new file mode 100644 index 00000000..8f246dce --- /dev/null +++ b/web/src/pages/flow/constant.ts @@ -0,0 +1,6 @@ +export enum Operator { + Begin = 'Begin', + Retrieval = 'Retrieval', + Generate = 'Generate', + Answer = 'Answer', +} diff --git a/web/src/pages/flow/flow-drawer/index.tsx b/web/src/pages/flow/flow-drawer/index.tsx index 7e395b25..01f3338d 100644 --- a/web/src/pages/flow/flow-drawer/index.tsx +++ b/web/src/pages/flow/flow-drawer/index.tsx @@ -1,18 +1,46 @@ import { IModalProps } from '@/interfaces/common'; import { Drawer } from 'antd'; +import { Node } from 'reactflow'; +import AnswerForm from '../answer-form'; +import BeginForm from '../begin-form'; +import { Operator } from '../constant'; +import GenerateForm from '../generate-form'; +import { useHandleFormValuesChange } from '../hooks'; +import RetrievalForm from '../retrieval-form'; + +interface IProps { + node?: Node; +} + +const FormMap = { + [Operator.Begin]: BeginForm, + [Operator.Retrieval]: RetrievalForm, + [Operator.Generate]: GenerateForm, + [Operator.Answer]: AnswerForm, +}; + +const FlowDrawer = ({ + visible, + hideModal, + node, +}: IModalProps & IProps) => { + const operatorName: Operator = node?.data.label; + const OperatorForm = FormMap[operatorName]; + const { handleValuesChange } = useHandleFormValuesChange(node?.id); -const FlowDrawer = ({ visible, hideModal }: IModalProps) => { return ( -

Some contents...

+ {visible && ( + + )}
); }; diff --git a/web/src/pages/flow/generate-form/index.tsx b/web/src/pages/flow/generate-form/index.tsx new file mode 100644 index 00000000..c5d63d19 --- /dev/null +++ b/web/src/pages/flow/generate-form/index.tsx @@ -0,0 +1,83 @@ +import LlmSettingItems from '@/components/llm-setting-items'; +import { + ModelVariableType, + settledModelVariableMap, +} from '@/constants/knowledge'; +import { useTranslate } from '@/hooks/commonHooks'; +import { Variable } from '@/interfaces/database/chat'; +import { variableEnabledFieldMap } from '@/pages/chat/constants'; +import { Form, Input, Switch } from 'antd'; +import { useCallback, useEffect } from 'react'; +import { IOperatorForm } from '../interface'; + +const GenerateForm = ({ onValuesChange }: IOperatorForm) => { + const { t } = useTranslate('flow'); + const [form] = Form.useForm(); + const initialLlmSetting = undefined; + + const handleParametersChange = useCallback( + (value: ModelVariableType) => { + const variable = settledModelVariableMap[value]; + form.setFieldsValue(variable); + }, + [form], + ); + + useEffect(() => { + const switchBoxValues = Object.keys(variableEnabledFieldMap).reduce< + Record + >((pre, field) => { + pre[field] = + initialLlmSetting === undefined + ? true + : !!initialLlmSetting[ + variableEnabledFieldMap[ + field as keyof typeof variableEnabledFieldMap + ] as keyof Variable + ]; + return pre; + }, {}); + const otherValues = settledModelVariableMap[ModelVariableType.Precise]; + form.setFieldsValue({ ...switchBoxValues, ...otherValues }); + }, [form, initialLlmSetting]); + + return ( +
+ + + + + + + +
+ ); +}; + +export default GenerateForm; diff --git a/web/src/pages/flow/hooks.ts b/web/src/pages/flow/hooks.ts index 23e5216b..52f65b88 100644 --- a/web/src/pages/flow/hooks.ts +++ b/web/src/pages/flow/hooks.ts @@ -1,19 +1,26 @@ import { useSetModalState } from '@/hooks/commonHooks'; -import React, { - Dispatch, - KeyboardEventHandler, - SetStateAction, - useCallback, - useState, -} from 'react'; -import { - Node, - Position, - ReactFlowInstance, - useOnSelectionChange, - useReactFlow, -} from 'reactflow'; +import { useFetchFlowTemplates } from '@/hooks/flow-hooks'; +import { useFetchLlmList } from '@/hooks/llmHooks'; +import React, { KeyboardEventHandler, useCallback, useState } from 'react'; +import { Node, Position, ReactFlowInstance } from 'reactflow'; import { v4 as uuidv4 } from 'uuid'; +import useStore, { RFState } from './store'; +import { buildDslComponentsByGraph } from './utils'; + +const selector = (state: RFState) => ({ + nodes: state.nodes, + edges: state.edges, + onNodesChange: state.onNodesChange, + onEdgesChange: state.onEdgesChange, + onConnect: state.onConnect, + setNodes: state.setNodes, + onSelectionChange: state.onSelectionChange, +}); + +export const useSelectCanvasData = () => { + // return useStore(useShallow(selector)); throw error + return useStore(selector); +}; export const useHandleDrag = () => { const handleDragStart = useCallback( @@ -27,7 +34,8 @@ export const useHandleDrag = () => { return { handleDragStart }; }; -export const useHandleDrop = (setNodes: Dispatch>) => { +export const useHandleDrop = () => { + const addNode = useStore((state) => state.addNode); const [reactFlowInstance, setReactFlowInstance] = useState>(); @@ -66,59 +74,40 @@ export const useHandleDrop = (setNodes: Dispatch>) => { targetPosition: Position.Left, }; - setNodes((nds) => nds.concat(newNode)); + addNode(newNode); }, - [reactFlowInstance, setNodes], + [reactFlowInstance, addNode], ); return { onDrop, onDragOver, setReactFlowInstance }; }; export const useShowDrawer = () => { + const [clickedNode, setClickedNode] = useState(); const { visible: drawerVisible, hideModal: hideDrawer, showModal: showDrawer, } = useSetModalState(); + const handleShow = useCallback( + (node: Node) => { + setClickedNode(node); + showDrawer(); + }, + [showDrawer], + ); + return { drawerVisible, hideDrawer, - showDrawer, + showDrawer: handleShow, + clickedNode, }; }; -export const useHandleSelectionChange = () => { - const [selectedNodes, setSelectedNodes] = useState([]); - const [selectedEdges, setSelectedEdges] = useState([]); - - useOnSelectionChange({ - onChange: ({ nodes, edges }) => { - setSelectedNodes(nodes.map((node) => node.id)); - setSelectedEdges(edges.map((edge) => edge.id)); - }, - }); - - return { selectedEdges, selectedNodes }; -}; - -export const useDeleteEdge = (selectedEdges: string[]) => { - const { setEdges } = useReactFlow(); - - const deleteEdge = useCallback(() => { - setEdges((edges) => - edges.filter((edge) => selectedEdges.every((x) => x !== edge.id)), - ); - }, [setEdges, selectedEdges]); - - return deleteEdge; -}; - -export const useHandleKeyUp = ( - selectedEdges: string[], - selectedNodes: string[], -) => { - const deleteEdge = useDeleteEdge(selectedEdges); +export const useHandleKeyUp = () => { + const deleteEdge = useStore((state) => state.deleteEdge); const handleKeyUp: KeyboardEventHandler = useCallback( (e) => { if (e.code === 'Delete') { @@ -132,7 +121,31 @@ export const useHandleKeyUp = ( }; export const useSaveGraph = () => { - const saveGraph = useCallback(() => {}, []); + const { nodes, edges } = useStore((state) => state); + const saveGraph = useCallback(() => { + const x = buildDslComponentsByGraph(nodes, edges); + console.info('components:', x); + }, [nodes, edges]); return { saveGraph }; }; + +export const useHandleFormValuesChange = (id?: string) => { + const updateNodeForm = useStore((state) => state.updateNodeForm); + const handleValuesChange = useCallback( + (changedValues: any, values: any) => { + console.info(changedValues, values); + if (id) { + updateNodeForm(id, values); + } + }, + [updateNodeForm, id], + ); + + return { handleValuesChange }; +}; + +export const useFetchDataOnMount = () => { + useFetchFlowTemplates(); + useFetchLlmList(); +}; diff --git a/web/src/pages/flow/index.tsx b/web/src/pages/flow/index.tsx index b1775b52..9bd1a1b0 100644 --- a/web/src/pages/flow/index.tsx +++ b/web/src/pages/flow/index.tsx @@ -4,19 +4,22 @@ import { ReactFlowProvider } from 'reactflow'; import FlowCanvas from './canvas'; import Sider from './flow-sider'; import FlowHeader from './header'; +import { useFetchDataOnMount } from './hooks'; const { Content } = Layout; function RagFlow() { const [collapsed, setCollapsed] = useState(false); + useFetchDataOnMount(); + return ( - + diff --git a/web/src/pages/flow/interface.ts b/web/src/pages/flow/interface.ts index 2d17dd48..ffb882c2 100644 --- a/web/src/pages/flow/interface.ts +++ b/web/src/pages/flow/interface.ts @@ -1,4 +1,62 @@ +import { Edge, Node } from 'reactflow'; + export interface DSLComponentList { id: string; name: string; } + +export interface IOperatorForm { + onValuesChange?(changedValues: any, values: any): void; +} + +export interface IBeginForm { + prologue?: string; +} + +export interface IRetrievalForm { + similarity_threshold?: number; + keywords_similarity_weight?: number; + top_n?: number; + top_k?: number; + rerank_id?: string; + empty_response?: string; + kb_ids: string[]; +} + +export interface IGenerateForm { + max_tokens?: number; + temperature?: number; + top_p?: number; + presence_penalty?: number; + frequency_penalty?: number; + cite?: boolean; + prompt: number; + llm_id: string; + parameters: { key: string; component_id: string }; +} + +export type NodeData = { + label: string; + color: string; + form: IBeginForm | IRetrievalForm | IGenerateForm; +}; + +export interface IFlow { + avatar: null; + canvas_type: null; + create_date: string; + create_time: number; + description: null; + dsl: { + answer: any[]; + components: DSLComponentList; + graph: { nodes: Node[]; edges: Edge[] }; + history: any[]; + path: string[]; + }; + id: string; + title: string; + update_date: string; + update_time: number; + user_id: string; +} diff --git a/web/src/pages/flow/list/flow-card/index.less b/web/src/pages/flow/list/flow-card/index.less new file mode 100644 index 00000000..1c7d174e --- /dev/null +++ b/web/src/pages/flow/list/flow-card/index.less @@ -0,0 +1,78 @@ +.container { + height: 251px; + display: flex; + flex-direction: column; + justify-content: space-between; + + .delete { + height: 24px; + } + + .content { + display: flex; + justify-content: space-between; + + .context { + flex: 1; + } + } + + .footer { + // text-align: left; + } + .footerTop { + padding-bottom: 2px; + } +} + +.card { + border-radius: 12px; + border: 1px solid rgba(234, 236, 240, 1); + box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05); + padding: 24px; + width: 300px; + cursor: pointer; + + .titleWrapper { + // flex: 1; + .title { + font-size: 24px; + line-height: 32px; + font-weight: 600; + color: rgba(0, 0, 0, 0.88); + word-break: break-all; + } + .description { + font-size: 12px; + font-weight: 600; + line-height: 20px; + color: rgba(0, 0, 0, 0.45); + } + } + + :global { + .ant-card-body { + padding: 0; + margin: 0; + } + } + .bottom { + display: flex; + align-items: center; + justify-content: space-between; + } + .bottomLeft { + vertical-align: middle; + } + .leftIcon { + margin-right: 10px; + font-size: 18px; + vertical-align: middle; + } + .rightText { + font-size: 12px; + font-weight: 600; + color: rgba(0, 0, 0, 0.65); + vertical-align: middle; + } +} diff --git a/web/src/pages/flow/list/flow-card/index.tsx b/web/src/pages/flow/list/flow-card/index.tsx new file mode 100644 index 00000000..643496e2 --- /dev/null +++ b/web/src/pages/flow/list/flow-card/index.tsx @@ -0,0 +1,94 @@ +import { ReactComponent as MoreIcon } from '@/assets/svg/more.svg'; +import { useShowDeleteConfirm } from '@/hooks/commonHooks'; +import { formatDate } from '@/utils/date'; +import { + CalendarOutlined, + DeleteOutlined, + UserOutlined, +} from '@ant-design/icons'; +import { Avatar, Card, Dropdown, MenuProps, Space } from 'antd'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'umi'; + +import { useDeleteFlow } from '@/hooks/flow-hooks'; +import { IFlow } from '../../interface'; +import styles from './index.less'; + +interface IProps { + item: IFlow; +} + +const FlowCard = ({ item }: IProps) => { + const navigate = useNavigate(); + const showDeleteConfirm = useShowDeleteConfirm(); + const { t } = useTranslation(); + const { deleteFlow } = useDeleteFlow(); + + const removeKnowledge = () => { + return deleteFlow([item.id]); + }; + + const handleDelete = () => { + showDeleteConfirm({ onOk: removeKnowledge }); + }; + + const items: MenuProps['items'] = [ + { + key: '1', + label: ( + + {t('common.delete')} + + + ), + }, + ]; + + const handleDropdownMenuClick: MenuProps['onClick'] = ({ domEvent, key }) => { + domEvent.preventDefault(); + domEvent.stopPropagation(); + if (key === '1') { + handleDelete(); + } + }; + + const handleCardClick = () => { + navigate(`/flow/${item.id}`); + }; + + return ( + +
+
+ } src={item.avatar} /> + + + + + +
+
+ {item.title} +

{item.description}

+
+
+
+
+ + + {formatDate(item.update_time)} + +
+
+
+
+
+ ); +}; + +export default FlowCard; diff --git a/web/src/pages/flow/list/hooks.ts b/web/src/pages/flow/list/hooks.ts new file mode 100644 index 00000000..1e0a2702 --- /dev/null +++ b/web/src/pages/flow/list/hooks.ts @@ -0,0 +1,48 @@ +import { useSetModalState } from '@/hooks/commonHooks'; +import { useFetchFlowList, useSetFlow } from '@/hooks/flow-hooks'; +import { useCallback, useState } from 'react'; +import { dsl } from '../mock'; + +export const useFetchDataOnMount = () => { + const { data, loading } = useFetchFlowList(); + + return { list: data, loading }; +}; + +export const useSaveFlow = () => { + const [currentFlow, setCurrentFlow] = useState({}); + const { + visible: flowSettingVisible, + hideModal: hideFlowSettingModal, + showModal: showFileRenameModal, + } = useSetModalState(); + const { loading, setFlow } = useSetFlow(); + + const onFlowOk = useCallback( + async (title: string) => { + const ret = await setFlow({ title, dsl }); + + if (ret === 0) { + hideFlowSettingModal(); + } + }, + [setFlow, hideFlowSettingModal], + ); + + const handleShowFlowSettingModal = useCallback( + async (record: any) => { + setCurrentFlow(record); + showFileRenameModal(); + }, + [showFileRenameModal], + ); + + return { + flowSettingLoading: loading, + initialFlowName: '', + onFlowOk, + flowSettingVisible, + hideFlowSettingModal, + showFlowSettingModal: handleShowFlowSettingModal, + }; +}; diff --git a/web/src/pages/flow/list/index.less b/web/src/pages/flow/list/index.less new file mode 100644 index 00000000..6c171193 --- /dev/null +++ b/web/src/pages/flow/list/index.less @@ -0,0 +1,48 @@ +.flowListWrapper { + padding: 48px; +} + +.topWrapper { + display: flex; + justify-content: space-between; + align-items: flex-start; + padding: 0 60px 72px; + + .title { + font-family: Inter; + font-size: 30px; + font-style: normal; + font-weight: @fontWeight600; + line-height: 38px; + color: rgba(16, 24, 40, 1); + } + .description { + font-family: Inter; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 24px; + color: rgba(71, 84, 103, 1); + } + + .topButton { + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: @fontWeight600; + line-height: 20px; + } + + .filterButton { + display: flex; + align-items: center; + .topButton(); + } +} +.flowCardContainer { + padding: 0 60px; + overflow: auto; + .knowledgeEmpty { + width: 100%; + } +} diff --git a/web/src/pages/flow/list/index.tsx b/web/src/pages/flow/list/index.tsx new file mode 100644 index 00000000..5b498269 --- /dev/null +++ b/web/src/pages/flow/list/index.tsx @@ -0,0 +1,53 @@ +import RenameModal from '@/components/rename-modal'; +import { PlusOutlined } from '@ant-design/icons'; +import { Button, Empty, Flex, Spin } from 'antd'; +import FlowCard from './flow-card'; +import { useFetchDataOnMount, useSaveFlow } from './hooks'; + +import styles from './index.less'; + +const FlowList = () => { + const { + showFlowSettingModal, + hideFlowSettingModal, + flowSettingVisible, + flowSettingLoading, + onFlowOk, + } = useSaveFlow(); + + const { list, loading } = useFetchDataOnMount(); + + return ( + + + + + + + {list.length > 0 ? ( + list.map((item: any) => { + return ; + }) + ) : ( + + )} + + + + + ); +}; + +export default FlowList; diff --git a/web/src/pages/flow/mock.tsx b/web/src/pages/flow/mock.tsx index 4b891646..1e393bf8 100644 --- a/web/src/pages/flow/mock.tsx +++ b/web/src/pages/flow/mock.tsx @@ -1,12 +1,7 @@ -import { - MergeCellsOutlined, - RocketOutlined, - SendOutlined, -} from '@ant-design/icons'; +import { MergeCellsOutlined, RocketOutlined } from '@ant-design/icons'; import { Position } from 'reactflow'; export const componentList = [ - { name: 'Begin', icon: , description: '' }, { name: 'Retrieval', icon: , description: '' }, { name: 'Generate', icon: , description: '' }, ]; @@ -159,7 +154,14 @@ export const dsl = { 'Retrieval:China': { obj: { component_name: 'Retrieval', - params: {}, + params: { + similarity_threshold: 0.2, + keywords_similarity_weight: 0.3, + top_n: 6, + top_k: 1024, + rerank_id: 'BAAI/bge-reranker-v2-m3', + kb_ids: ['568aa82603b611efa9d9fa163e197198'], + }, }, downstream: ['Generate:China'], upstream: ['Answer:China'], @@ -167,7 +169,12 @@ export const dsl = { 'Generate:China': { obj: { component_name: 'Generate', - params: {}, + params: { + llm_id: 'deepseek-chat', + prompt: + 'You are an intelligent assistant. Please summarize the content of the knowledge base to answer the question. Please list the data in the knowledge base and answer in detail. When all knowledge base content is irrelevant to the question, your answer must include the sentence "The answer you are looking for is not found in the knowledge base!" Answers need to consider chat history.\n Here is the knowledge base:\n {input}\n The above is the knowledge base.', + temperature: 0.2, + }, }, downstream: ['Answer:China'], upstream: ['Retrieval:China'], diff --git a/web/src/pages/flow/retrieval-form/index.tsx b/web/src/pages/flow/retrieval-form/index.tsx new file mode 100644 index 00000000..afbea852 --- /dev/null +++ b/web/src/pages/flow/retrieval-form/index.tsx @@ -0,0 +1,43 @@ +import KnowledgeBaseItem from '@/components/knowledge-base-item'; +import Rerank from '@/components/rerank'; +import SimilaritySlider from '@/components/similarity-slider'; +import TopNItem from '@/components/top-n-item'; +import type { FormProps } from 'antd'; +import { Form } from 'antd'; +import { IOperatorForm } from '../interface'; + +type FieldType = { + top_n?: number; +}; + +const onFinish: FormProps['onFinish'] = (values) => { + console.log('Success:', values); +}; + +const onFinishFailed: FormProps['onFinishFailed'] = (errorInfo) => { + console.log('Failed:', errorInfo); +}; + +const RetrievalForm = ({ onValuesChange }: IOperatorForm) => { + const [form] = Form.useForm(); + + return ( +
+ + + + +
+ ); +}; + +export default RetrievalForm; diff --git a/web/src/pages/flow/store.ts b/web/src/pages/flow/store.ts new file mode 100644 index 00000000..1d6b8d5f --- /dev/null +++ b/web/src/pages/flow/store.ts @@ -0,0 +1,106 @@ +import type {} from '@redux-devtools/extension'; +import { + Connection, + Edge, + EdgeChange, + Node, + NodeChange, + OnConnect, + OnEdgesChange, + OnNodesChange, + OnSelectionChangeFunc, + OnSelectionChangeParams, + addEdge, + applyEdgeChanges, + applyNodeChanges, +} from 'reactflow'; +import { create } from 'zustand'; +import { devtools } from 'zustand/middleware'; +import { NodeData } from './interface'; +import { dsl } from './mock'; + +const { nodes: initialNodes, edges: initialEdges } = dsl.graph; + +export type RFState = { + nodes: Node[]; + edges: Edge[]; + selectedNodeIds: string[]; + selectedEdgeIds: string[]; + onNodesChange: OnNodesChange; + onEdgesChange: OnEdgesChange; + onConnect: OnConnect; + setNodes: (nodes: Node[]) => void; + setEdges: (edges: Edge[]) => void; + updateNodeForm: (nodeId: string, values: any) => void; + onSelectionChange: OnSelectionChangeFunc; + addNode: (nodes: Node) => void; + deleteEdge: () => void; + deleteEdgeById: (id: string) => void; +}; + +// this is our useStore hook that we can use in our components to get parts of the store and call actions +const useStore = create()( + devtools((set, get) => ({ + nodes: initialNodes as Node[], + edges: initialEdges as Edge[], + selectedNodeIds: [], + selectedEdgeIds: [], + onNodesChange: (changes: NodeChange[]) => { + set({ + nodes: applyNodeChanges(changes, get().nodes), + }); + }, + onEdgesChange: (changes: EdgeChange[]) => { + set({ + edges: applyEdgeChanges(changes, get().edges), + }); + }, + onConnect: (connection: Connection) => { + set({ + edges: addEdge(connection, get().edges), + }); + }, + onSelectionChange: ({ nodes, edges }: OnSelectionChangeParams) => { + set({ + selectedEdgeIds: edges.map((x) => x.id), + selectedNodeIds: nodes.map((x) => x.id), + }); + }, + setNodes: (nodes: Node[]) => { + set({ nodes }); + }, + setEdges: (edges: Edge[]) => { + set({ edges }); + }, + addNode: (node: Node) => { + set({ nodes: get().nodes.concat(node) }); + }, + deleteEdge: () => { + const { edges, selectedEdgeIds } = get(); + set({ + edges: edges.filter((edge) => + selectedEdgeIds.every((x) => x !== edge.id), + ), + }); + }, + deleteEdgeById: (id: string) => { + const { edges } = get(); + set({ + edges: edges.filter((edge) => edge.id !== id), + }); + }, + updateNodeForm: (nodeId: string, values: any) => { + set({ + nodes: get().nodes.map((node) => { + if (node.id === nodeId) { + node.data = { ...node.data, form: values }; + } + + return node; + }), + }); + }, + })), +); + +export default useStore; diff --git a/web/src/pages/flow/utils.ts b/web/src/pages/flow/utils.ts index eadd8e53..7cd5810e 100644 --- a/web/src/pages/flow/utils.ts +++ b/web/src/pages/flow/utils.ts @@ -2,6 +2,7 @@ import { DSLComponents } from '@/interfaces/database/flow'; import dagre from 'dagre'; import { Edge, MarkerType, Node, Position } from 'reactflow'; import { v4 as uuidv4 } from 'uuid'; +import { NodeData } from './interface'; const buildEdges = ( operatorIds: string[], @@ -96,3 +97,35 @@ export const getLayoutedElements = ( return { nodes, edges }; }; + +const buildComponentDownstreamOrUpstream = ( + edges: Edge[], + nodeId: string, + isBuildDownstream = true, +) => { + return edges + .filter((y) => y[isBuildDownstream ? 'source' : 'target'] === nodeId) + .map((y) => y[isBuildDownstream ? 'target' : 'source']); +}; + +// construct a dsl based on the node information of the graph +export const buildDslComponentsByGraph = ( + nodes: Node[], + edges: Edge[], +): DSLComponents => { + const components: DSLComponents = {}; + + nodes.forEach((x) => { + const id = x.id; + components[id] = { + obj: { + component_name: x.data.label, + params: x.data.form as Record, + }, + downstream: buildComponentDownstreamOrUpstream(edges, id, true), + upstream: buildComponentDownstreamOrUpstream(edges, id, false), + }; + }); + + return components; +}; diff --git a/web/src/routes.ts b/web/src/routes.ts index 965ddf63..551fcb8b 100644 --- a/web/src/routes.ts +++ b/web/src/routes.ts @@ -90,6 +90,10 @@ const routes = [ }, { path: '/flow', + component: '@/pages/flow/list', + }, + { + path: '/flow/:id', component: '@/pages/flow', }, ], diff --git a/web/src/services/flow-service.ts b/web/src/services/flow-service.ts new file mode 100644 index 00000000..00bd9cc8 --- /dev/null +++ b/web/src/services/flow-service.ts @@ -0,0 +1,43 @@ +import api from '@/utils/api'; +import registerServer from '@/utils/registerServer'; +import request from '@/utils/request'; + +const { + getCanvas, + setCanvas, + listCanvas, + resetCanvas, + removeCanvas, + listTemplates, +} = api; + +const methods = { + getCanvas: { + url: getCanvas, + method: 'get', + }, + setCanvas: { + url: setCanvas, + method: 'post', + }, + listCanvas: { + url: listCanvas, + method: 'get', + }, + resetCanvas: { + url: resetCanvas, + method: 'post', + }, + removeCanvas: { + url: removeCanvas, + method: 'post', + }, + listTemplates: { + url: listTemplates, + method: 'get', + }, +} as const; + +const chatService = registerServer(methods, request); + +export default chatService; diff --git a/web/src/utils/api.ts b/web/src/utils/api.ts index fe083d67..52b37660 100644 --- a/web/src/utils/api.ts +++ b/web/src/utils/api.ts @@ -81,4 +81,12 @@ export default { // system getSystemVersion: `${api_host}/system/version`, getSystemStatus: `${api_host}/system/status`, + + // flow + listTemplates: `${api_host}/canvas/templates`, + listCanvas: `${api_host}/canvas/list`, + getCanvas: `${api_host}/canvas/get`, + removeCanvas: `${api_host}/canvas/rm`, + setCanvas: `${api_host}/canvas/set`, + resetCanvas: `${api_host}/canvas/reset`, }; diff --git a/web/src/utils/registerServer.ts b/web/src/utils/registerServer.ts index 02ae2b1f..046b863d 100644 --- a/web/src/utils/registerServer.ts +++ b/web/src/utils/registerServer.ts @@ -1,7 +1,7 @@ import omit from 'lodash/omit'; import { RequestMethod } from 'umi-request'; -type Service = Record any>; +type Service = Record any>; const registerServer = ( opt: Record,