From 565bfc84868967974d3e17f05d1146a90cb09de5 Mon Sep 17 00:00:00 2001 From: Archer <545436317@qq.com> Date: Wed, 19 Jun 2024 14:38:21 +0800 Subject: [PATCH] Concat plugin to app (#1799) --- .github/workflows/build-sandbox-image.yml | 92 ++-- .../zh-cn/docs/development/upgrading/485.md | 37 ++ .../global/common/file/image/constants.ts | 1 + packages/global/core/app/constants.ts | 15 +- packages/global/core/app/controller.d.ts | 24 + .../core/{plugin => app}/httpPlugin/type.d.ts | 0 .../core/{plugin => app}/httpPlugin/utils.ts | 20 +- packages/global/core/app/type.d.ts | 7 + packages/global/core/app/utils.ts | 2 +- packages/global/core/app/version.d.ts | 8 +- packages/global/core/plugin/controller.d.ts | 2 +- packages/global/core/plugin/type.d.ts | 3 +- .../global/core/workflow/node/constant.ts | 1 + .../core/workflow/template/constants.ts | 46 +- packages/global/core/workflow/type/index.d.ts | 7 +- packages/global/tsconfig.json | 18 +- .../service/common/string/tiktoken/index.ts | 9 +- .../core/{ => app}/plugin/controller.ts | 16 +- packages/service/core/app/schema.ts | 24 +- packages/service/core/app/version/schema.ts | 3 +- packages/service/core/plugin/schema.ts | 5 +- packages/service/core/plugin/store/schema.ts | 31 -- .../core/workflow/dispatch/plugin/run.ts | 19 +- .../core/workflow/dispatchV1/plugin/run.ts | 7 +- .../service/support/permission/auth/plugin.ts | 84 --- .../service/support/permission/teamLimit.ts | 20 +- packages/service/tsconfig.json | 18 +- .../web/components/common/Icon/constants.ts | 6 + .../Icon/icons/common/leftArrowLight.svg | 4 + .../Icon/icons/core/app/type/httpPlugin.svg | 4 + .../icons/core/app/type/httpPluginFill.svg | 13 + .../Icon/icons/core/app/type/plugin.svg | 4 + .../Icon/icons/core/app/type/pluginFill.svg | 13 + .../Icon/icons/core/app/type/workflowFill.svg | 34 ++ .../icons/support/permission/privateLight.svg | 4 +- packages/web/components/common/Icon/index.tsx | 6 +- .../web/components/common/MyMenu/index.tsx | 8 +- .../web/components/common/MyModal/index.tsx | 2 +- .../common/MyPopover/PopoverConfirm.tsx | 98 ++++ .../web/components/common/MyPopover/index.tsx | 8 +- .../web/components/common/Tabs/RowTabs.tsx | 2 +- packages/web/components/common/Tag/index.tsx | 8 +- packages/web/hooks/useConfirm.tsx | 8 +- packages/web/styles/theme.ts | 51 +- packages/web/tsconfig.json | 20 +- projects/app/Dockerfile | 2 +- .../data/pluginTemplates/customFeedback.json | 1 + .../data/pluginTemplates/getCurrentTime.json | 1 + .../app/data/pluginTemplates/textEditor.json | 1 + projects/app/i18n/en/app.json | 21 +- projects/app/i18n/en/common.json | 3 +- projects/app/i18n/zh/app.json | 21 +- projects/app/i18n/zh/common.json | 3 +- .../app/public/imgs/app/httpPluginFill.svg | 13 + .../ChatBox/Input/InputGuideBox.tsx | 4 +- projects/app/src/components/Layout/index.tsx | 5 +- projects/app/src/components/Layout/navbar.tsx | 7 - projects/app/src/components/Tabs/index.tsx | 6 +- .../src/components/common/MyRadio/index.tsx | 2 +- .../src/components/common/Rowtabs/index.tsx | 48 -- .../app/src/components/common/folder/Path.tsx | 17 +- .../app/src/components/core/app/TypeTag.tsx | 10 +- .../components/core/app/WelcomeTextConfig.tsx | 5 +- .../components/core/workflow/Flow/index.tsx | 300 ---------- .../src/components/support/apikey/Table.tsx | 18 +- .../permission/ConfigPerModal/index.tsx | 2 +- .../user/team/TeamManageModal/TeamCard.tsx | 2 +- projects/app/src/global/core/app/api.d.ts | 2 +- .../app/src/global/core/workflow/api.d.ts | 3 +- projects/app/src/pages/_app.tsx | 2 +- .../pages/account/components/ApiKeyTable.tsx | 7 +- projects/app/src/pages/api/admin/initv485.ts | 163 ++++++ projects/app/src/pages/api/core/app/create.ts | 112 ++-- projects/app/src/pages/api/core/app/del.ts | 30 +- .../pages/api/core/app/httpPlugin/create.ts | 68 +++ .../httpPlugin/getApiSchemaByUrl.ts | 0 .../pages/api/core/app/httpPlugin/update.ts | 127 +++++ projects/app/src/pages/api/core/app/list.ts | 43 +- .../api/core/app/plugin/getPreviewNode.ts | 33 ++ .../plugin}/getSystemPluginTemplates.ts | 4 +- .../pages/api/core/app/transitionWorkflow.ts | 53 ++ projects/app/src/pages/api/core/app/update.ts | 9 +- .../src/pages/api/core/app/version/latest.ts | 36 ++ .../src/pages/api/core/app/version/publish.ts | 36 +- .../src/pages/api/core/app/version/revert.ts | 24 +- .../pages/api/core/chat/inputGuide/query.ts | 3 +- .../app/src/pages/api/core/plugin/create.ts | 78 --- .../app/src/pages/api/core/plugin/delete.ts | 41 -- .../app/src/pages/api/core/plugin/detail.ts | 21 - .../pages/api/core/plugin/getPreviewNode.ts | 28 - .../app/src/pages/api/core/plugin/list.ts | 36 -- .../app/src/pages/api/core/plugin/paths.ts | 46 -- .../pluginTemplate/getTeamPluginTemplates.ts | 64 --- .../app/src/pages/api/core/plugin/update.ts | 152 ----- .../app/src/pages/api/core/workflow/debug.ts | 12 +- .../app/detail/components/FlowEdit/Header.tsx | 375 ------------- .../pages/app/detail/components/InfoModal.tsx | 2 +- .../components/{Logs.tsx => Logs/index.tsx} | 204 +++---- .../app/detail/components/Plugin/Header.tsx | 191 +++++++ .../app/detail/components/Plugin/index.tsx | 72 +++ .../detail/components/Publish/API/index.tsx | 6 +- .../detail/components/Publish/Link/index.tsx | 6 +- .../app/detail/components/Publish/index.tsx | 40 +- .../components/PublishHistoriesSlider.tsx | 78 +-- .../pages/app/detail/components/RouteTab.tsx | 75 +++ .../detail/components/SimpleApp/AppCard.tsx | 187 +++++++ .../detail/components/SimpleApp/ChatTest.tsx | 63 +++ .../app/detail/components/SimpleApp/Edit.tsx | 80 +++ .../detail/components/SimpleApp/EditForm.tsx | 480 ++++++++++++++++ .../detail/components/SimpleApp/Header.tsx | 164 ++++++ .../components}/ToolSelectModal.tsx | 110 ++-- .../app/detail/components/SimpleApp/index.tsx | 34 ++ .../components/SimpleApp/styles.module.scss | 10 + .../detail/components/SimpleEdit/AppCard.tsx | 175 ------ .../detail/components/SimpleEdit/ChatTest.tsx | 160 ------ .../detail/components/SimpleEdit/EditForm.tsx | 521 ------------------ .../detail/components/SimpleEdit/index.tsx | 70 --- .../{SimpleEdit => }/TagsEditModal.tsx | 2 +- .../app/detail/components/Workflow/Header.tsx | 198 +++++++ .../{FlowEdit => Workflow}/index.tsx | 76 +-- .../components/WorkflowComponents/AppCard.tsx | 222 ++++++++ .../WorkflowComponents}/Flow/ChatTest.tsx | 103 +--- .../Flow/ImportSettings.tsx | 0 .../Flow/NodeTemplatesModal.tsx | 372 ++++++------- .../Flow/SelectAppModal.tsx | 0 .../Flow/components/ButtonEdge.tsx | 2 +- .../Flow/components/Container.tsx | 0 .../Flow/components/Divider.tsx | 0 .../Flow/components/IOTitle.tsx | 0 .../Flow/hooks/useDebug.tsx | 4 + .../Flow/hooks/useKeyboard.tsx | 14 +- .../Flow/hooks/useWorkflow.tsx | 127 +++++ .../WorkflowComponents/Flow/index.tsx | 184 +++++++ .../Flow/nodes/NodeAnswer.tsx | 0 .../Flow/nodes/NodeCQNode.tsx | 0 .../Flow/nodes/NodeCode.tsx | 0 .../Flow/nodes/NodeDatasetConcat.tsx | 0 .../Flow/nodes/NodeEmpty.tsx | 0 .../nodes/NodeExtract/ExtractFieldModal.tsx | 0 .../Flow/nodes/NodeExtract/index.tsx | 0 .../Flow/nodes/NodeHttp/CurlImportModal.tsx | 0 .../Flow/nodes/NodeHttp/index.tsx | 2 +- .../Flow/nodes/NodeIfElse/ListItem.tsx | 2 +- .../Flow/nodes/NodeIfElse/index.tsx | 0 .../Flow/nodes/NodeLaf.tsx | 4 +- .../Flow/nodes/NodePluginInput.tsx | 2 +- .../Flow/nodes/NodePluginOutput.tsx | 0 .../Flow/nodes/NodeSimple.tsx | 0 .../Flow/nodes/NodeSystemConfig.tsx | 2 +- .../Flow/nodes/NodeTools.tsx | 0 .../Flow/nodes/NodeVariableUpdate.tsx | 4 +- .../Flow/nodes/NodeWorkflowStart.tsx | 2 +- .../Flow/nodes/render/FieldEditModal.tsx | 0 .../nodes/render/Handle/ConnectionHandle.tsx | 6 +- .../Flow/nodes/render/Handle/ToolHandle.tsx | 6 +- .../Flow/nodes/render/Handle/index.tsx | 6 +- .../Flow/nodes/render/Handle/style.tsx} | 4 + .../Flow/nodes/render/NodeCard.tsx | 34 +- .../Flow/nodes/render/RenderInput/Label.tsx | 2 +- .../Flow/nodes/render/RenderInput/index.tsx | 0 .../RenderInput/templates/AddInputParam.tsx | 10 +- .../RenderInput/templates/JsonEditor.tsx | 4 +- .../RenderInput/templates/NumberInput.tsx | 2 +- .../RenderInput/templates/Reference.tsx | 4 +- .../render/RenderInput/templates/Select.tsx | 2 +- .../RenderInput/templates/SelectApp.tsx | 2 +- .../RenderInput/templates/SelectDataset.tsx | 2 +- .../templates/SelectDatasetParams.tsx | 2 +- .../RenderInput/templates/SelectLLMModel.tsx | 2 +- .../RenderInput/templates/SettingLLMModel.tsx | 2 +- .../templates/SettingQuotePrompt.tsx | 4 +- .../render/RenderInput/templates/Slider.tsx | 2 +- .../render/RenderInput/templates/Switch.tsx | 2 +- .../RenderInput/templates/TextInput.tsx | 2 +- .../render/RenderInput/templates/Textarea.tsx | 4 +- .../Flow/nodes/render/RenderInput/type.d.ts | 0 .../Flow/nodes/render/RenderOutput/Label.tsx | 0 .../Flow/nodes/render/RenderOutput/index.tsx | 2 +- .../Flow/nodes/render/RenderOutput/type.d.ts | 0 .../render/RenderToolInput/EditFieldModal.tsx | 2 +- .../render/RenderToolInput/constants.tsx} | 4 + .../nodes/render/RenderToolInput/index.tsx | 2 +- .../nodes/render/RenderToolInput/type.d.ts | 0 .../Flow/nodes/render/ValueTypeLabel.tsx | 0 .../Flow/nodes/render/VariableTable.tsx | 0 .../WorkflowComponents/constants.tsx} | 4 + .../WorkflowComponents}/context.tsx | 251 ++++++--- .../components/WorkflowComponents/utils.tsx} | 7 +- .../pages/app/detail/components/constants.tsx | 33 ++ .../pages/app/detail/components/context.tsx | 207 +++++++ .../app/detail/components/useChatTest.tsx | 87 +++ projects/app/src/pages/app/detail/index.tsx | 198 +------ .../{component => components}/CreateModal.tsx | 44 +- .../list/components}/HttpPluginEditModal.tsx | 194 +++---- .../list/{component => components}/List.tsx | 71 ++- .../{component => components}/context.tsx | 39 +- projects/app/src/pages/app/list/index.tsx | 185 +++++-- projects/app/src/pages/chat/index.tsx | 6 +- projects/app/src/pages/chat/share.tsx | 26 +- projects/app/src/pages/chat/team.tsx | 6 +- .../LoginForm/components/FormLayout.tsx | 2 +- .../pages/login/components/RegisterForm.tsx | 4 +- projects/app/src/pages/plugin/edit/Header.tsx | 169 ------ projects/app/src/pages/plugin/edit/index.tsx | 98 ---- .../pages/plugin/list/component/EditModal.tsx | 250 --------- .../src/pages/plugin/list/component/type.d.ts | 5 - projects/app/src/pages/plugin/list/index.tsx | 241 -------- projects/app/src/pages/tools/index.tsx | 5 - projects/app/src/web/common/utils/voice.ts | 9 +- projects/app/src/web/core/app/api/app.ts | 9 + projects/app/src/web/core/app/api/plugin.ts | 50 ++ .../app/{versionApi.ts => api/version.ts} | 7 + .../src/web/core/app/context/appContext.tsx | 114 ---- projects/app/src/web/core/app/templates.ts | 58 +- projects/app/src/web/core/app/utils.ts | 12 +- projects/app/src/web/core/plugin/api.ts | 41 -- .../src/web/core/workflow/store/workflow.ts | 56 -- projects/app/src/web/core/workflow/utils.ts | 82 +++ projects/app/tsconfig.json | 18 +- tsconfig.json | 21 + 220 files changed, 5018 insertions(+), 4667 deletions(-) create mode 100644 docSite/content/zh-cn/docs/development/upgrading/485.md create mode 100644 packages/global/core/app/controller.d.ts rename packages/global/core/{plugin => app}/httpPlugin/type.d.ts (100%) rename packages/global/core/{plugin => app}/httpPlugin/utils.ts (96%) rename packages/service/core/{ => app}/plugin/controller.ts (88%) delete mode 100644 packages/service/core/plugin/store/schema.ts delete mode 100644 packages/service/support/permission/auth/plugin.ts create mode 100644 packages/web/components/common/Icon/icons/common/leftArrowLight.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/type/httpPlugin.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/type/httpPluginFill.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/type/plugin.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/type/pluginFill.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/type/workflowFill.svg create mode 100644 packages/web/components/common/MyPopover/PopoverConfirm.tsx create mode 100644 projects/app/public/imgs/app/httpPluginFill.svg delete mode 100644 projects/app/src/components/common/Rowtabs/index.tsx delete mode 100644 projects/app/src/components/core/workflow/Flow/index.tsx create mode 100644 projects/app/src/pages/api/admin/initv485.ts create mode 100644 projects/app/src/pages/api/core/app/httpPlugin/create.ts rename projects/app/src/pages/api/core/{plugin => app}/httpPlugin/getApiSchemaByUrl.ts (100%) create mode 100644 projects/app/src/pages/api/core/app/httpPlugin/update.ts create mode 100644 projects/app/src/pages/api/core/app/plugin/getPreviewNode.ts rename projects/app/src/pages/api/core/{plugin/pluginTemplate => app/plugin}/getSystemPluginTemplates.ts (89%) create mode 100644 projects/app/src/pages/api/core/app/transitionWorkflow.ts create mode 100644 projects/app/src/pages/api/core/app/version/latest.ts delete mode 100644 projects/app/src/pages/api/core/plugin/create.ts delete mode 100644 projects/app/src/pages/api/core/plugin/delete.ts delete mode 100644 projects/app/src/pages/api/core/plugin/detail.ts delete mode 100644 projects/app/src/pages/api/core/plugin/getPreviewNode.ts delete mode 100644 projects/app/src/pages/api/core/plugin/list.ts delete mode 100644 projects/app/src/pages/api/core/plugin/paths.ts delete mode 100644 projects/app/src/pages/api/core/plugin/pluginTemplate/getTeamPluginTemplates.ts delete mode 100644 projects/app/src/pages/api/core/plugin/update.ts delete mode 100644 projects/app/src/pages/app/detail/components/FlowEdit/Header.tsx rename projects/app/src/pages/app/detail/components/{Logs.tsx => Logs/index.tsx} (64%) create mode 100644 projects/app/src/pages/app/detail/components/Plugin/Header.tsx create mode 100644 projects/app/src/pages/app/detail/components/Plugin/index.tsx rename projects/app/src/{components/core/workflow => pages/app/detail}/components/PublishHistoriesSlider.tsx (78%) create mode 100644 projects/app/src/pages/app/detail/components/RouteTab.tsx create mode 100644 projects/app/src/pages/app/detail/components/SimpleApp/AppCard.tsx create mode 100644 projects/app/src/pages/app/detail/components/SimpleApp/ChatTest.tsx create mode 100644 projects/app/src/pages/app/detail/components/SimpleApp/Edit.tsx create mode 100644 projects/app/src/pages/app/detail/components/SimpleApp/EditForm.tsx create mode 100644 projects/app/src/pages/app/detail/components/SimpleApp/Header.tsx rename projects/app/src/pages/app/detail/components/{SimpleEdit => SimpleApp/components}/ToolSelectModal.tsx (80%) create mode 100644 projects/app/src/pages/app/detail/components/SimpleApp/index.tsx create mode 100644 projects/app/src/pages/app/detail/components/SimpleApp/styles.module.scss delete mode 100644 projects/app/src/pages/app/detail/components/SimpleEdit/AppCard.tsx delete mode 100644 projects/app/src/pages/app/detail/components/SimpleEdit/ChatTest.tsx delete mode 100644 projects/app/src/pages/app/detail/components/SimpleEdit/EditForm.tsx delete mode 100644 projects/app/src/pages/app/detail/components/SimpleEdit/index.tsx rename projects/app/src/pages/app/detail/components/{SimpleEdit => }/TagsEditModal.tsx (98%) create mode 100644 projects/app/src/pages/app/detail/components/Workflow/Header.tsx rename projects/app/src/pages/app/detail/components/{FlowEdit => Workflow}/index.tsx (52%) create mode 100644 projects/app/src/pages/app/detail/components/WorkflowComponents/AppCard.tsx rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/ChatTest.tsx (54%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/ImportSettings.tsx (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/NodeTemplatesModal.tsx (52%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/SelectAppModal.tsx (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/components/ButtonEdge.tsx (99%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/components/Container.tsx (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/components/Divider.tsx (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/components/IOTitle.tsx (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/hooks/useDebug.tsx (99%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/hooks/useKeyboard.tsx (92%) create mode 100644 projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useWorkflow.tsx create mode 100644 projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/NodeAnswer.tsx (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/NodeCQNode.tsx (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/NodeCode.tsx (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/NodeDatasetConcat.tsx (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/NodeEmpty.tsx (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/NodeExtract/ExtractFieldModal.tsx (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/NodeExtract/index.tsx (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/NodeHttp/CurlImportModal.tsx (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/NodeHttp/index.tsx (99%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/NodeIfElse/ListItem.tsx (99%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/NodeIfElse/index.tsx (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/NodeLaf.tsx (99%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/NodePluginInput.tsx (99%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/NodePluginOutput.tsx (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/NodeSimple.tsx (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/NodeSystemConfig.tsx (98%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/NodeTools.tsx (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/NodeVariableUpdate.tsx (98%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/NodeWorkflowStart.tsx (96%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/FieldEditModal.tsx (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/Handle/ConnectionHandle.tsx (98%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/Handle/ToolHandle.tsx (95%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/Handle/index.tsx (98%) rename projects/app/src/{components/core/workflow/Flow/nodes/render/Handle/style.ts => pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/style.tsx} (91%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/NodeCard.tsx (96%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderInput/Label.tsx (98%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderInput/index.tsx (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderInput/templates/AddInputParam.tsx (90%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderInput/templates/JsonEditor.tsx (93%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderInput/templates/NumberInput.tsx (92%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderInput/templates/Reference.tsx (97%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderInput/templates/Select.tsx (90%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderInput/templates/SelectApp.tsx (96%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderInput/templates/SelectDataset.tsx (97%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderInput/templates/SelectDatasetParams.tsx (97%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderInput/templates/SelectLLMModel.tsx (94%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderInput/templates/SettingLLMModel.tsx (95%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderInput/templates/SettingQuotePrompt.tsx (98%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderInput/templates/Slider.tsx (92%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderInput/templates/Switch.tsx (89%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderInput/templates/TextInput.tsx (90%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderInput/templates/Textarea.tsx (92%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderInput/type.d.ts (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderOutput/Label.tsx (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderOutput/index.tsx (98%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderOutput/type.d.ts (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderToolInput/EditFieldModal.tsx (97%) rename projects/app/src/{components/core/workflow/Flow/nodes/render/RenderToolInput/constants.ts => pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderToolInput/constants.tsx} (91%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderToolInput/index.tsx (97%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/RenderToolInput/type.d.ts (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/ValueTypeLabel.tsx (100%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/Flow/nodes/render/VariableTable.tsx (100%) rename projects/app/src/{components/core/workflow/constants.ts => pages/app/detail/components/WorkflowComponents/constants.tsx} (91%) rename projects/app/src/{components/core/workflow => pages/app/detail/components/WorkflowComponents}/context.tsx (80%) rename projects/app/src/{components/core/workflow/utils.ts => pages/app/detail/components/WorkflowComponents/utils.tsx} (96%) create mode 100644 projects/app/src/pages/app/detail/components/constants.tsx create mode 100644 projects/app/src/pages/app/detail/components/context.tsx create mode 100644 projects/app/src/pages/app/detail/components/useChatTest.tsx rename projects/app/src/pages/app/list/{component => components}/CreateModal.tsx (81%) rename projects/app/src/pages/{plugin/list/component => app/list/components}/HttpPluginEditModal.tsx (79%) rename projects/app/src/pages/app/list/{component => components}/List.tsx (79%) rename projects/app/src/pages/app/list/{component => components}/context.tsx (84%) delete mode 100644 projects/app/src/pages/plugin/edit/Header.tsx delete mode 100644 projects/app/src/pages/plugin/edit/index.tsx delete mode 100644 projects/app/src/pages/plugin/list/component/EditModal.tsx delete mode 100644 projects/app/src/pages/plugin/list/component/type.d.ts delete mode 100644 projects/app/src/pages/plugin/list/index.tsx create mode 100644 projects/app/src/web/core/app/api/plugin.ts rename projects/app/src/web/core/app/{versionApi.ts => api/version.ts} (74%) delete mode 100644 projects/app/src/web/core/app/context/appContext.tsx delete mode 100644 projects/app/src/web/core/plugin/api.ts delete mode 100644 projects/app/src/web/core/workflow/store/workflow.ts create mode 100644 tsconfig.json diff --git a/.github/workflows/build-sandbox-image.yml b/.github/workflows/build-sandbox-image.yml index 7423c2d4b..ae7ccca5b 100644 --- a/.github/workflows/build-sandbox-image.yml +++ b/.github/workflows/build-sandbox-image.yml @@ -30,22 +30,50 @@ jobs: key: ${{ runner.os }}-buildx-${{ github.sha }} restore-keys: | ${{ runner.os }}-buildx- + - name: Login to GitHub Container Registry uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GH_PAT }} - - name: Set DOCKER_REPO_TAGGED based on branch or tag + - name: Login to Ali Hub + uses: docker/login-action@v2 + with: + registry: registry.cn-hangzhou.aliyuncs.com + username: ${{ secrets.ALI_HUB_USERNAME }} + password: ${{ secrets.ALI_HUB_PASSWORD }} + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_HUB_NAME }} + password: ${{ secrets.DOCKER_HUB_PASSWORD }} + + - name: Set image name and tag run: | if [[ "${{ github.ref_name }}" == "main" ]]; then - echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox:latest" >> $GITHUB_ENV + echo "Git_Tag=ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox:latest" >> $GITHUB_ENV + echo "Git_Latest=ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox:latest" >> $GITHUB_ENV + echo "Ali_Tag=${{ secrets.ALI_IMAGE_NAME }}/fastgpt-sandbox:latest" >> $GITHUB_ENV + echo "Ali_Latest=${{ secrets.ALI_IMAGE_NAME }}/fastgpt-sandbox:latest" >> $GITHUB_ENV + echo "Docker_Hub_Tag=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-sandbox:latest" >> $GITHUB_ENV + echo "Docker_Hub_Latest=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-sandbox:latest" >> $GITHUB_ENV else - echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox:${{ github.ref_name }}" >> $GITHUB_ENV + echo "Git_Tag=ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox:${{ github.ref_name }}" >> $GITHUB_ENV + echo "Git_Latest=ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox:latest" >> $GITHUB_ENV + echo "Ali_Tag=${{ secrets.ALI_IMAGE_NAME }}/fastgpt-sandbox:${{ github.ref_name }}" >> $GITHUB_ENV + echo "Ali_Latest=${{ secrets.ALI_IMAGE_NAME }}/fastgpt-sandbox:latest" >> $GITHUB_ENV + echo "Docker_Hub_Tag=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-sandbox:${{ github.ref_name }}" >> $GITHUB_ENV + echo "Docker_Hub_Latest=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-sandbox:latest" >> $GITHUB_ENV fi - name: Build and publish image for main branch or tag push event env: - DOCKER_REPO_TAGGED: ${{ env.DOCKER_REPO_TAGGED }} + Git_Tag: ${{ env.Git_Tag }} + Git_Latest: ${{ env.Git_Latest }} + Ali_Tag: ${{ env.Ali_Tag }} + Ali_Latest: ${{ env.Ali_Latest }} + Docker_Hub_Tag: ${{ env.Docker_Hub_Tag }} + Docker_Hub_Latest: ${{ env.Docker_Hub_Latest }} run: | docker buildx build \ -f projects/sandbox/Dockerfile \ @@ -55,54 +83,10 @@ jobs: --push \ --cache-from=type=local,src=/tmp/.buildx-cache \ --cache-to=type=local,dest=/tmp/.buildx-cache \ - -t ${DOCKER_REPO_TAGGED} \ + -t ${Git_Tag} \ + -t ${Git_Latest} \ + -t ${Ali_Tag} \ + -t ${Ali_Latest} \ + -t ${Docker_Hub_Tag} \ + -t ${Docker_Hub_Latest} \ . - push-to-ali-hub: - needs: build-fastgpt-sandbox-images - runs-on: ubuntu-20.04 - steps: - - name: Checkout code - uses: actions/checkout@v3 - - name: Login to Ali Hub - uses: docker/login-action@v2 - with: - registry: registry.cn-hangzhou.aliyuncs.com - username: ${{ secrets.ALI_HUB_USERNAME }} - password: ${{ secrets.ALI_HUB_PASSWORD }} - - name: Set DOCKER_REPO_TAGGED based on branch or tag - run: | - if [[ "${{ github.ref_name }}" == "main" ]]; then - echo "IMAGE_TAG=latest" >> $GITHUB_ENV - else - echo "IMAGE_TAG=${{ github.ref_name }}" >> $GITHUB_ENV - fi - - name: Pull image from GitHub Container Registry - run: docker pull ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox:${{env.IMAGE_TAG}} - - name: Tag image with Docker Hub repository name and version tag - run: docker tag ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox:${{env.IMAGE_TAG}} ${{ secrets.ALI_IMAGE_NAME }}/fastgpt-sandbox:${{env.IMAGE_TAG}} - - name: Push image to Docker Hub - run: docker push ${{ secrets.ALI_IMAGE_NAME }}/fastgpt-sandbox:${{env.IMAGE_TAG}} - push-to-docker-hub: - needs: build-fastgpt-sandbox-images - runs-on: ubuntu-20.04 - steps: - - name: Checkout code - uses: actions/checkout@v3 - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_HUB_NAME }} - password: ${{ secrets.DOCKER_HUB_PASSWORD }} - - name: Set DOCKER_REPO_TAGGED based on branch or tag - run: | - if [[ "${{ github.ref_name }}" == "main" ]]; then - echo "IMAGE_TAG=latest" >> $GITHUB_ENV - else - echo "IMAGE_TAG=${{ github.ref_name }}" >> $GITHUB_ENV - fi - - name: Pull image from GitHub Container Registry - run: docker pull ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox:${{env.IMAGE_TAG}} - - name: Tag image with Docker Hub repository name and version tag - run: docker tag ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox:${{env.IMAGE_TAG}} ${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-sandbox:${{env.IMAGE_TAG}} - - name: Push image to Docker Hub - run: docker push ${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-sandbox:${{env.IMAGE_TAG}} diff --git a/docSite/content/zh-cn/docs/development/upgrading/485.md b/docSite/content/zh-cn/docs/development/upgrading/485.md new file mode 100644 index 000000000..ec4d1af09 --- /dev/null +++ b/docSite/content/zh-cn/docs/development/upgrading/485.md @@ -0,0 +1,37 @@ +--- +title: 'V4.8.5(进行中)' +description: 'FastGPT V4.8.5 更新说明' +icon: 'upgrade' +draft: false +toc: true +weight: 820 +--- + +## 升级指南 + +### 1. 做好数据库备份 + +### 2. 修改镜像 + +- fastgpt 镜像 tag 修改成 v4.8.5-alpha + + + +### 3. 执行初始化 + +从任意终端,发起 1 个 HTTP 请求。其中 {{rootkey}} 替换成环境变量里的 `rootkey`;{{host}} 替换成**FastGPT 域名**。 + +```bash +curl --location --request POST 'https://{{host}}/api/admin/initv485' \ +--header 'rootkey: {{rootkey}}' \ +--header 'Content-Type: application/json' +``` + +会把插件的数据表合并到应用中,插件表不会删除。 + +## V4.8.5 更新说明 + +1. 新增 - 合并插件和应用,统一成工作台 +2. 修复 - SSR渲染 +3. 修复 - 定时任务无法实际关闭 +4. 修复 - 输入引导特殊字符导致正则报错 \ No newline at end of file diff --git a/packages/global/common/file/image/constants.ts b/packages/global/common/file/image/constants.ts index b19562eea..8e810919d 100644 --- a/packages/global/common/file/image/constants.ts +++ b/packages/global/common/file/image/constants.ts @@ -53,4 +53,5 @@ export const uniqueImageTypeList = Object.entries(mongoImageTypeMap) export const FolderIcon = 'file/fill/folder'; export const FolderImgUrl = '/imgs/files/folder.svg'; +export const HttpPluginImgUrl = '/imgs/app/httpPluginFill.svg'; export const HttpImgUrl = '/imgs/workflow/http.png'; diff --git a/packages/global/core/app/constants.ts b/packages/global/core/app/constants.ts index 4ad12b2a4..1c0dfdaba 100644 --- a/packages/global/core/app/constants.ts +++ b/packages/global/core/app/constants.ts @@ -3,19 +3,10 @@ import { AppTTSConfigType, AppWhisperConfigType } from './type'; export enum AppTypeEnum { folder = 'folder', simple = 'simple', - advanced = 'advanced' + workflow = 'advanced', + plugin = 'plugin', + httpPlugin = 'httpPlugin' } -export const AppTypeMap = { - [AppTypeEnum.folder]: { - label: 'folder' - }, - [AppTypeEnum.simple]: { - label: 'simple' - }, - [AppTypeEnum.advanced]: { - label: 'advanced' - } -}; export const defaultTTSConfig: AppTTSConfigType = { type: 'web' }; diff --git a/packages/global/core/app/controller.d.ts b/packages/global/core/app/controller.d.ts new file mode 100644 index 000000000..6ba9552d6 --- /dev/null +++ b/packages/global/core/app/controller.d.ts @@ -0,0 +1,24 @@ +import { ParentIdType } from 'common/parentFolder/type'; +import { AppSchema } from './type'; +import { AppTypeEnum } from './constants'; + +export type CreateAppProps = { + parentId?: ParentIdType; + name?: string; + avatar?: string; + intro?: string; + type?: AppTypeEnum; + modules: AppSchema['modules']; + edges?: AppSchema['edges']; +}; +export type CreateHttpPluginChildrenPros = Omit & { + parentId: ParentIdType; + name: string; + intro: string; + avatar: string; + modules: AppSchema['modules']; + edges: AppSchema['edges']; + pluginData: { + pluginUniId: string; + }; +}; diff --git a/packages/global/core/plugin/httpPlugin/type.d.ts b/packages/global/core/app/httpPlugin/type.d.ts similarity index 100% rename from packages/global/core/plugin/httpPlugin/type.d.ts rename to packages/global/core/app/httpPlugin/type.d.ts diff --git a/packages/global/core/plugin/httpPlugin/utils.ts b/packages/global/core/app/httpPlugin/utils.ts similarity index 96% rename from packages/global/core/plugin/httpPlugin/utils.ts rename to packages/global/core/app/httpPlugin/utils.ts index c2121ea3e..248c8b6ae 100644 --- a/packages/global/core/plugin/httpPlugin/utils.ts +++ b/packages/global/core/app/httpPlugin/utils.ts @@ -2,19 +2,20 @@ import { getNanoid } from '../../../common/string/tools'; import { OpenApiJsonSchema } from './type'; import yaml from 'js-yaml'; import { OpenAPIV3 } from 'openapi-types'; -import { PluginTypeEnum } from '../constants'; -import { FlowNodeInputItemType, FlowNodeOutputItemType } from '../../workflow/type/io.d'; +import { FlowNodeInputItemType, FlowNodeOutputItemType } from '../../workflow/type/io'; import { FlowNodeInputTypeEnum, FlowNodeOutputTypeEnum } from '../../workflow/node/constant'; import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '../../workflow/constants'; import { PluginInputModule } from '../../workflow/template/system/pluginInput'; import { PluginOutputModule } from '../../workflow/template/system/pluginOutput'; import { HttpModule468 } from '../../workflow/template/system/http468'; import { HttpParamAndHeaderItemType } from '../../workflow/api'; -import { CreateOnePluginParams } from '../controller'; import { StoreNodeItemType } from '../../workflow/type'; import { HttpImgUrl } from '../../../common/file/image/constants'; import SwaggerParser from '@apidevtools/swagger-parser'; -import { getHandleId } from '../../../core/workflow/utils'; +import { getHandleId } from '../../workflow/utils'; +import { CreateHttpPluginChildrenPros } from '../controller'; +import { AppTypeEnum } from '../constants'; +import type { StoreEdgeItemType } from '../../workflow/type/edge'; export const str2OpenApiSchema = async (yamlStr = ''): Promise => { try { @@ -89,7 +90,7 @@ export const httpApiSchema2Plugins = async ({ parentId: string; apiSchemaStr?: string; customHeader?: string; -}): Promise => { +}): Promise => { const jsonSchema = await str2OpenApiSchema(apiSchemaStr); const baseUrl = jsonSchema.serverPath; @@ -400,7 +401,7 @@ export const httpApiSchema2Plugins = async ({ } ]; - const edges = [ + const edges: StoreEdgeItemType[] = [ { source: pluginInputId, target: httpId, @@ -420,9 +421,12 @@ export const httpApiSchema2Plugins = async ({ avatar: HttpImgUrl, intro: item.description, parentId, - type: PluginTypeEnum.http, + type: AppTypeEnum.plugin, modules, - edges + edges, + pluginData: { + pluginUniId: item.name + } }; }); }; diff --git a/packages/global/core/app/type.d.ts b/packages/global/core/app/type.d.ts index 19185659a..c837fcd6c 100644 --- a/packages/global/core/app/type.d.ts +++ b/packages/global/core/app/type.d.ts @@ -25,6 +25,12 @@ export type AppSchema = { modules: StoreNodeItemType[]; edges: StoreEdgeItemType[]; + pluginData?: { + nodeVersion?: string; + pluginUniId?: string; // plugin unique id(plugin name) + apiSchemaStr?: string; // api schema string + customHeaders?: string; + }; // App system config chatConfig: AppChatConfigType; @@ -44,6 +50,7 @@ export type AppListItemType = { type: AppTypeEnum; defaultPermission: PermissionValueType; permission: AppPermission; + pluginData?: AppSchema['pluginData']; }; export type AppDetailType = AppSchema & { diff --git a/packages/global/core/app/utils.ts b/packages/global/core/app/utils.ts index 667ee89d8..041e77be0 100644 --- a/packages/global/core/app/utils.ts +++ b/packages/global/core/app/utils.ts @@ -99,7 +99,7 @@ export const appWorkflow2Form = ({ if (!node.pluginId) return; defaultAppForm.selectedTools.push({ - id: node.pluginId, + id: node.nodeId, pluginId: node.pluginId, name: node.name, avatar: node.avatar, diff --git a/packages/global/core/app/version.d.ts b/packages/global/core/app/version.d.ts index 4b3c9920b..5fdc68f63 100644 --- a/packages/global/core/app/version.d.ts +++ b/packages/global/core/app/version.d.ts @@ -1,12 +1,12 @@ import { StoreNodeItemType } from '../workflow/type'; import { StoreEdgeItemType } from '../workflow/type/edge'; -import { AppChatConfigType } from './type'; +import { AppChatConfigType, AppSchema } from './type'; export type AppVersionSchemaType = { _id: string; appId: string; time: Date; - nodes: StoreNodeItemType[]; - edges: StoreEdgeItemType[]; - chatConfig: AppChatConfigType; + nodes: AppSchema['modules']; + edges: AppSchema['edges']; + chatConfig: AppSchema['chatConfig']; }; diff --git a/packages/global/core/plugin/controller.d.ts b/packages/global/core/plugin/controller.d.ts index c550dbb80..4b245f211 100644 --- a/packages/global/core/plugin/controller.d.ts +++ b/packages/global/core/plugin/controller.d.ts @@ -1,7 +1,7 @@ import { StoreEdgeItemType } from 'core/workflow/type/edge'; import type { StoreNodeItemType } from '../workflow/type'; import { PluginTypeEnum } from './constants'; -import { HttpAuthMethodType } from './httpPlugin/type'; +import { HttpAuthMethodType } from '../app/httpPlugin/type'; export type CreateOnePluginParams = { name: string; diff --git a/packages/global/core/plugin/type.d.ts b/packages/global/core/plugin/type.d.ts index 7d293862b..873ca6f7a 100644 --- a/packages/global/core/plugin/type.d.ts +++ b/packages/global/core/plugin/type.d.ts @@ -24,6 +24,7 @@ export type PluginItemSchema = { }; version?: 'v1' | 'v2'; nodeVersion?: string; + inited?: boolean; }; /* plugin template */ @@ -33,7 +34,7 @@ export type PluginTemplateType = PluginRuntimeType & { source: `${PluginSourceEnum}`; templateType: FlowNodeTemplateType['templateType']; intro: string; - nodeVersion: string; + version: string; }; export type PluginRuntimeType = { diff --git a/packages/global/core/workflow/node/constant.ts b/packages/global/core/workflow/node/constant.ts index 08fea5175..7b9047f91 100644 --- a/packages/global/core/workflow/node/constant.ts +++ b/packages/global/core/workflow/node/constant.ts @@ -118,3 +118,4 @@ export enum FlowNodeTypeEnum { } export const EDGE_TYPE = 'default'; +export const defaultNodeVersion = '481'; diff --git a/packages/global/core/workflow/template/constants.ts b/packages/global/core/workflow/template/constants.ts index 8358ce5b9..c8cf7f22d 100644 --- a/packages/global/core/workflow/template/constants.ts +++ b/packages/global/core/workflow/template/constants.ts @@ -24,10 +24,7 @@ import { IfElseNode } from './system/ifElse/index'; import { VariableUpdateNode } from './system/variableUpdate'; import { CodeNode } from './system/sandbox'; -/* app flow module templates */ -export const appSystemModuleTemplates: FlowNodeTemplateType[] = [ - SystemConfigNode, - WorkflowStart, +const systemNodes: FlowNodeTemplateType[] = [ AiChatModule, AssignedAnswerModule, DatasetSearchModule, @@ -44,49 +41,26 @@ export const appSystemModuleTemplates: FlowNodeTemplateType[] = [ VariableUpdateNode, CodeNode ]; +/* app flow module templates */ +export const appSystemModuleTemplates: FlowNodeTemplateType[] = [ + SystemConfigNode, + WorkflowStart, + ...systemNodes +]; /* plugin flow module templates */ export const pluginSystemModuleTemplates: FlowNodeTemplateType[] = [ PluginInputModule, PluginOutputModule, - AiChatModule, - AssignedAnswerModule, - DatasetSearchModule, - DatasetConcatModule, - RunAppModule, - ToolModule, - StopToolNode, - ClassifyQuestionModule, - ContextExtractModule, - HttpModule468, - AiQueryExtension, - LafModule, - IfElseNode, - VariableUpdateNode, - CodeNode + ...systemNodes ]; /* all module */ export const moduleTemplatesFlat: FlowNodeTemplateType[] = [ + ...systemNodes, EmptyNode, SystemConfigNode, WorkflowStart, - AiChatModule, - DatasetSearchModule, - DatasetConcatModule, - AssignedAnswerModule, - ClassifyQuestionModule, - ContextExtractModule, - HttpModule468, - ToolModule, - StopToolNode, - AiChatModule, - RunAppModule, PluginInputModule, PluginOutputModule, - RunPluginModule, - AiQueryExtension, - LafModule, - IfElseNode, - VariableUpdateNode, - CodeNode + RunPluginModule ]; diff --git a/packages/global/core/workflow/type/index.d.ts b/packages/global/core/workflow/type/index.d.ts index 3f6e2d2ac..296a8faab 100644 --- a/packages/global/core/workflow/type/index.d.ts +++ b/packages/global/core/workflow/type/index.d.ts @@ -21,6 +21,8 @@ import { PluginTypeEnum } from '../../plugin/constants'; import { RuntimeEdgeItemType, StoreEdgeItemType } from './edge'; import { NextApiResponse } from 'next'; import { AppDetailType, AppSchema } from '../../app/type'; +import { ParentIdType } from 'common/parentFolder/type'; +import { AppTypeEnum } from 'core/app/constants'; export type FlowNodeCommonType = { flowNodeType: FlowNodeTypeEnum; // render node card @@ -37,8 +39,8 @@ export type FlowNodeCommonType = { // plugin data pluginId?: string; - pluginType?: `${PluginTypeEnum}`; - parentId?: string; + pluginType?: AppTypeEnum; + // parentId: ParentIdType; }; export type FlowNodeTemplateType = FlowNodeCommonType & { @@ -65,7 +67,6 @@ export type FlowNodeTemplateType = FlowNodeCommonType & { // action forbidDelete?: boolean; // forbid delete unique?: boolean; - nodeVersion?: string; }; export type FlowNodeItemType = FlowNodeTemplateType & { nodeId: string; diff --git a/packages/global/tsconfig.json b/packages/global/tsconfig.json index ae0de363f..c7f84d24a 100644 --- a/packages/global/tsconfig.json +++ b/packages/global/tsconfig.json @@ -1,21 +1,7 @@ { + "extends":"../../tsconfig.json", "compilerOptions": { - "target": "es2015", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, "baseUrl": "." }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.d.ts"], - "exclude": ["node_modules"] + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.d.ts"] } diff --git a/packages/service/common/string/tiktoken/index.ts b/packages/service/common/string/tiktoken/index.ts index c51f763e7..8c825c906 100644 --- a/packages/service/common/string/tiktoken/index.ts +++ b/packages/service/common/string/tiktoken/index.ts @@ -85,7 +85,14 @@ export const countGptMessagesTokens = ( functionCall }); } catch (error) { - resolve(0); + addLog.error('Count token error', error); + const total = messages.reduce((sum, item) => { + if (item.content) { + return sum + item.content.length; + } + return sum; + }, 0); + resolve(total); } }); }; diff --git a/packages/service/core/plugin/controller.ts b/packages/service/core/app/plugin/controller.ts similarity index 88% rename from packages/service/core/plugin/controller.ts rename to packages/service/core/app/plugin/controller.ts index 787cfee5c..7faef3e54 100644 --- a/packages/service/core/plugin/controller.ts +++ b/packages/service/core/app/plugin/controller.ts @@ -1,13 +1,13 @@ -import { MongoPlugin } from './schema'; import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/index.d'; -import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; +import { FlowNodeTypeEnum, defaultNodeVersion } from '@fastgpt/global/core/workflow/node/constant'; import { pluginData2FlowNodeIO } from '@fastgpt/global/core/workflow/utils'; import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants'; import type { PluginRuntimeType, PluginTemplateType } from '@fastgpt/global/core/plugin/type.d'; import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants'; -import { getHandleConfig } from '../../../global/core/workflow/template/utils'; +import { getHandleConfig } from '@fastgpt/global/core/workflow/template/utils'; import { getNanoid } from '@fastgpt/global/common/string/tools'; import { cloneDeep } from 'lodash'; +import { MongoApp } from '../schema'; /* plugin id rule: @@ -19,6 +19,7 @@ import { cloneDeep } from 'lodash'; export async function splitCombinePluginId(id: string) { const splitRes = id.split('-'); if (splitRes.length === 1) { + // app id return { source: PluginSourceEnum.personal, pluginId: id @@ -33,6 +34,7 @@ export async function splitCombinePluginId(id: string) { const getPluginTemplateById = async (id: string): Promise => { const { source, pluginId } = await splitCombinePluginId(id); + if (source === PluginSourceEnum.community) { const item = global.communityPlugins?.find((plugin) => plugin.id === pluginId); if (!item) return Promise.reject('plugin not found'); @@ -40,8 +42,9 @@ const getPluginTemplateById = async (id: string): Promise => return cloneDeep(item); } if (source === PluginSourceEnum.personal) { - const item = await MongoPlugin.findById(id).lean(); + const item = await MongoApp.findById(id).lean(); if (!item) return Promise.reject('plugin not found'); + return { id: String(item._id), teamId: String(item.teamId), @@ -54,7 +57,7 @@ const getPluginTemplateById = async (id: string): Promise => edges: item.edges, templateType: FlowNodeTemplateTypeEnum.personalPlugin, isTool: true, - nodeVersion: item?.nodeVersion || '' + version: item?.pluginData?.nodeVersion || defaultNodeVersion }; } return Promise.reject('plugin not found'); @@ -74,8 +77,7 @@ export async function getPluginPreviewNode({ id }: { id: string }): Promise new Date() - }, - modules: { - type: Array, - default: [] - } -}); - -export const MongoPluginStore: Model = - models[ModuleCollectionName] || model(ModuleCollectionName, PluginStoreSchema); -MongoPluginStore.syncIndexes(); diff --git a/packages/service/core/workflow/dispatch/plugin/run.ts b/packages/service/core/workflow/dispatch/plugin/run.ts index 377e6d210..46f49d9f8 100644 --- a/packages/service/core/workflow/dispatch/plugin/run.ts +++ b/packages/service/core/workflow/dispatch/plugin/run.ts @@ -3,8 +3,7 @@ import { dispatchWorkFlow } from '../index'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; -import { getPluginRuntimeById } from '../../../plugin/controller'; -import { authPluginCanUse } from '../../../../support/permission/auth/plugin'; +import { getPluginRuntimeById, splitCombinePluginId } from '../../../app/plugin/controller'; import { getDefaultEntryNodeIds, initWorkflowEdgeStatus, @@ -13,6 +12,9 @@ import { import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type'; import { updateToolInputValue } from '../agent/runTool/utils'; import { replaceVariable } from '@fastgpt/global/common/string/tools'; +import { authAppByTmbId } from '../../../../support/permission/app/auth'; +import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; +import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants'; type RunPluginProps = ModuleDispatchProps<{ [key: string]: any; @@ -22,9 +24,9 @@ type RunPluginResponse = DispatchNodeResultType<{}>; export const dispatchRunPlugin = async (props: RunPluginProps): Promise => { const { node: { pluginId }, + app: workflowApp, mode, teamId, - tmbId, params: data } = props; @@ -32,7 +34,16 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise item.flowType === FlowNodeTypeEnum.pluginInput); diff --git a/packages/service/support/permission/auth/plugin.ts b/packages/service/support/permission/auth/plugin.ts deleted file mode 100644 index 0e4534719..000000000 --- a/packages/service/support/permission/auth/plugin.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { AuthResponseType } from '@fastgpt/global/support/permission/type'; -import { AuthModeType } from '../type'; -import { parseHeaderCert } from '../controller'; -import { getTmbInfoByTmbId } from '../../user/team/controller'; -import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; -import { MongoPlugin } from '../../../core/plugin/schema'; -import { PluginErrEnum } from '@fastgpt/global/common/error/code/plugin'; -import { PluginItemSchema } from '@fastgpt/global/core/plugin/type'; -import { splitCombinePluginId } from '../../../core/plugin/controller'; -import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants'; - -export async function authPluginCrud({ - pluginId, - per = 'owner', - ...props -}: AuthModeType & { - pluginId: string; -}): Promise< - AuthResponseType & { - plugin: PluginItemSchema; - } -> { - const result = await parseHeaderCert(props); - const { tmbId, teamId } = result; - - const { role } = await getTmbInfoByTmbId({ tmbId }); - - const { plugin, isOwner, canWrite } = await (async () => { - const plugin = await MongoPlugin.findOne({ _id: pluginId, teamId }); - - if (!plugin) { - throw new Error(PluginErrEnum.unExist); - } - - const isOwner = String(plugin.tmbId) === tmbId || role === TeamMemberRoleEnum.owner; - const canWrite = isOwner; - - if (per === 'w' && !canWrite) { - return Promise.reject(PluginErrEnum.unAuth); - } - if (per === 'owner' && !isOwner) { - return Promise.reject(PluginErrEnum.unAuth); - } - - return { - plugin, - isOwner, - canWrite - }; - })(); - - return { - ...result, - plugin, - isOwner, - canWrite - }; -} - -export async function authPluginCanUse({ - id, - teamId, - tmbId -}: { - id: string; - teamId: string; - tmbId: string; -}) { - const { source, pluginId } = await splitCombinePluginId(id); - - if (source === PluginSourceEnum.community) { - return true; - } - - if (source === PluginSourceEnum.personal) { - const { role } = await getTmbInfoByTmbId({ tmbId }); - const plugin = await MongoPlugin.findOne({ _id: pluginId, teamId }); - if (!plugin) { - return Promise.reject(PluginErrEnum.unExist); - } - } - - return true; -} diff --git a/packages/service/support/permission/teamLimit.ts b/packages/service/support/permission/teamLimit.ts index d84e60dde..814480ccf 100644 --- a/packages/service/support/permission/teamLimit.ts +++ b/packages/service/support/permission/teamLimit.ts @@ -1,6 +1,5 @@ import { getTeamPlanStatus, getTeamStandPlan } from '../../support/wallet/sub/utils'; import { MongoApp } from '../../core/app/schema'; -import { MongoPlugin } from '../../core/plugin/schema'; import { MongoDataset } from '../../core/dataset/schema'; import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; @@ -64,26 +63,19 @@ export const checkTeamDatasetLimit = async (teamId: string) => { return Promise.reject(SystemErrEnum.communityVersionNumLimit); } }; -export const checkTeamAppLimit = async (teamId: string) => { +export const checkTeamAppLimit = async (teamId: string, amount = 1) => { const [{ standardConstants }, appCount] = await Promise.all([ getTeamStandPlan({ teamId }), - MongoApp.count({ teamId, type: { $in: [AppTypeEnum.advanced, AppTypeEnum.simple] } }) + MongoApp.count({ + teamId, + type: { $in: [AppTypeEnum.simple, AppTypeEnum.workflow, AppTypeEnum.plugin] } + }) ]); - if (standardConstants && appCount >= standardConstants.maxAppAmount) { + if (standardConstants && appCount + amount >= standardConstants.maxAppAmount) { return Promise.reject(TeamErrEnum.appAmountNotEnough); } }; -export const checkTeamPluginLimit = async (teamId: string) => { - const [{ standardConstants }, pluginCount] = await Promise.all([ - getTeamStandPlan({ teamId }), - MongoPlugin.count({ teamId }) - ]); - - if (standardConstants && pluginCount >= standardConstants.maxAppAmount) { - return Promise.reject(TeamErrEnum.pluginAmountNotEnough); - } -}; export const checkTeamReRankPermission = async (teamId: string) => { const { standardConstants } = await getTeamStandPlan({ diff --git a/packages/service/tsconfig.json b/packages/service/tsconfig.json index 13c4daa3b..272c5509d 100644 --- a/packages/service/tsconfig.json +++ b/packages/service/tsconfig.json @@ -1,21 +1,7 @@ { + "extends":"../../tsconfig.json", "compilerOptions": { - "target": "es2015", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, "baseUrl": "." }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.d.ts", "../**/*.d.ts"], - "exclude": ["node_modules"] + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.d.ts", "../**/*.d.ts"] } diff --git a/packages/web/components/common/Icon/constants.ts b/packages/web/components/common/Icon/constants.ts index 21146d65f..1f310434f 100644 --- a/packages/web/components/common/Icon/constants.ts +++ b/packages/web/components/common/Icon/constants.ts @@ -32,6 +32,7 @@ export const iconPaths = { 'common/inviteLight': () => import('./icons/common/inviteLight.svg'), 'common/language/en': () => import('./icons/common/language/en.svg'), 'common/language/zh': () => import('./icons/common/language/zh.svg'), + 'common/leftArrowLight': () => import('./icons/common/leftArrowLight.svg'), 'common/linkBlue': () => import('./icons/common/linkBlue.svg'), 'common/loading': () => import('./icons/common/loading.svg'), 'common/navbar/pluginFill': () => import('./icons/common/navbar/pluginFill.svg'), @@ -83,8 +84,13 @@ export const iconPaths = { 'core/app/simpleMode/whisper': () => import('./icons/core/app/simpleMode/whisper.svg'), 'core/app/toolCall': () => import('./icons/core/app/toolCall.svg'), 'core/app/ttsFill': () => import('./icons/core/app/ttsFill.svg'), + 'core/app/type/httpPlugin': () => import('./icons/core/app/type/httpPlugin.svg'), + 'core/app/type/httpPluginFill': () => import('./icons/core/app/type/httpPluginFill.svg'), + 'core/app/type/plugin': () => import('./icons/core/app/type/plugin.svg'), + 'core/app/type/pluginFill': () => import('./icons/core/app/type/pluginFill.svg'), 'core/app/type/simple': () => import('./icons/core/app/type/simple.svg'), 'core/app/type/workflow': () => import('./icons/core/app/type/workflow.svg'), + 'core/app/type/workflowFill': () => import('./icons/core/app/type/workflowFill.svg'), 'core/app/variable/external': () => import('./icons/core/app/variable/external.svg'), 'core/app/variable/input': () => import('./icons/core/app/variable/input.svg'), 'core/app/variable/select': () => import('./icons/core/app/variable/select.svg'), diff --git a/packages/web/components/common/Icon/icons/common/leftArrowLight.svg b/packages/web/components/common/Icon/icons/common/leftArrowLight.svg new file mode 100644 index 000000000..376eb1258 --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/leftArrowLight.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/core/app/type/httpPlugin.svg b/packages/web/components/common/Icon/icons/core/app/type/httpPlugin.svg new file mode 100644 index 000000000..e7497f1e1 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/type/httpPlugin.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/core/app/type/httpPluginFill.svg b/packages/web/components/common/Icon/icons/core/app/type/httpPluginFill.svg new file mode 100644 index 000000000..688aafb8c --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/type/httpPluginFill.svg @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/core/app/type/plugin.svg b/packages/web/components/common/Icon/icons/core/app/type/plugin.svg new file mode 100644 index 000000000..b45671589 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/type/plugin.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/core/app/type/pluginFill.svg b/packages/web/components/common/Icon/icons/core/app/type/pluginFill.svg new file mode 100644 index 000000000..661e96062 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/type/pluginFill.svg @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/core/app/type/workflowFill.svg b/packages/web/components/common/Icon/icons/core/app/type/workflowFill.svg new file mode 100644 index 000000000..202171cd4 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/type/workflowFill.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/support/permission/privateLight.svg b/packages/web/components/common/Icon/icons/support/permission/privateLight.svg index 12be6e9de..567909545 100644 --- a/packages/web/components/common/Icon/icons/support/permission/privateLight.svg +++ b/packages/web/components/common/Icon/icons/support/permission/privateLight.svg @@ -1,4 +1,4 @@ - + + d="M4.52599 1.16675L9.47417 1.16675C9.94374 1.16674 10.3313 1.16674 10.647 1.19253C10.9749 1.21932 11.2763 1.27681 11.5594 1.42107C11.9984 1.64477 12.3554 2.00173 12.5791 2.44077C12.7233 2.72388 12.7808 3.02529 12.8076 3.35318C12.8334 3.66888 12.8334 4.05642 12.8334 4.52598V9.47419C12.8334 9.94375 12.8334 10.3313 12.8076 10.647C12.7808 10.9749 12.7233 11.2763 12.5791 11.5594C12.3554 11.9984 11.9984 12.3554 11.5594 12.5791C11.2763 12.7233 10.9749 12.7808 10.647 12.8076C10.3313 12.8334 9.94375 12.8334 9.47419 12.8334H4.52603C4.05645 12.8334 3.66889 12.8334 3.35318 12.8076C3.02529 12.7808 2.72388 12.7233 2.44077 12.5791C2.00173 12.3554 1.64477 11.9984 1.42107 11.5594C1.27681 11.2763 1.21932 10.9749 1.19253 10.647C1.16673 10.3313 1.16674 9.94374 1.16675 9.47417L1.16675 4.52599C1.16674 4.05642 1.16673 3.66888 1.19253 3.35318C1.21932 3.02529 1.27681 2.72388 1.42107 2.44077C1.64477 2.00173 2.00173 1.64477 2.44077 1.42107C2.72388 1.27681 3.02529 1.21932 3.35318 1.19253C3.66888 1.16673 4.05642 1.16674 4.52599 1.16675ZM3.44819 2.35532C3.19245 2.37622 3.06166 2.41409 2.97043 2.46057C2.7509 2.57243 2.57243 2.7509 2.46057 2.97043C2.41409 3.06166 2.37622 3.19244 2.35532 3.44819C2.33387 3.71074 2.33342 4.05041 2.33342 4.55008V9.45008C2.33342 9.94975 2.33387 10.2894 2.35532 10.552C2.37622 10.8077 2.41409 10.9385 2.46057 11.0297C2.57243 11.2493 2.7509 11.4277 2.97043 11.5396C3.06166 11.5861 3.19245 11.6239 3.44819 11.6448C3.71074 11.6663 4.05041 11.6667 4.55008 11.6667H9.45008C9.94975 11.6667 10.2894 11.6663 10.552 11.6448C10.8077 11.6239 10.9385 11.5861 11.0297 11.5396C11.2493 11.4277 11.4277 11.2493 11.5396 11.0297C11.5861 10.9385 11.6239 10.8077 11.6448 10.552C11.6663 10.2894 11.6667 9.94975 11.6667 9.45008V4.55008C11.6667 4.05041 11.6663 3.71074 11.6448 3.44819C11.6239 3.19245 11.5861 3.06166 11.5396 2.97043C11.4277 2.7509 11.2493 2.57243 11.0297 2.46058C10.9385 2.41409 10.8077 2.37622 10.552 2.35532C10.2894 2.33387 9.94975 2.33342 9.45008 2.33342L4.55008 2.33342C4.05041 2.33342 3.71074 2.33387 3.44819 2.35532ZM4.66675 5.83341C4.66675 4.54475 5.71142 3.50008 7.00008 3.50008C8.28875 3.50008 9.33341 4.54475 9.33341 5.83341C9.33341 6.50809 9.04631 7.11661 8.58932 7.54186C8.58515 7.54574 8.58108 7.54953 8.57712 7.55321L9.09873 9.11804C9.1005 9.12334 9.10229 9.12869 9.10408 9.13408C9.1339 9.22344 9.16732 9.3236 9.18821 9.41196C9.21103 9.50852 9.23803 9.6631 9.19407 9.83818C9.13965 10.0549 9.00436 10.2426 8.81595 10.3628C8.66376 10.4599 8.50857 10.4831 8.40975 10.492C8.31931 10.5001 8.21371 10.5001 8.1195 10.5001C8.11383 10.5001 8.1082 10.5001 8.10262 10.5001H5.89755C5.89196 10.5001 5.88633 10.5001 5.88066 10.5001C5.78646 10.5001 5.68085 10.5001 5.59042 10.492C5.4916 10.4831 5.33641 10.4599 5.18422 10.3628C4.99581 10.2426 4.86052 10.0549 4.80609 9.83818C4.76213 9.6631 4.78914 9.50852 4.81196 9.41196C4.83284 9.3236 4.86627 9.22343 4.89608 9.13407C4.89788 9.12869 4.89966 9.12334 4.90143 9.11804L5.42304 7.55321C5.41908 7.54953 5.41501 7.54574 5.41084 7.54186C4.95385 7.11661 4.66675 6.50809 4.66675 5.83341ZM7.00008 4.66675C6.35575 4.66675 5.83342 5.18908 5.83342 5.83341C5.83342 6.17079 5.97586 6.47399 6.2056 6.68777L6.21324 6.69488C6.26875 6.74653 6.32357 6.79753 6.36672 6.84136C6.40478 6.88003 6.48081 6.95858 6.53581 7.06465C6.5588 7.10896 6.5881 7.17165 6.6083 7.25081C6.62849 7.32998 6.63281 7.39904 6.63386 7.44895C6.63615 7.55691 6.61458 7.64803 6.59888 7.70549C6.58437 7.75862 6.56433 7.8187 6.54659 7.8719L6.05941 9.33341H7.94075L7.45356 7.87185C7.43582 7.81866 7.41579 7.7586 7.40128 7.70549C7.38558 7.64803 7.36401 7.55691 7.3663 7.44895C7.36736 7.39904 7.37167 7.32998 7.39187 7.25081C7.41207 7.17165 7.44137 7.10896 7.46435 7.06465C7.51935 6.95859 7.59538 6.88004 7.63345 6.84136C7.6766 6.79752 7.73144 6.74651 7.78696 6.69484L7.79456 6.68777C8.0243 6.47399 8.16675 6.17079 8.16675 5.83341C8.16675 5.18908 7.64441 4.66675 7.00008 4.66675Z" /> \ No newline at end of file diff --git a/packages/web/components/common/Icon/index.tsx b/packages/web/components/common/Icon/index.tsx index aec245e7e..8ab758947 100644 --- a/packages/web/components/common/Icon/index.tsx +++ b/packages/web/components/common/Icon/index.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import type { IconProps } from '@chakra-ui/react'; -import { Icon } from '@chakra-ui/react'; +import { Box, Icon } from '@chakra-ui/react'; import { iconPaths } from './constants'; import type { IconNameType } from './type.d'; @@ -25,7 +25,9 @@ const MyIcon = ({ name, w = 'auto', h = 'auto', ...props }: { name: IconNameType fill={'currentcolor'} {...props} /> - ) : null; + ) : ( + + ); }; export default React.memo(MyIcon); diff --git a/packages/web/components/common/MyMenu/index.tsx b/packages/web/components/common/MyMenu/index.tsx index 50b3f70da..969cbf222 100644 --- a/packages/web/components/common/MyMenu/index.tsx +++ b/packages/web/components/common/MyMenu/index.tsx @@ -126,12 +126,12 @@ const MyMenu = ({ {Button} {menuList.map((item, i) => { return ( diff --git a/packages/web/components/common/MyModal/index.tsx b/packages/web/components/common/MyModal/index.tsx index 71244a003..73b23a8c1 100644 --- a/packages/web/components/common/MyModal/index.tsx +++ b/packages/web/components/common/MyModal/index.tsx @@ -59,12 +59,12 @@ const MyModal = ({ {iconSrc && ( <> diff --git a/packages/web/components/common/MyPopover/PopoverConfirm.tsx b/packages/web/components/common/MyPopover/PopoverConfirm.tsx new file mode 100644 index 000000000..a94d52c00 --- /dev/null +++ b/packages/web/components/common/MyPopover/PopoverConfirm.tsx @@ -0,0 +1,98 @@ +import React, { useMemo } from 'react'; +import MyPopover from './index'; +import { useTranslation } from 'next-i18next'; +import MyIcon from '../Icon'; +import { useRequest2 } from '../../../hooks/useRequest'; +import { + Popover, + PopoverTrigger, + PopoverContent, + useDisclosure, + PlacementWithLogical, + HStack, + Box, + Button, + PopoverArrow +} from '@chakra-ui/react'; + +const PopoverConfirm = ({ + content, + showCancel, + type, + Trigger, + placement = 'bottom-start', + offset, + onConfirm +}: { + content: string; + showCancel?: boolean; + type?: 'info' | 'delete'; + Trigger: React.ReactNode; + placement?: PlacementWithLogical; + offset?: [number, number]; + onConfirm: () => any; +}) => { + const { t } = useTranslation(); + + const map = useMemo(() => { + const map = { + info: { + variant: 'primary', + icon: 'common/confirm/commonTip' + }, + delete: { + variant: 'dangerFill', + icon: 'common/confirm/deleteTip' + } + }; + if (type && map[type]) return map[type]; + return map.info; + }, [type, t]); + + const firstFieldRef = React.useRef(null); + const { onOpen, onClose, isOpen } = useDisclosure(); + + const { runAsync: onclickConfirm, loading } = useRequest2(onConfirm, { + onSuccess: onClose + }); + + return ( + + {Trigger} + + + + + + {content} + + + {showCancel && ( + + )} + + + + + ); +}; + +export default PopoverConfirm; diff --git a/packages/web/components/common/MyPopover/index.tsx b/packages/web/components/common/MyPopover/index.tsx index 60cc53144..fb46de2d6 100644 --- a/packages/web/components/common/MyPopover/index.tsx +++ b/packages/web/components/common/MyPopover/index.tsx @@ -4,7 +4,8 @@ import { PopoverTrigger, PopoverContent, useDisclosure, - PlacementWithLogical + PlacementWithLogical, + PopoverArrow } from '@chakra-ui/react'; const MyPopover = ({ @@ -40,7 +41,10 @@ const MyPopover = ({ lazyBehavior="keepMounted" > {Trigger} - {children({ onClose })} + + + {children({ onClose })} + ); }; diff --git a/packages/web/components/common/Tabs/RowTabs.tsx b/packages/web/components/common/Tabs/RowTabs.tsx index 846387000..3e7bd6125 100644 --- a/packages/web/components/common/Tabs/RowTabs.tsx +++ b/packages/web/components/common/Tabs/RowTabs.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Flex, Box, BoxProps } from '@chakra-ui/react'; import MyIcon from '../Icon'; -type Props = BoxProps & { +type Props = Omit & { list: { icon?: string; label: string | React.ReactNode; diff --git a/packages/web/components/common/Tag/index.tsx b/packages/web/components/common/Tag/index.tsx index 3b0e035a1..c5ce8ce4b 100644 --- a/packages/web/components/common/Tag/index.tsx +++ b/packages/web/components/common/Tag/index.tsx @@ -1,5 +1,5 @@ import React, { useMemo } from 'react'; -import { Flex, type FlexProps } from '@chakra-ui/react'; +import { Box, Flex, type FlexProps } from '@chakra-ui/react'; type ColorSchemaType = 'white' | 'blue' | 'green' | 'red' | 'yellow' | 'gray' | 'purple' | 'adora'; @@ -7,6 +7,7 @@ export type TagProps = FlexProps & { children: React.ReactNode | React.ReactNode[]; colorSchema?: ColorSchemaType; type?: 'fill' | 'borderFill' | 'borderSolid'; + showDot?: boolean; }; const colorMap: Record< @@ -59,7 +60,7 @@ const colorMap: Record< } }; -const MyTag = ({ children, colorSchema = 'blue', type = 'fill', ...props }: TagProps) => { +const MyTag = ({ children, colorSchema = 'blue', type = 'fill', showDot, ...props }: TagProps) => { const theme = useMemo(() => { return colorMap[colorSchema]; }, [colorSchema]); @@ -75,10 +76,11 @@ const MyTag = ({ children, colorSchema = 'blue', type = 'fill', ...props }: TagP whiteSpace={'nowrap'} borderWidth={'1px'} {...theme} - {...props} borderColor={type !== 'fill' ? theme.borderColor : 'transparent'} bg={type !== 'borderSolid' ? theme.bg : 'transparent'} + {...props} > + {showDot && } {children} ); diff --git a/packages/web/hooks/useConfirm.tsx b/packages/web/hooks/useConfirm.tsx index 9a0d69830..9b2fc44ff 100644 --- a/packages/web/hooks/useConfirm.tsx +++ b/packages/web/hooks/useConfirm.tsx @@ -17,12 +17,12 @@ export const useConfirm = (props?: { const map = { common: { title: t('common.confirm.Common Tip'), - bg: undefined, + variant: 'primary', iconSrc: 'common/confirm/commonTip' }, delete: { title: t('common.Delete Warning'), - bg: 'red.600', + variant: 'dangerFill', iconSrc: 'common/confirm/deleteTip' } }; @@ -108,7 +108,7 @@ export const useConfirm = (props?: { - + @@ -225,7 +232,6 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => { ))}
- {!!editData && ( @@ -279,7 +285,7 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => { - + ); }; diff --git a/projects/app/src/components/support/permission/ConfigPerModal/index.tsx b/projects/app/src/components/support/permission/ConfigPerModal/index.tsx index 6e947294b..05b09af37 100644 --- a/projects/app/src/components/support/permission/ConfigPerModal/index.tsx +++ b/projects/app/src/components/support/permission/ConfigPerModal/index.tsx @@ -40,7 +40,7 @@ const ConfigPerModal = ({ - {name} + {name} {t('permission.Default permission')} diff --git a/projects/app/src/components/support/user/team/TeamManageModal/TeamCard.tsx b/projects/app/src/components/support/user/team/TeamManageModal/TeamCard.tsx index 681062dea..cfa7f441d 100644 --- a/projects/app/src/components/support/user/team/TeamManageModal/TeamCard.tsx +++ b/projects/app/src/components/support/user/team/TeamManageModal/TeamCard.tsx @@ -3,7 +3,7 @@ import MyIcon from '@fastgpt/web/components/common/Icon'; import { useUserStore } from '@/web/support/user/useUserStore'; import { useContextSelector } from 'use-context-selector'; import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; -import RowTabs from '../../../../common/Rowtabs'; +import RowTabs from '@fastgpt/web/components/common/Tabs/RowTabs'; import { useMemo, useState } from 'react'; import { useTranslation } from 'next-i18next'; import { useSystemStore } from '@/web/common/system/useSystemStore'; diff --git a/projects/app/src/global/core/app/api.d.ts b/projects/app/src/global/core/app/api.d.ts index bea4c3935..a8b3b56ef 100644 --- a/projects/app/src/global/core/app/api.d.ts +++ b/projects/app/src/global/core/app/api.d.ts @@ -11,7 +11,6 @@ export type AppUpdateParams = { nodes?: AppSchema['modules']; edges?: AppSchema['edges']; chatConfig?: AppSchema['chatConfig']; - permission?: AppSchema['permission']; teamTags?: AppSchema['teamTags']; defaultPermission?: AppSchema['defaultPermission']; }; @@ -28,4 +27,5 @@ export type PostRevertAppProps = { // edit workflow editNodes: AppSchema['modules']; editEdges: AppSchema['edges']; + editChatConfig: AppSchema['chatConfig']; }; diff --git a/projects/app/src/global/core/workflow/api.d.ts b/projects/app/src/global/core/workflow/api.d.ts index b118745e3..cb1f38e23 100644 --- a/projects/app/src/global/core/workflow/api.d.ts +++ b/projects/app/src/global/core/workflow/api.d.ts @@ -6,8 +6,7 @@ export type PostWorkflowDebugProps = { nodes: RuntimeNodeItemType[]; edges: RuntimeEdgeItemType[]; variables: Record; - appId?: string; - pluginId?: string; + appId: string; }; export type PostWorkflowDebugResponse = { diff --git a/projects/app/src/pages/_app.tsx b/projects/app/src/pages/_app.tsx index c8b133c8f..5cccfa9ae 100644 --- a/projects/app/src/pages/_app.tsx +++ b/projects/app/src/pages/_app.tsx @@ -1,6 +1,6 @@ import type { AppProps } from 'next/app'; import Script from 'next/script'; -import Head from 'next/head'; + import Layout from '@/components/Layout'; import { appWithTranslation } from 'next-i18next'; diff --git a/projects/app/src/pages/account/components/ApiKeyTable.tsx b/projects/app/src/pages/account/components/ApiKeyTable.tsx index 08226b699..c86c0dff7 100644 --- a/projects/app/src/pages/account/components/ApiKeyTable.tsx +++ b/projects/app/src/pages/account/components/ApiKeyTable.tsx @@ -2,10 +2,15 @@ import React from 'react'; import { useTranslation } from 'next-i18next'; import ApiKeyTable from '@/components/support/apikey/Table'; import { useI18n } from '@/web/context/I18n'; +import { Box } from '@chakra-ui/react'; const ApiKey = () => { const { publishT } = useI18n(); - return ; + return ( + + + + ); }; export default ApiKey; diff --git a/projects/app/src/pages/api/admin/initv485.ts b/projects/app/src/pages/api/admin/initv485.ts new file mode 100644 index 000000000..fd72d830a --- /dev/null +++ b/projects/app/src/pages/api/admin/initv485.ts @@ -0,0 +1,163 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; +import { MongoPlugin } from '@fastgpt/service/core/plugin/schema'; +import { MongoApp } from '@fastgpt/service/core/app/schema'; +import { NextAPI } from '@/service/middleware/entry'; +import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants'; +import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun'; +import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; + +/* + 1. 先读取 HTTP plugin 内容,并找到所有的子plugin,然后事务批量创建,最后修改 inited + 2. 读取剩下未 inited 的plugin,逐一创建 +*/ + +let success = 0; +async function handler(req: NextApiRequest, res: NextApiResponse) { + await authCert({ req, authRoot: true }); + + const total = await MongoPlugin.countDocuments({ + inited: { $ne: true } + }); + + console.log('Total plugin', total); + + await initHttp(); + await initPlugin(); +} + +async function initHttp(): Promise { + /* 读取http插件和他的children */ + const plugin = await MongoPlugin.findOne({ + type: PluginTypeEnum.folder, + inited: { $ne: true } + }).lean(); + if (!plugin) return; + + const children = await MongoPlugin.find({ + teamId: plugin.teamId, + parentId: plugin._id, + inited: { $ne: true } + }).lean(); + + await mongoSessionRun(async (session) => { + /* 创建HTTP插件作为目录 */ + const [{ _id }] = await MongoApp.create( + [ + { + teamId: plugin.teamId, + tmbId: plugin.tmbId, + type: AppTypeEnum.httpPlugin, + name: plugin.name, + avatar: plugin.avatar, + intro: plugin.intro, + metadata: plugin.metadata, + version: 'v2', + pluginData: { + apiSchemaStr: plugin.metadata?.apiSchemaStr, + customHeaders: plugin.metadata?.customHeaders + } + } + ], + { session } + ); + + /* 批量创建子插件 */ + for await (const item of children) { + await MongoApp.create( + [ + { + parentId: _id, + teamId: item.teamId, + tmbId: item.tmbId, + type: AppTypeEnum.plugin, + name: item.name, + avatar: item.avatar, + intro: item.intro, + version: 'v2', + modules: item.modules, + edges: item.edges, + pluginData: { + nodeVersion: item.nodeVersion, + pluginUniId: plugin.metadata?.pluginUid + } + } + ], + { session } + ); + } + + /* 更新插件信息 */ + await MongoPlugin.findOneAndUpdate( + { + _id: plugin._id + }, + { + $set: { inited: true } + }, + { session } + ); + await MongoPlugin.updateMany( + { + teamId: plugin.teamId, + parentId: plugin._id + }, + { + $set: { inited: true } + }, + { session } + ); + + success += children.length + 1; + console.log(success); + }); + + return initHttp(); +} + +async function initPlugin(): Promise { + const plugin = await MongoPlugin.findOne({ + type: PluginTypeEnum.custom, + inited: { $ne: true } + }).lean(); + if (!plugin) return; + + await mongoSessionRun(async (session) => { + await MongoApp.create( + [ + { + teamId: plugin.teamId, + tmbId: plugin.tmbId, + type: AppTypeEnum.plugin, + name: plugin.name, + avatar: plugin.avatar, + intro: plugin.intro, + version: 'v2', + modules: plugin.modules, + edges: plugin.edges, + pluginData: { + nodeVersion: plugin.nodeVersion + } + } + ], + { session } + ); + + await MongoPlugin.findOneAndUpdate( + { + _id: plugin._id + }, + { + $set: { inited: true } + }, + { session } + ); + + success++; + console.log(success); + }); + + return initPlugin(); +} + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/app/create.ts b/projects/app/src/pages/api/core/app/create.ts index 056024132..63aaf6aa4 100644 --- a/projects/app/src/pages/api/core/app/create.ts +++ b/projects/app/src/pages/api/core/app/create.ts @@ -12,6 +12,8 @@ import type { AppSchema } from '@fastgpt/global/core/app/type'; import { ApiRequestProps } from '@fastgpt/service/type/next'; import type { ParentIdType } from '@fastgpt/global/common/parentFolder/type'; import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils'; +import { defaultNodeVersion } from '@fastgpt/global/core/workflow/node/constant'; +import { ClientSession } from '@fastgpt/service/common/mongo'; export type CreateAppBody = { parentId?: ParentIdType; @@ -35,37 +37,16 @@ async function handler(req: ApiRequestProps, res: NextApiResponse // 上限校验 await checkTeamAppLimit(teamId); - // 创建模型 - const appId = await mongoSessionRun(async (session) => { - const [{ _id: appId }] = await MongoApp.create( - [ - { - ...parseParentIdInMongo(parentId), - avatar, - name, - teamId, - tmbId, - modules, - edges, - type, - version: 'v2' - } - ], - { session } - ); - - await MongoAppVersion.create( - [ - { - appId, - nodes: modules, - edges - } - ], - { session } - ); - - return appId; + // 创建app + const appId = await onCreateApp({ + parentId, + name, + avatar, + type, + modules, + edges, + teamId, + tmbId }); jsonRes(res, { @@ -74,3 +55,72 @@ async function handler(req: ApiRequestProps, res: NextApiResponse } export default NextAPI(handler); + +export const onCreateApp = async ({ + parentId, + name, + intro, + avatar, + type, + modules, + edges, + teamId, + tmbId, + pluginData, + session +}: { + parentId?: ParentIdType; + name?: string; + avatar?: string; + type?: AppTypeEnum; + modules?: AppSchema['modules']; + edges?: AppSchema['edges']; + intro?: string; + teamId: string; + tmbId: string; + pluginData?: AppSchema['pluginData']; + session?: ClientSession; +}) => { + const create = async (session: ClientSession) => { + const [{ _id: appId }] = await MongoApp.create( + [ + { + ...parseParentIdInMongo(parentId), + avatar, + name, + intro, + teamId, + tmbId, + modules, + edges, + type, + version: 'v2', + pluginData, + ...(type === AppTypeEnum.plugin && { 'pluginData.nodeVersion': defaultNodeVersion }) + } + ], + { session } + ); + + if (type !== AppTypeEnum.folder && type !== AppTypeEnum.httpPlugin) { + await MongoAppVersion.create( + [ + { + appId, + nodes: modules, + edges + } + ], + { session } + ); + } + + return appId; + }; + + if (session) { + return create(session); + } else { + return await mongoSessionRun(create); + } +}; diff --git a/projects/app/src/pages/api/core/app/del.ts b/projects/app/src/pages/api/core/app/del.ts index 794932155..66d2db38d 100644 --- a/projects/app/src/pages/api/core/app/del.ts +++ b/projects/app/src/pages/api/core/app/del.ts @@ -14,6 +14,7 @@ import { } from '@fastgpt/global/support/permission/constant'; import { findAppAndAllChildren } from '@fastgpt/service/core/app/controller'; import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema'; +import { ClientSession } from '@fastgpt/service/common/mongo'; async function handler(req: NextApiRequest, res: NextApiResponse) { const { appId } = req.query as { appId: string }; @@ -25,13 +26,30 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { // Auth owner (folder owner, can delete all apps in the folder) const { teamId } = await authApp({ req, authToken: true, appId, per: OwnerPermissionVal }); + await onDelOneApp({ + teamId, + appId + }); +} + +export default NextAPI(handler); + +export const onDelOneApp = async ({ + teamId, + appId, + session +}: { + teamId: string; + appId: string; + session?: ClientSession; +}) => { const apps = await findAppAndAllChildren({ teamId, appId, fields: '_id' }); - await mongoSessionRun(async (session) => { + const del = async (session: ClientSession) => { for await (const app of apps) { const appId = app._id; // Chats @@ -83,7 +101,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { { session } ); } - }); -} + }; -export default NextAPI(handler); + if (session) { + return del(session); + } + + return mongoSessionRun(del); +}; diff --git a/projects/app/src/pages/api/core/app/httpPlugin/create.ts b/projects/app/src/pages/api/core/app/httpPlugin/create.ts new file mode 100644 index 000000000..471bb0998 --- /dev/null +++ b/projects/app/src/pages/api/core/app/httpPlugin/create.ts @@ -0,0 +1,68 @@ +import { authUserPer } from '@fastgpt/service/support/permission/user/auth'; +import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun'; +import { httpApiSchema2Plugins } from '@fastgpt/global/core/app/httpPlugin/utils'; +import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'; + +import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next'; +import { NextAPI } from '@/service/middleware/entry'; +import { onCreateApp, type CreateAppBody } from '../create'; +import { AppSchema } from '@fastgpt/global/core/app/type'; +import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; + +export type createHttpPluginQuery = {}; + +export type createHttpPluginBody = Omit & { + intro?: string; + pluginData: AppSchema['pluginData']; +}; + +export type createHttpPluginResponse = {}; + +async function handler( + req: ApiRequestProps, + res: ApiResponseType +): Promise { + const { parentId, name, intro, avatar, pluginData } = req.body; + + if (!name || !pluginData) { + return Promise.reject('缺少参数'); + } + + const { teamId, tmbId } = await authUserPer({ req, authToken: true, per: WritePermissionVal }); + + await mongoSessionRun(async (session) => { + // create http plugin folder + const httpPluginIid = await onCreateApp({ + parentId, + name, + avatar, + intro, + teamId, + tmbId, + type: AppTypeEnum.httpPlugin, + pluginData, + session + }); + + // compute children plugins + const childrenPlugins = await httpApiSchema2Plugins({ + parentId: httpPluginIid, + apiSchemaStr: pluginData.apiSchemaStr, + customHeader: pluginData.customHeaders + }); + + // create children plugins + for await (const item of childrenPlugins) { + await onCreateApp({ + ...item, + teamId, + tmbId, + session + }); + } + }); + + return {}; +} + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/plugin/httpPlugin/getApiSchemaByUrl.ts b/projects/app/src/pages/api/core/app/httpPlugin/getApiSchemaByUrl.ts similarity index 100% rename from projects/app/src/pages/api/core/plugin/httpPlugin/getApiSchemaByUrl.ts rename to projects/app/src/pages/api/core/app/httpPlugin/getApiSchemaByUrl.ts diff --git a/projects/app/src/pages/api/core/app/httpPlugin/update.ts b/projects/app/src/pages/api/core/app/httpPlugin/update.ts new file mode 100644 index 000000000..0edc9e8f2 --- /dev/null +++ b/projects/app/src/pages/api/core/app/httpPlugin/update.ts @@ -0,0 +1,127 @@ +import type { NextApiResponse } from 'next'; +import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun'; +import { ClientSession } from '@fastgpt/service/common/mongo'; +import { httpApiSchema2Plugins } from '@fastgpt/global/core/app/httpPlugin/utils'; +import { NextAPI } from '@/service/middleware/entry'; +import { AppSchema } from '@fastgpt/global/core/app/type'; +import { ApiRequestProps } from '@fastgpt/service/type/next'; +import { authApp } from '@fastgpt/service/support/permission/app/auth'; +import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant'; +import { MongoApp } from '@fastgpt/service/core/app/schema'; +import { isEqual } from 'lodash'; +import { onCreateApp } from '../create'; +import { onDelOneApp } from '../del'; + +export type UpdateHttpPluginBody = { + appId: string; + name: string; + avatar?: string; + intro?: string; + pluginData: AppSchema['pluginData']; +}; + +async function handler(req: ApiRequestProps, res: NextApiResponse) { + const { appId, name, avatar, intro, pluginData } = req.body; + + const { app } = await authApp({ req, authToken: true, appId, per: ManagePermissionVal }); + + const storeData = { + apiSchemaStr: app.pluginData?.apiSchemaStr, + customHeaders: app.pluginData?.customHeaders + }; + const updateData = { + apiSchemaStr: pluginData?.apiSchemaStr, + customHeaders: pluginData?.customHeaders + }; + + await mongoSessionRun(async (session) => { + // update children + if (!isEqual(storeData, updateData)) { + await updateHttpChildrenPlugin({ + teamId: app.teamId, + tmbId: app.tmbId, + parentId: app._id, + pluginData, + session + }); + } + + await MongoApp.findByIdAndUpdate( + appId, + { + name, + avatar, + intro, + pluginData + }, + { session } + ); + }); +} + +export default NextAPI(handler); + +const updateHttpChildrenPlugin = async ({ + teamId, + tmbId, + parentId, + pluginData, + session +}: { + teamId: string; + tmbId: string; + parentId: string; + pluginData?: AppSchema['pluginData']; + session: ClientSession; +}) => { + if (!pluginData?.apiSchemaStr) return; + + const dbPlugins = await MongoApp.find({ + parentId, + teamId + }).select({ + pluginData: 1 + }); + + const schemaPlugins = await httpApiSchema2Plugins({ + parentId, + apiSchemaStr: pluginData?.apiSchemaStr, + customHeader: pluginData?.customHeaders + }); + + // 数据库中存在,schema不存在,删除 + for await (const plugin of dbPlugins) { + if (!schemaPlugins.find((p) => p.name === plugin.pluginData?.pluginUniId)) { + await onDelOneApp({ + teamId, + appId: plugin._id, + session + }); + } + } + // 数据库中不存在,schema存在,新增 + for await (const plugin of schemaPlugins) { + if (!dbPlugins.find((p) => p.pluginData?.pluginUniId === plugin.name)) { + await onCreateApp({ + ...plugin, + teamId, + tmbId, + session + }); + } + } + // 数据库中存在,schema存在,更新 + for await (const plugin of schemaPlugins) { + const dbPlugin = dbPlugins.find((p) => plugin.name === p.pluginData?.pluginUniId); + if (dbPlugin) { + await MongoApp.findByIdAndUpdate( + dbPlugin._id, + { + ...plugin, + version: 'v2' + }, + { session } + ); + } + } +}; diff --git a/projects/app/src/pages/api/core/app/list.ts b/projects/app/src/pages/api/core/app/list.ts index 2e198f73c..10877f12c 100644 --- a/projects/app/src/pages/api/core/app/list.ts +++ b/projects/app/src/pages/api/core/app/list.ts @@ -17,8 +17,9 @@ import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/ export type ListAppBody = { parentId?: ParentIdType; - type?: AppTypeEnum; + type?: AppTypeEnum | AppTypeEnum[]; getRecentlyChat?: boolean; + searchKey?: string; }; async function handler( @@ -36,26 +37,43 @@ async function handler( per: ReadPermissionVal }); - const { parentId, type, getRecentlyChat } = req.body; + const { parentId, type, getRecentlyChat, searchKey } = req.body; - const findAppsQuery = getRecentlyChat - ? { + const findAppsQuery = (() => { + const searchMatch = searchKey + ? { + $or: [ + { name: { $regex: searchKey, $options: 'i' } }, + { intro: { $regex: searchKey, $options: 'i' } } + ] + } + : {}; + + if (getRecentlyChat) { + return { // get all chat app teamId, - type: { $in: [AppTypeEnum.advanced, AppTypeEnum.simple] } - } - : { - teamId, - ...(type && { type }), - ...parseParentIdInMongo(parentId) + type: { $in: [AppTypeEnum.workflow, AppTypeEnum.simple] }, + ...searchMatch }; + } + + return { + teamId, + ...(type && Array.isArray(type) && { type: { $in: type } }), + ...(type && { type }), + ...parseParentIdInMongo(parentId), + ...searchMatch + }; + })(); /* temp: get all apps and per */ const [myApps, rpList] = await Promise.all([ - MongoApp.find(findAppsQuery, '_id avatar type name intro tmbId defaultPermission') + MongoApp.find(findAppsQuery, '_id avatar type name intro tmbId pluginData defaultPermission') .sort({ updateTime: -1 }) + .limit(searchKey ? 20 : 1000) .lean(), MongoResourcePermission.find({ resourceType: PerResourceTypeEnum.app, @@ -88,7 +106,8 @@ async function handler( name: app.name, intro: app.intro, permission: app.permission, - defaultPermission: app.defaultPermission || AppDefaultPermissionVal + defaultPermission: app.defaultPermission || AppDefaultPermissionVal, + pluginData: app.pluginData })); } diff --git a/projects/app/src/pages/api/core/app/plugin/getPreviewNode.ts b/projects/app/src/pages/api/core/app/plugin/getPreviewNode.ts new file mode 100644 index 000000000..321cd3a10 --- /dev/null +++ b/projects/app/src/pages/api/core/app/plugin/getPreviewNode.ts @@ -0,0 +1,33 @@ +/* + get plugin preview modules + */ +import type { NextApiResponse } from 'next'; +import { + getPluginPreviewNode, + splitCombinePluginId +} from '@fastgpt/service/core/app/plugin/controller'; +import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/index.d'; +import { NextAPI } from '@/service/middleware/entry'; +import { ApiRequestProps } from '@fastgpt/service/type/next'; +import { authApp } from '@fastgpt/service/support/permission/app/auth'; +import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'; +import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants'; + +export type GetPreviewNodeQuery = { appId: string }; + +async function handler( + req: ApiRequestProps<{}, GetPreviewNodeQuery>, + res: NextApiResponse +): Promise { + const { appId } = req.query; + + const { source } = await splitCombinePluginId(appId); + + if (source === PluginSourceEnum.personal) { + await authApp({ req, authToken: true, appId, per: WritePermissionVal }); + } + + return getPluginPreviewNode({ id: appId }); +} + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/plugin/pluginTemplate/getSystemPluginTemplates.ts b/projects/app/src/pages/api/core/app/plugin/getSystemPluginTemplates.ts similarity index 89% rename from projects/app/src/pages/api/core/plugin/pluginTemplate/getSystemPluginTemplates.ts rename to projects/app/src/pages/api/core/app/plugin/getSystemPluginTemplates.ts index 9f56f59fa..eed7ade99 100644 --- a/projects/app/src/pages/api/core/plugin/pluginTemplate/getSystemPluginTemplates.ts +++ b/projects/app/src/pages/api/core/app/plugin/getSystemPluginTemplates.ts @@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { authCert } from '@fastgpt/service/support/permission/auth/common'; -import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; +import { FlowNodeTypeEnum, defaultNodeVersion } from '@fastgpt/global/core/workflow/node/constant'; import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/index.d'; import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants'; @@ -22,7 +22,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< intro: plugin.intro, showStatus: true, isTool: plugin.isTool, - version: '481', + version: defaultNodeVersion, inputs: [], outputs: [] })) || []; diff --git a/projects/app/src/pages/api/core/app/transitionWorkflow.ts b/projects/app/src/pages/api/core/app/transitionWorkflow.ts new file mode 100644 index 000000000..0fdd63b52 --- /dev/null +++ b/projects/app/src/pages/api/core/app/transitionWorkflow.ts @@ -0,0 +1,53 @@ +import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next'; +import { NextAPI } from '@/service/middleware/entry'; +import { authApp } from '@fastgpt/service/support/permission/app/auth'; +import { OwnerPermissionVal } from '@fastgpt/global/support/permission/constant'; +import { MongoApp } from '@fastgpt/service/core/app/schema'; +import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; +import { onCreateApp } from './create'; + +export type transitionWorkflowQuery = {}; + +export type transitionWorkflowBody = { + appId: string; + createNew?: boolean; +}; + +export type transitionWorkflowResponse = { + id?: string; +}; + +async function handler( + req: ApiRequestProps, + res: ApiResponseType +): Promise { + const { appId, createNew } = req.body; + + const { app, tmbId } = await authApp({ + req, + appId, + authToken: true, + per: OwnerPermissionVal + }); + + if (createNew) { + const appId = await onCreateApp({ + parentId: app.parentId, + name: app.name + ' Copy', + avatar: app.avatar, + type: AppTypeEnum.workflow, + modules: app.modules, + edges: app.edges, + teamId: app.teamId, + tmbId + }); + + return { id: appId }; + } else { + await MongoApp.findByIdAndUpdate(appId, { type: AppTypeEnum.workflow }); + } + + return {}; +} + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/app/update.ts b/projects/app/src/pages/api/core/app/update.ts index f7cb0d6c2..30f4b3114 100644 --- a/projects/app/src/pages/api/core/app/update.ts +++ b/projects/app/src/pages/api/core/app/update.ts @@ -6,8 +6,7 @@ import { beforeUpdateAppFormat } from '@fastgpt/service/core/app/controller'; import { NextAPI } from '@/service/middleware/entry'; import { ManagePermissionVal, - WritePermissionVal, - OwnerPermissionVal + WritePermissionVal } from '@fastgpt/global/support/permission/constant'; import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils'; @@ -22,7 +21,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { nodes, edges, chatConfig, - permission, teamTags, defaultPermission } = req.body as AppUpdateParams; @@ -33,9 +31,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { } // 凭证校验 - if (permission) { - await authApp({ req, authToken: true, appId, per: OwnerPermissionVal }); - } else if (defaultPermission) { + if (defaultPermission) { await authApp({ req, authToken: true, appId, per: ManagePermissionVal }); } else { await authApp({ req, authToken: true, appId, per: WritePermissionVal }); @@ -56,7 +52,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { type, avatar, intro, - permission, defaultPermission, ...(teamTags && teamTags), ...(formatNodes && { diff --git a/projects/app/src/pages/api/core/app/version/latest.ts b/projects/app/src/pages/api/core/app/version/latest.ts new file mode 100644 index 000000000..8ad49c1a6 --- /dev/null +++ b/projects/app/src/pages/api/core/app/version/latest.ts @@ -0,0 +1,36 @@ +import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next'; +import { NextAPI } from '@/service/middleware/entry'; +import { authApp } from '@fastgpt/service/support/permission/app/auth'; +import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'; +import { getAppLatestVersion } from '@fastgpt/service/core/app/controller'; +import { AppChatConfigType } from '@fastgpt/global/core/app/type'; +import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; +import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type'; + +export type getLatestVersionQuery = { + appId: string; +}; + +export type getLatestVersionBody = {}; + +export type getLatestVersionResponse = { + nodes: StoreNodeItemType[]; + edges: StoreEdgeItemType[]; + chatConfig: AppChatConfigType; +}; + +async function handler( + req: ApiRequestProps, + res: ApiResponseType +): Promise { + const { app } = await authApp({ + req, + authToken: true, + appId: req.query.appId, + per: WritePermissionVal + }); + + return getAppLatestVersion(req.query.appId, app); +} + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/app/version/publish.ts b/projects/app/src/pages/api/core/app/version/publish.ts index 398503b8d..d2b6d76f5 100644 --- a/projects/app/src/pages/api/core/app/version/publish.ts +++ b/projects/app/src/pages/api/core/app/version/publish.ts @@ -8,6 +8,7 @@ import { beforeUpdateAppFormat } from '@fastgpt/service/core/app/controller'; import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time'; import { PostPublishAppProps } from '@/global/core/app/api'; import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'; +import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; type Response = {}; @@ -15,13 +16,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse): Promise< const { appId } = req.query as { appId: string }; const { nodes = [], edges = [], chatConfig, type } = req.body as PostPublishAppProps; - await authApp({ appId, req, per: WritePermissionVal, authToken: true }); + const { app } = await authApp({ appId, req, per: WritePermissionVal, authToken: true }); const { nodes: formatNodes } = beforeUpdateAppFormat({ nodes }); await mongoSessionRun(async (session) => { // create version histories - await MongoAppVersion.create( + const [{ _id }] = await MongoAppVersion.create( [ { appId, @@ -34,18 +35,25 @@ async function handler(req: NextApiRequest, res: NextApiResponse): Promise< ); // update app - await MongoApp.findByIdAndUpdate(appId, { - modules: formatNodes, - edges, - chatConfig, - updateTime: new Date(), - version: 'v2', - type, - scheduledTriggerConfig: chatConfig?.scheduledTriggerConfig, - scheduledTriggerNextTime: chatConfig?.scheduledTriggerConfig - ? getNextTimeByCronStringAndTimezone(chatConfig.scheduledTriggerConfig) - : null - }); + await MongoApp.findByIdAndUpdate( + appId, + { + modules: formatNodes, + edges, + chatConfig, + updateTime: new Date(), + version: 'v2', + type, + scheduledTriggerConfig: chatConfig?.scheduledTriggerConfig, + scheduledTriggerNextTime: chatConfig?.scheduledTriggerConfig?.cronString + ? getNextTimeByCronStringAndTimezone(chatConfig.scheduledTriggerConfig) + : null, + ...(app.type === AppTypeEnum.plugin && { 'pluginData.nodeVersion': _id }) + }, + { + session + } + ); }); return {}; diff --git a/projects/app/src/pages/api/core/app/version/revert.ts b/projects/app/src/pages/api/core/app/version/revert.ts index d7933ad06..fe48334a4 100644 --- a/projects/app/src/pages/api/core/app/version/revert.ts +++ b/projects/app/src/pages/api/core/app/version/revert.ts @@ -8,14 +8,20 @@ import { beforeUpdateAppFormat } from '@fastgpt/service/core/app/controller'; import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time'; import { PostRevertAppProps } from '@/global/core/app/api'; import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'; +import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; type Response = {}; async function handler(req: NextApiRequest, res: NextApiResponse): Promise<{}> { const { appId } = req.query as { appId: string }; - const { editNodes = [], editEdges = [], versionId } = req.body as PostRevertAppProps; + const { + editNodes = [], + editEdges = [], + editChatConfig, + versionId + } = req.body as PostRevertAppProps; - await authApp({ appId, req, per: WritePermissionVal, authToken: true }); + const { app } = await authApp({ appId, req, per: WritePermissionVal, authToken: true }); const version = await MongoAppVersion.findOne({ _id: versionId, @@ -37,19 +43,21 @@ async function handler(req: NextApiRequest, res: NextApiResponse): Promise< { appId, nodes: formatEditNodes, - edges: editEdges + edges: editEdges, + chatConfig: editChatConfig } ], { session } ); // 为历史版本再创建一个版本 - await MongoAppVersion.create( + const [{ _id }] = await MongoAppVersion.create( [ { appId, nodes: version.nodes, - edges: version.edges + edges: version.edges, + chatConfig: version.chatConfig } ], { session } @@ -59,11 +67,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse): Promise< await MongoApp.findByIdAndUpdate(appId, { modules: version.nodes, edges: version.edges, + chatConfig: version.chatConfig, updateTime: new Date(), scheduledTriggerConfig, - scheduledTriggerNextTime: scheduledTriggerConfig + scheduledTriggerNextTime: scheduledTriggerConfig?.cronString ? getNextTimeByCronStringAndTimezone(scheduledTriggerConfig) - : null + : null, + ...(app.type === AppTypeEnum.plugin && { 'pluginData.nodeVersion': _id }) }); }); diff --git a/projects/app/src/pages/api/core/chat/inputGuide/query.ts b/projects/app/src/pages/api/core/chat/inputGuide/query.ts index 4afca44e1..429151d2b 100644 --- a/projects/app/src/pages/api/core/chat/inputGuide/query.ts +++ b/projects/app/src/pages/api/core/chat/inputGuide/query.ts @@ -6,6 +6,7 @@ import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat'; import { authChatCert } from '@/service/support/permission/auth/chat'; import { MongoApp } from '@fastgpt/service/core/app/schema'; import { AppErrEnum } from '@fastgpt/global/common/error/code/app'; +import { replaceRegChars } from '@fastgpt/global/common/string/tools'; export type QueryChatInputGuideBody = OutLinkChatAuthProps & { appId: string; @@ -28,7 +29,7 @@ async function handler( const params = { appId, - ...(searchKey && { text: { $regex: new RegExp(searchKey, 'i') } }) + ...(searchKey && { text: { $regex: new RegExp(`${replaceRegChars(searchKey)}`, 'i') } }) }; const result = await MongoChatInputGuide.find(params).sort({ _id: -1 }).limit(6); diff --git a/projects/app/src/pages/api/core/plugin/create.ts b/projects/app/src/pages/api/core/plugin/create.ts deleted file mode 100644 index 8d992cd1e..000000000 --- a/projects/app/src/pages/api/core/plugin/create.ts +++ /dev/null @@ -1,78 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@fastgpt/service/common/response'; -import { connectToDatabase } from '@/service/mongo'; -import type { CreateOnePluginParams } from '@fastgpt/global/core/plugin/controller'; -import { authUserPer } from '@fastgpt/service/support/permission/user/auth'; -import { MongoPlugin } from '@fastgpt/service/core/plugin/schema'; -import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun'; -import { httpApiSchema2Plugins } from '@fastgpt/global/core/plugin/httpPlugin/utils'; -import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { teamId, tmbId } = await authUserPer({ req, authToken: true, per: WritePermissionVal }); - const body = req.body as CreateOnePluginParams; - - // await checkTeamPluginLimit(teamId); - - // create parent plugin and child plugin - if (body.metadata?.apiSchemaStr) { - const parentId = await mongoSessionRun(async (session) => { - const [{ _id: parentId }] = await MongoPlugin.create( - [ - { - ...body, - parentId: null, - teamId, - tmbId, - version: 'v2' - } - ], - { session } - ); - - const childrenPlugins = await httpApiSchema2Plugins({ - parentId, - apiSchemaStr: body.metadata?.apiSchemaStr, - customHeader: body.metadata?.customHeaders - }); - - await MongoPlugin.create( - childrenPlugins.map((item) => ({ - ...item, - metadata: { - pluginUid: item.name - }, - teamId, - tmbId, - version: 'v2' - })), - { - session - } - ); - return parentId; - }); - - jsonRes(res, { - data: parentId - }); - } else { - const { _id } = await MongoPlugin.create({ - ...body, - teamId, - tmbId, - version: 'v2' - }); - jsonRes(res, { - data: _id - }); - } - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } -} diff --git a/projects/app/src/pages/api/core/plugin/delete.ts b/projects/app/src/pages/api/core/plugin/delete.ts deleted file mode 100644 index dc624a3d1..000000000 --- a/projects/app/src/pages/api/core/plugin/delete.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@fastgpt/service/common/response'; -import { connectToDatabase } from '@/service/mongo'; -import { MongoPlugin } from '@fastgpt/service/core/plugin/schema'; -import { authPluginCrud } from '@fastgpt/service/support/permission/auth/plugin'; -import { authUserPer } from '@fastgpt/service/support/permission/user/auth'; -import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun'; -import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { teamId } = await authUserPer({ req, authToken: true, per: WritePermissionVal }); - const { pluginId } = req.query as { pluginId: string }; - - if (!pluginId) { - throw new Error('缺少参数'); - } - await authPluginCrud({ req, authToken: true, pluginId, per: 'owner' }); - - await mongoSessionRun(async (session) => { - await MongoPlugin.deleteMany( - { - teamId, - parentId: pluginId - }, - { - session - } - ); - await MongoPlugin.findByIdAndDelete(pluginId, { session }); - }); - - jsonRes(res, {}); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } -} diff --git a/projects/app/src/pages/api/core/plugin/detail.ts b/projects/app/src/pages/api/core/plugin/detail.ts deleted file mode 100644 index edde82339..000000000 --- a/projects/app/src/pages/api/core/plugin/detail.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@fastgpt/service/common/response'; -import { connectToDatabase } from '@/service/mongo'; -import { authPluginCrud } from '@fastgpt/service/support/permission/auth/plugin'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - const { id } = req.query as { id: string }; - await connectToDatabase(); - const { plugin } = await authPluginCrud({ req, authToken: true, pluginId: id, per: 'r' }); - - jsonRes(res, { - data: plugin - }); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } -} diff --git a/projects/app/src/pages/api/core/plugin/getPreviewNode.ts b/projects/app/src/pages/api/core/plugin/getPreviewNode.ts deleted file mode 100644 index 860c34952..000000000 --- a/projects/app/src/pages/api/core/plugin/getPreviewNode.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - get plugin preview modules - */ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@fastgpt/service/common/response'; -import { connectToDatabase } from '@/service/mongo'; -import { getPluginPreviewNode } from '@fastgpt/service/core/plugin/controller'; -import { authPluginCanUse } from '@fastgpt/service/support/permission/auth/plugin'; -import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/index.d'; -import { authCert } from '@fastgpt/service/support/permission/auth/common'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - const { id } = req.query as { id: string }; - await connectToDatabase(); - const { teamId, tmbId } = await authCert({ req, authToken: true }); - await authPluginCanUse({ id, teamId, tmbId }); - - jsonRes(res, { - data: await getPluginPreviewNode({ id }) - }); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } -} diff --git a/projects/app/src/pages/api/core/plugin/list.ts b/projects/app/src/pages/api/core/plugin/list.ts deleted file mode 100644 index 1d935bbf7..000000000 --- a/projects/app/src/pages/api/core/plugin/list.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@fastgpt/service/common/response'; -import { connectToDatabase } from '@/service/mongo'; -import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; -import { authCert } from '@fastgpt/service/support/permission/auth/common'; -import { MongoPlugin } from '@fastgpt/service/core/plugin/schema'; -import { PluginListItemType } from '@fastgpt/global/core/plugin/controller'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { parentId, type } = req.query as { parentId?: string; type?: DatasetTypeEnum }; - - const { teamId } = await authCert({ req, authToken: true }); - - const plugins = await MongoPlugin.find( - { - teamId, - ...(parentId !== undefined && { parentId: parentId || null }), - ...(type && { type }) - }, - '_id parentId type name avatar intro metadata' - ) - .sort({ updateTime: -1 }) - .lean(); - - jsonRes(res, { - data: plugins - }); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } -} diff --git a/projects/app/src/pages/api/core/plugin/paths.ts b/projects/app/src/pages/api/core/plugin/paths.ts deleted file mode 100644 index d25c75af4..000000000 --- a/projects/app/src/pages/api/core/plugin/paths.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@fastgpt/service/common/response'; -import { connectToDatabase } from '@/service/mongo'; -import type { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type.d'; -import { authCert } from '@fastgpt/service/support/permission/auth/common'; -import { MongoPlugin } from '@fastgpt/service/core/plugin/schema'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - - const { parentId } = req.query as { parentId: string }; - - if (!parentId) { - return jsonRes(res, { - data: [] - }); - } - - await authCert({ req, authToken: true }); - - jsonRes(res, { - data: await getParents(parentId) - }); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } -} - -async function getParents(parentId?: string): Promise { - if (!parentId) { - return []; - } - - const parent = await MongoPlugin.findById(parentId, 'name parentId'); - - if (!parent) return []; - - const paths = await getParents(parent.parentId); - paths.push({ parentId, parentName: parent.name }); - - return paths; -} diff --git a/projects/app/src/pages/api/core/plugin/pluginTemplate/getTeamPluginTemplates.ts b/projects/app/src/pages/api/core/plugin/pluginTemplate/getTeamPluginTemplates.ts deleted file mode 100644 index 319d5c936..000000000 --- a/projects/app/src/pages/api/core/plugin/pluginTemplate/getTeamPluginTemplates.ts +++ /dev/null @@ -1,64 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@fastgpt/service/common/response'; -import { connectToDatabase } from '@/service/mongo'; -import { authCert } from '@fastgpt/service/support/permission/auth/common'; -import { MongoPlugin } from '@fastgpt/service/core/plugin/schema'; -import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; -import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/index.d'; -import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const { parentId, searchKey } = req.query as { parentId?: string; searchKey?: string }; - const { teamId } = await authCert({ req, authToken: true }); - - const userPlugins = await (async () => { - if (searchKey) { - return MongoPlugin.find({ - teamId, - // search name or intro - $or: [ - { name: { $regex: searchKey, $options: 'i' } }, - { intro: { $regex: searchKey, $options: 'i' } } - ] - }) - .sort({ - updateTime: -1 - }) - .lean(); - } else { - return MongoPlugin.find({ teamId, parentId: parentId || null }) - .sort({ - updateTime: -1 - }) - .lean(); - } - })(); - - const data: FlowNodeTemplateType[] = userPlugins.map((plugin) => ({ - id: String(plugin._id), - parentId: String(plugin.parentId), - pluginId: String(plugin._id), - pluginType: plugin.type, - templateType: FlowNodeTemplateTypeEnum.personalPlugin, - flowNodeType: FlowNodeTypeEnum.pluginModule, - avatar: plugin.avatar, - name: plugin.name, - intro: plugin.intro, - showStatus: false, - version: '481', - inputs: [], - outputs: [] - })); - - jsonRes(res, { - data - }); - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } -} diff --git a/projects/app/src/pages/api/core/plugin/update.ts b/projects/app/src/pages/api/core/plugin/update.ts deleted file mode 100644 index d85214ffa..000000000 --- a/projects/app/src/pages/api/core/plugin/update.ts +++ /dev/null @@ -1,152 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@fastgpt/service/common/response'; -import { connectToDatabase } from '@/service/mongo'; -import type { UpdatePluginParams } from '@fastgpt/global/core/plugin/controller'; -import { authPluginCrud } from '@fastgpt/service/support/permission/auth/plugin'; -import { MongoPlugin } from '@fastgpt/service/core/plugin/schema'; -import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun'; -import { ClientSession } from '@fastgpt/service/common/mongo'; -import { httpApiSchema2Plugins } from '@fastgpt/global/core/plugin/httpPlugin/utils'; -import { isEqual } from 'lodash'; -import { nanoid } from 'nanoid'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - await connectToDatabase(); - const body = req.body as UpdatePluginParams; - - const { id, modules, edges, ...props } = body; - - const { teamId, tmbId } = await authPluginCrud({ - req, - authToken: true, - pluginId: id, - per: 'owner' - }); - - const originPlugin = await MongoPlugin.findById(id); - - let updateData = { - name: props.name, - intro: props.intro, - avatar: props.avatar, - parentId: props.parentId, - version: 'v2', - ...(modules?.length && { - modules: modules - }), - ...(edges?.length && { edges }), - metadata: props.metadata, - nodeVersion: originPlugin?.nodeVersion - }; - - const isNodeVersionEqual = - isEqual( - originPlugin?.modules.map((module) => { - return { ...module, position: undefined }; - }), - updateData.modules?.map((module) => { - return { ...module, position: undefined }; - }) - ) && isEqual(originPlugin?.edges, updateData.edges); - - if (!isNodeVersionEqual) { - updateData = { - ...updateData, - nodeVersion: nanoid(6) - }; - } - if (props.metadata?.apiSchemaStr) { - await mongoSessionRun(async (session) => { - // update children - await updateHttpChildrenPlugin({ - teamId, - tmbId, - parent: body, - session - }); - await MongoPlugin.findByIdAndUpdate(id, updateData, { session }); - }); - - jsonRes(res, {}); - } else { - jsonRes(res, { - data: await MongoPlugin.findByIdAndUpdate(id, updateData) - }); - } - } catch (err) { - jsonRes(res, { - code: 500, - error: err - }); - } -} - -const updateHttpChildrenPlugin = async ({ - teamId, - tmbId, - parent, - session -}: { - teamId: string; - tmbId: string; - parent: UpdatePluginParams; - session: ClientSession; -}) => { - if (!parent.metadata?.apiSchemaStr) return; - const dbPlugins = await MongoPlugin.find( - { - parentId: parent.id, - teamId - }, - '_id metadata' - ); - - const schemaPlugins = await httpApiSchema2Plugins({ - parentId: parent.id, - apiSchemaStr: parent.metadata?.apiSchemaStr, - customHeader: parent.metadata?.customHeaders - }); - - // 数据库中存在,schema不存在,删除 - for await (const plugin of dbPlugins) { - if (!schemaPlugins.find((p) => p.name === plugin.metadata?.pluginUid)) { - await MongoPlugin.deleteOne({ _id: plugin._id }, { session }); - } - } - // 数据库中不存在,schema存在,新增 - for await (const plugin of schemaPlugins) { - if (!dbPlugins.find((p) => p.metadata?.pluginUid === plugin.name)) { - await MongoPlugin.create( - [ - { - ...plugin, - metadata: { - pluginUid: plugin.name - }, - teamId, - tmbId, - version: 'v2' - } - ], - { - session - } - ); - } - } - // 数据库中存在,schema存在,更新 - for await (const plugin of schemaPlugins) { - const dbPlugin = dbPlugins.find((p) => plugin.name === p.metadata?.pluginUid); - if (dbPlugin) { - await MongoPlugin.findByIdAndUpdate( - dbPlugin._id, - { - ...plugin, - version: 'v2' - }, - { session } - ); - } - } -}; diff --git a/projects/app/src/pages/api/core/workflow/debug.ts b/projects/app/src/pages/api/core/workflow/debug.ts index e28a97271..6264c3905 100644 --- a/projects/app/src/pages/api/core/workflow/debug.ts +++ b/projects/app/src/pages/api/core/workflow/debug.ts @@ -6,7 +6,6 @@ import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch'; import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team'; import { PostWorkflowDebugProps, PostWorkflowDebugResponse } from '@/global/core/workflow/api'; -import { authPluginCrud } from '@fastgpt/service/support/permission/auth/plugin'; import { NextAPI } from '@/service/middleware/entry'; import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; import { defaultApp } from '@/web/core/app/constants'; @@ -15,13 +14,7 @@ async function handler( req: NextApiRequest, res: NextApiResponse ): Promise { - const { - nodes = [], - edges = [], - variables = {}, - appId, - pluginId - } = req.body as PostWorkflowDebugProps; + const { nodes = [], edges = [], variables = {}, appId } = req.body as PostWorkflowDebugProps; if (!nodes) { throw new Error('Prams Error'); @@ -39,8 +32,7 @@ async function handler( req, authToken: true }), - appId && authApp({ req, authToken: true, appId, per: ReadPermissionVal }), - pluginId && authPluginCrud({ req, authToken: true, pluginId, per: 'r' }) + authApp({ req, authToken: true, appId, per: ReadPermissionVal }) ]); // auth balance diff --git a/projects/app/src/pages/app/detail/components/FlowEdit/Header.tsx b/projects/app/src/pages/app/detail/components/FlowEdit/Header.tsx deleted file mode 100644 index 088148a57..000000000 --- a/projects/app/src/pages/app/detail/components/FlowEdit/Header.tsx +++ /dev/null @@ -1,375 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { Box, Flex, IconButton, useTheme, useDisclosure, Button } from '@chakra-ui/react'; -import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d'; -import { useTranslation } from 'next-i18next'; -import { useCopyData } from '@/web/common/hooks/useCopyData'; -import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; -import dynamic from 'next/dynamic'; - -import MyIcon from '@fastgpt/web/components/common/Icon'; -import ChatTest, { type ChatTestComponentRef } from '@/components/core/workflow/Flow/ChatTest'; -import { uiWorkflow2StoreWorkflow } from '@/components/core/workflow/utils'; -import { useToast } from '@fastgpt/web/hooks/useToast'; -import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; -import { getErrText } from '@fastgpt/global/common/error/utils'; -import MyMenu from '@fastgpt/web/components/common/MyMenu'; -import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; -import { - checkWorkflowNodeAndConnection, - filterSensitiveNodesData -} from '@/web/core/workflow/utils'; -import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload'; -import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; -import { formatTime2HM } from '@fastgpt/global/common/string/time'; -import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext, getWorkflowStore } from '@/components/core/workflow/context'; -import { useInterval, useUpdateEffect } from 'ahooks'; -import { useI18n } from '@/web/context/I18n'; -import { AppContext } from '@/web/core/app/context/appContext'; - -const ImportSettings = dynamic(() => import('@/components/core/workflow/Flow/ImportSettings')); -const PublishHistories = dynamic( - () => import('@/components/core/workflow/components/PublishHistoriesSlider') -); - -type Props = { onClose: () => void }; - -const RenderHeaderContainer = React.memo(function RenderHeaderContainer({ - ChatTestRef, - setWorkflowTestData, - onClose -}: Props & { - ChatTestRef: React.RefObject; - setWorkflowTestData: React.Dispatch< - React.SetStateAction< - | { - nodes: StoreNodeItemType[]; - edges: StoreEdgeItemType[]; - } - | undefined - > - >; -}) { - const { appDetail } = useContextSelector(AppContext, (v) => v); - - const isV2Workflow = appDetail?.version === 'v2'; - - const theme = useTheme(); - const { toast } = useToast(); - const { t } = useTranslation(); - const { appT } = useI18n(); - - const { copyData } = useCopyData(); - const { openConfirm: openConfigPublish, ConfirmModal } = useConfirm({ - content: t('core.app.Publish Confirm') - }); - const { publishApp, updateAppDetail } = useContextSelector(AppContext, (v) => v); - const edges = useContextSelector(WorkflowContext, (v) => v.edges); - - const [isSaving, setIsSaving] = useState(false); - const [saveLabel, setSaveLabel] = useState(t('core.app.Onclick to save')); - const onUpdateNodeError = useContextSelector(WorkflowContext, (v) => v.onUpdateNodeError); - - const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure(); - - const isShowVersionHistories = useContextSelector( - WorkflowContext, - (v) => v.isShowVersionHistories - ); - const setIsShowVersionHistories = useContextSelector( - WorkflowContext, - (v) => v.setIsShowVersionHistories - ); - const workflowDebugData = useContextSelector(WorkflowContext, (v) => v.workflowDebugData); - - const flowData2StoreDataAndCheck = useCallback(async () => { - const { nodes } = await getWorkflowStore(); - const checkResults = checkWorkflowNodeAndConnection({ nodes, edges }); - - if (!checkResults) { - const storeNodes = uiWorkflow2StoreWorkflow({ nodes, edges }); - - return storeNodes; - } else { - checkResults.forEach((nodeId) => onUpdateNodeError(nodeId, true)); - toast({ - status: 'warning', - title: t('core.workflow.Check Failed') - }); - } - }, [edges, onUpdateNodeError, t, toast]); - - const onclickSave = useCallback( - async (forbid?: boolean) => { - // version preview / debug mode, not save - if (!isV2Workflow || isShowVersionHistories || forbid) return; - - const { nodes } = await getWorkflowStore(); - - if (nodes.length === 0) return null; - setIsSaving(true); - - const storeWorkflow = uiWorkflow2StoreWorkflow({ nodes, edges }); - - try { - await updateAppDetail({ - ...storeWorkflow, - type: AppTypeEnum.advanced, - chatConfig: appDetail.chatConfig, - //@ts-ignore - version: 'v2' - }); - - setSaveLabel( - t('core.app.Auto Save time', { - time: formatTime2HM() - }) - ); - // ChatTestRef.current?.resetChatTest(); - } catch (error) {} - - setIsSaving(false); - - return null; - }, - [isV2Workflow, isShowVersionHistories, edges, updateAppDetail, appDetail.chatConfig, t] - ); - - const onclickPublish = useCallback(async () => { - setIsSaving(true); - const data = await flowData2StoreDataAndCheck(); - if (data) { - try { - await publishApp({ - ...data, - type: AppTypeEnum.advanced, - chatConfig: appDetail.chatConfig, - //@ts-ignore - version: 'v2' - }); - toast({ - status: 'success', - title: t('core.app.Publish Success') - }); - ChatTestRef.current?.resetChatTest(); - } catch (error) { - toast({ - status: 'warning', - title: getErrText(error, t('core.app.Publish Failed')) - }); - } - } - - setIsSaving(false); - }, [flowData2StoreDataAndCheck, publishApp, appDetail.chatConfig, toast, t, ChatTestRef]); - - const saveAndBack = useCallback(async () => { - try { - await onclickSave(); - onClose(); - } catch (error) {} - }, [onClose, onclickSave]); - - const onExportWorkflow = useCallback(async () => { - const data = await flowData2StoreDataAndCheck(); - if (data) { - copyData( - JSON.stringify( - { - nodes: filterSensitiveNodesData(data.nodes), - edges: data.edges, - chatConfig: appDetail.chatConfig - }, - null, - 2 - ), - appT('Export Config Successful') - ); - } - }, [appDetail.chatConfig, appT, copyData, flowData2StoreDataAndCheck]); - - // effect - useBeforeunload({ - callback: onclickSave, - tip: t('core.common.tip.leave page') - }); - - useInterval(() => { - if (!appDetail._id) return; - onclickSave(!!workflowDebugData); - }, 20000); - - const Render = useMemo(() => { - return ( - <> - - } - borderRadius={'50%'} - w={'26px'} - h={'26px'} - borderColor={'myGray.300'} - variant={'whiteBase'} - aria-label={''} - isLoading={isSaving} - onClick={saveAndBack} - /> - - - {appDetail.name} - - {!isShowVersionHistories && isV2Workflow && ( - - onclickSave()} - color={'myGray.500'} - > - {saveLabel} - - - )} - - - - - {!isShowVersionHistories && ( - <> - } - aria-label={''} - size={'sm'} - variant={'whitePrimary'} - /> - } - menuList={[ - { - children: [ - { - label: appT('Import Configs'), - icon: 'common/importLight', - onClick: onOpenImport - }, - { - label: appT('Export Configs'), - icon: 'export', - onClick: onExportWorkflow - } - ] - } - ]} - /> - - } - aria-label={''} - size={'sm'} - w={'30px'} - variant={'whitePrimary'} - onClick={() => setIsShowVersionHistories(true)} - /> - - )} - - - - {!isShowVersionHistories && ( - - )} - - - - ); - }, [ - theme.borders.base, - isSaving, - saveAndBack, - appDetail.name, - isShowVersionHistories, - isV2Workflow, - t, - saveLabel, - appT, - onOpenImport, - onExportWorkflow, - openConfigPublish, - onclickPublish, - ConfirmModal, - onclickSave, - setIsShowVersionHistories, - flowData2StoreDataAndCheck, - setWorkflowTestData - ]); - - return ( - <> - {Render} - {isOpenImport && } - {isShowVersionHistories && } - - ); -}); - -const Header = (props: Props) => { - const ChatTestRef = useRef(null); - - const [workflowTestData, setWorkflowTestData] = useState<{ - nodes: StoreNodeItemType[]; - edges: StoreEdgeItemType[]; - }>(); - const { isOpen: isOpenTest, onOpen: onOpenTest, onClose: onCloseTest } = useDisclosure(); - - useUpdateEffect(() => { - onOpenTest(); - }, [workflowTestData]); - - return ( - <> - - - - ); -}; - -export default React.memo(Header); diff --git a/projects/app/src/pages/app/detail/components/InfoModal.tsx b/projects/app/src/pages/app/detail/components/InfoModal.tsx index 10925c291..3aff70e90 100644 --- a/projects/app/src/pages/app/detail/components/InfoModal.tsx +++ b/projects/app/src/pages/app/detail/components/InfoModal.tsx @@ -27,7 +27,7 @@ import { getCollaboratorList } from '@/web/core/app/api/collaborator'; import { useContextSelector } from 'use-context-selector'; -import { AppContext } from '@/web/core/app/context/appContext'; +import { AppContext } from '@/pages/app/detail/components/context'; import { AppDefaultPermissionVal, AppPermissionList diff --git a/projects/app/src/pages/app/detail/components/Logs.tsx b/projects/app/src/pages/app/detail/components/Logs/index.tsx similarity index 64% rename from projects/app/src/pages/app/detail/components/Logs.tsx rename to projects/app/src/pages/app/detail/components/Logs/index.tsx index 35ff23656..0d6cd4f09 100644 --- a/projects/app/src/pages/app/detail/components/Logs.tsx +++ b/projects/app/src/pages/app/detail/components/Logs/index.tsx @@ -11,7 +11,8 @@ import { Tbody, useTheme, useDisclosure, - ModalBody + ModalBody, + HStack } from '@chakra-ui/react'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { useTranslation } from 'next-i18next'; @@ -35,13 +36,17 @@ import { formatChatValue2InputType } from '@/components/ChatBox/utils'; import { getNanoid } from '@fastgpt/global/common/string/tools'; import { useI18n } from '@/web/context/I18n'; import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; +import { useContextSelector } from 'use-context-selector'; +import { AppContext } from '../context'; +import { cardStyles } from '../constants'; -const Logs = ({ appId }: { appId: string }) => { +const Logs = () => { const { t } = useTranslation(); const { appT } = useI18n(); - const { isPc } = useSystemStore(); + const appId = useContextSelector(AppContext, (v) => v.appId); + const [dateRange, setDateRange] = useState({ from: addDays(new Date(), -7), to: new Date() @@ -72,8 +77,8 @@ const Logs = ({ appId }: { appId: string }) => { const [detailLogsId, setDetailLogsId] = useState(); return ( - - + <> + {isPc && ( <> @@ -96,97 +101,106 @@ const Logs = ({ appId }: { appId: string }) => { {/* table */} - - - - - - - - - - - - - - {logs.map((item) => ( - setDetailLogsId(item.id)} - > - - - - - - + + +
{t('core.app.logs.Source And Time')}{appT('Logs Title')}{appT('Logs Message Total')}{appT('Feedback Count')}{t('core.app.feedback.Custom feedback')}{appT('Mark Count')}
- {t(ChatSourceMap[item.source]?.name || 'UnKnow')} - {dayjs(item.time).format('YYYY/MM/DD HH:mm')} - - {item.title} - {item.messageCount} - {!!item?.userGoodFeedbackCount && ( - - - {item.userGoodFeedbackCount} - - )} - {!!item?.userBadFeedbackCount && ( - - - {item.userBadFeedbackCount} - - )} - {!item?.userGoodFeedbackCount && !item?.userBadFeedbackCount && <>-} - {item.customFeedbacksCount || '-'}{item.markCount}
+ + + + + + + + - ))} - -
{t('core.app.logs.Source And Time')}{appT('Logs Title')}{appT('Logs Message Total')}{appT('Feedback Count')}{t('core.app.feedback.Custom feedback')}{appT('Mark Count')}
-
- {logs.length === 0 && !isLoading && } - - getData(1)} - /> - + + + {logs.map((item) => ( + setDetailLogsId(item.id)} + > + + {t(ChatSourceMap[item.source]?.name || 'UnKnow')} + {dayjs(item.time).format('YYYY/MM/DD HH:mm')} + + + {item.title} + + {item.messageCount} + + {!!item?.userGoodFeedbackCount && ( + + + {item.userGoodFeedbackCount} + + )} + {!!item?.userBadFeedbackCount && ( + + + {item.userBadFeedbackCount} + + )} + {!item?.userGoodFeedbackCount && !item?.userBadFeedbackCount && <>-} + + {item.customFeedbacksCount || '-'} + {item.markCount} + + ))} + + + {logs.length === 0 && !isLoading && } + + + + getData(1)} + /> - + {!!detailLogsId && ( @@ -206,7 +220,7 @@ const Logs = ({ appId }: { appId: string }) => { > {t('core.chat.Mark Description')} -
+ ); }; diff --git a/projects/app/src/pages/app/detail/components/Plugin/Header.tsx b/projects/app/src/pages/app/detail/components/Plugin/Header.tsx new file mode 100644 index 000000000..76b83786a --- /dev/null +++ b/projects/app/src/pages/app/detail/components/Plugin/Header.tsx @@ -0,0 +1,191 @@ +import React, { useCallback, useMemo } from 'react'; +import { Box, Flex, Button, IconButton } from '@chakra-ui/react'; +import { useTranslation } from 'next-i18next'; +import dynamic from 'next/dynamic'; + +import MyIcon from '@fastgpt/web/components/common/Icon'; +import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext, getWorkflowStore } from '../WorkflowComponents/context'; +import { useInterval } from 'ahooks'; +import { AppContext, TabEnum } from '../context'; +import PopoverConfirm from '@fastgpt/web/components/common/MyPopover/PopoverConfirm'; +import { useRouter } from 'next/router'; + +import AppCard from '../WorkflowComponents/AppCard'; +import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils'; +const PublishHistories = dynamic(() => import('../PublishHistoriesSlider')); + +const Header = () => { + const { t } = useTranslation(); + const router = useRouter(); + + const { appDetail, onPublish, currentTab } = useContextSelector(AppContext, (v) => v); + const isV2Workflow = appDetail?.version === 'v2'; + + const { + flowData2StoreDataAndCheck, + onSaveWorkflow, + setHistoriesDefaultData, + historiesDefaultData, + initData + } = useContextSelector(WorkflowContext, (v) => v); + + const onclickPublish = useCallback(async () => { + const data = flowData2StoreDataAndCheck(); + if (data) { + await onPublish({ + ...data, + chatConfig: appDetail.chatConfig, + //@ts-ignore + version: 'v2' + }); + } + }, [flowData2StoreDataAndCheck, onPublish, appDetail.chatConfig]); + + const saveAndBack = useCallback(async () => { + try { + await onSaveWorkflow(); + router.push('/app/list'); + } catch (error) {} + }, [onSaveWorkflow, router]); + // effect + useBeforeunload({ + callback: onSaveWorkflow, + tip: t('core.common.tip.leave page') + }); + useInterval(() => { + if (!appDetail._id) return; + onSaveWorkflow(); + }, 40000); + + const Render = useMemo(() => { + return ( + <> + {/* {!isPc && ( + + + + )} */} + + {/* back */} + + {/* app info */} + + + + + {/* {isPc && ( + + + + )} */} + + + {currentTab === TabEnum.appEdit && ( + <> + {!historiesDefaultData && ( + } + aria-label={''} + size={'sm'} + w={'30px'} + variant={'whitePrimary'} + onClick={async () => { + const { nodes, edges } = uiWorkflow2StoreWorkflow(await getWorkflowStore()); + + setHistoriesDefaultData({ + nodes, + edges, + chatConfig: appDetail.chatConfig + }); + }} + /> + )} + {/* */} + + {!historiesDefaultData && ( + } + > + {t('core.app.Publish')} + + } + onConfirm={() => onclickPublish()} + /> + )} + + )} + + {historiesDefaultData && ( + { + setHistoriesDefaultData(undefined); + }} + defaultData={historiesDefaultData} + /> + )} + + ); + }, [ + appDetail.chatConfig, + currentTab, + historiesDefaultData, + initData, + isV2Workflow, + onclickPublish, + saveAndBack, + setHistoriesDefaultData, + t + ]); + + return Render; +}; + +export default React.memo(Header); diff --git a/projects/app/src/pages/app/detail/components/Plugin/index.tsx b/projects/app/src/pages/app/detail/components/Plugin/index.tsx new file mode 100644 index 000000000..ca099c12d --- /dev/null +++ b/projects/app/src/pages/app/detail/components/Plugin/index.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { pluginSystemModuleTemplates } from '@fastgpt/global/core/workflow/template/constants'; +import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; +import { v1Workflow2V2 } from '@/web/core/workflow/adapt'; +import WorkflowContextProvider, { WorkflowContext } from '../WorkflowComponents/context'; +import { useContextSelector } from 'use-context-selector'; +import { AppContext, TabEnum } from '../context'; +import { useMount } from 'ahooks'; +import Header from './Header'; +import { Flex } from '@chakra-ui/react'; +import { workflowBoxStyles } from '../constants'; +import dynamic from 'next/dynamic'; +import { cloneDeep } from 'lodash'; + +import Flow from '../WorkflowComponents/Flow'; +const Logs = dynamic(() => import('../Logs/index')); +const PublishChannel = dynamic(() => import('../Publish')); + +const WorkflowEdit = () => { + const { appDetail, currentTab } = useContextSelector(AppContext, (e) => e); + const isV2Workflow = appDetail?.version === 'v2'; + + const { openConfirm, ConfirmModal } = useConfirm({ + showCancel: false, + content: + '检测到您的高级编排为旧版,系统将为您自动格式化成新版工作流。\n\n由于版本差异较大,会导致一些工作流无法正常排布,请重新手动连接工作流。如仍异常,可尝试删除对应节点后重新添加。\n\n你可以直接点击调试进行工作流测试,调试完毕后点击发布。直到你点击发布,新工作流才会真正保存生效。\n\n在你发布新工作流前,自动保存不会生效。' + }); + + const initData = useContextSelector(WorkflowContext, (v) => v.initData); + + useMount(() => { + if (!isV2Workflow) { + openConfirm(() => { + initData(JSON.parse(JSON.stringify(v1Workflow2V2((appDetail.modules || []) as any)))); + })(); + } else { + initData( + cloneDeep({ + nodes: appDetail.modules || [], + edges: appDetail.edges || [] + }) + ); + } + }); + + return ( + +
+ + {currentTab === TabEnum.appEdit ? ( + + ) : ( + + {currentTab === TabEnum.publish && } + {currentTab === TabEnum.logs && } + + )} + + {!isV2Workflow && } + + ); +}; + +const Render = () => { + return ( + + + + ); +}; + +export default React.memo(Render); diff --git a/projects/app/src/pages/app/detail/components/Publish/API/index.tsx b/projects/app/src/pages/app/detail/components/Publish/API/index.tsx index 86b693a59..533f03cdd 100644 --- a/projects/app/src/pages/app/detail/components/Publish/API/index.tsx +++ b/projects/app/src/pages/app/detail/components/Publish/API/index.tsx @@ -6,11 +6,7 @@ import { useI18n } from '@/web/context/I18n'; const API = ({ appId }: { appId: string }) => { const { publishT } = useI18n(); - return ( - - - - ); + return ; }; export default API; diff --git a/projects/app/src/pages/app/detail/components/Publish/Link/index.tsx b/projects/app/src/pages/app/detail/components/Publish/Link/index.tsx index 08a411b01..94c385513 100644 --- a/projects/app/src/pages/app/detail/components/Publish/Link/index.tsx +++ b/projects/app/src/pages/app/detail/components/Publish/Link/index.tsx @@ -47,6 +47,7 @@ import { useI18n } from '@/web/context/I18n'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; +import MyBox from '@fastgpt/web/components/common/MyBox'; const SelectUsingWayModal = dynamic(() => import('./SelectUsingWayModal')); @@ -72,7 +73,7 @@ const Share = ({ appId }: { appId: string; type: PublishChannelEnum }) => { ); return ( - + @@ -241,8 +242,7 @@ const Share = ({ appId }: { appId: string; type: PublishChannelEnum }) => { /> )} - - + ); }; diff --git a/projects/app/src/pages/app/detail/components/Publish/index.tsx b/projects/app/src/pages/app/detail/components/Publish/index.tsx index 55d84427d..e04aa6891 100644 --- a/projects/app/src/pages/app/detail/components/Publish/index.tsx +++ b/projects/app/src/pages/app/detail/components/Publish/index.tsx @@ -1,5 +1,5 @@ import React, { useRef, useState } from 'react'; -import { Box, useTheme } from '@chakra-ui/react'; +import { Box, Flex, useTheme } from '@chakra-ui/react'; import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant'; import dynamic from 'next/dynamic'; @@ -7,13 +7,20 @@ import dynamic from 'next/dynamic'; import MyRadio from '@/components/common/MyRadio'; import { useTranslation } from 'next-i18next'; +import { useContextSelector } from 'use-context-selector'; +import { AppContext } from '../context'; +import { cardStyles } from '../constants'; + import Link from './Link'; const API = dynamic(() => import('./API')); const FeiShu = dynamic(() => import('./FeiShu')); -const OutLink = ({ appId }: { appId: string }) => { +const OutLink = () => { const { t } = useTranslation(); const theme = useTheme(); + + const appId = useContextSelector(AppContext, (v) => v.appId); + const publishList = useRef([ { icon: '/imgs/modal/shareFill.svg', @@ -38,11 +45,8 @@ const OutLink = ({ appId }: { appId: string }) => { const [linkType, setLinkType] = useState(PublishChannelEnum.share); return ( - - - {t('core.app.navbar.Publish app')} - - + <> + { /> - {linkType === PublishChannelEnum.share && ( - - )} - {linkType === PublishChannelEnum.apikey && } - {linkType === PublishChannelEnum.feishu && } - + + {linkType === PublishChannelEnum.share && ( + + )} + {linkType === PublishChannelEnum.apikey && } + {linkType === PublishChannelEnum.feishu && } + + ); }; diff --git a/projects/app/src/components/core/workflow/components/PublishHistoriesSlider.tsx b/projects/app/src/pages/app/detail/components/PublishHistoriesSlider.tsx similarity index 78% rename from projects/app/src/components/core/workflow/components/PublishHistoriesSlider.tsx rename to projects/app/src/pages/app/detail/components/PublishHistoriesSlider.tsx index 8a5c36474..ae24890fc 100644 --- a/projects/app/src/components/core/workflow/components/PublishHistoriesSlider.tsx +++ b/projects/app/src/pages/app/detail/components/PublishHistoriesSlider.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useState } from 'react'; -import { getPublishList, postRevertVersion } from '@/web/core/app/versionApi'; +import { getPublishList, postRevertVersion } from '@/web/core/app/api/version'; import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination'; import CustomRightDrawer from '@fastgpt/web/components/common/MyDrawer/CustomRightDrawer'; import { useTranslation } from 'next-i18next'; @@ -7,29 +7,38 @@ import { useMemoizedFn } from 'ahooks'; import { Box, Button, Flex } from '@chakra-ui/react'; import { formatTime2YMDHM } from '@fastgpt/global/common/string/time'; import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext } from '../context'; import { AppVersionSchemaType } from '@fastgpt/global/core/app/version'; import MyIcon from '@fastgpt/web/components/common/Icon'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { useRequest } from '@fastgpt/web/hooks/useRequest'; -import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type'; -import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; -import { AppContext } from '@/web/core/app/context/appContext'; +import { AppContext } from './context'; +import { useI18n } from '@/web/context/I18n'; +import { AppSchema } from '@fastgpt/global/core/app/type'; -const PublishHistoriesSlider = () => { +export type InitProps = { + nodes: AppSchema['modules']; + edges: AppSchema['edges']; + chatConfig: AppSchema['chatConfig']; +}; + +const PublishHistoriesSlider = ({ + onClose, + initData, + defaultData +}: { + onClose: () => void; + initData: (data: InitProps) => void; + defaultData: InitProps; +}) => { const { t } = useTranslation(); + const { appT } = useI18n(); const { openConfirm, ConfirmModal } = useConfirm({ content: t('core.workflow.publish.OnRevert version confirm') }); const { appDetail, setAppDetail } = useContextSelector(AppContext, (v) => v); - const appId = useContextSelector(WorkflowContext, (e) => e.appId); - const setIsShowVersionHistories = useContextSelector( - WorkflowContext, - (e) => e.setIsShowVersionHistories - ); - const initData = useContextSelector(WorkflowContext, (e) => e.initData); + const appId = appDetail._id; const [selectedHistoryId, setSelectedHistoryId] = useState(); @@ -43,25 +52,25 @@ const PublishHistoriesSlider = () => { } }); - const onClose = useMemoizedFn(() => { - setIsShowVersionHistories(false); - }); + const onPreview = useCallback( + (data: AppVersionSchemaType) => { + setSelectedHistoryId(data._id); - const onPreview = useCallback((data: AppVersionSchemaType) => { - setSelectedHistoryId(data._id); - - initData({ - nodes: data.nodes, - edges: data.edges - }); - }, []); + initData({ + nodes: data.nodes, + edges: data.edges, + chatConfig: data.chatConfig + }); + }, + [initData] + ); const onCloseSlider = useCallback( - (data: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => { + (data: InitProps) => { setSelectedHistoryId(undefined); initData(data); onClose(); }, - [appDetail] + [initData, onClose] ); const { mutate: onRevert, isLoading: isReverting } = useRequest({ @@ -69,8 +78,9 @@ const PublishHistoriesSlider = () => { if (!appId) return; await postRevertVersion(appId, { versionId: data._id, - editNodes: appDetail.modules, // old workflow - editEdges: appDetail.edges + editNodes: defaultData.nodes, // old workflow + editEdges: defaultData.edges, + editChatConfig: defaultData.chatConfig }); setAppDetail((state) => ({ @@ -90,8 +100,9 @@ const PublishHistoriesSlider = () => { onCloseSlider({ - nodes: appDetail.modules, - edges: appDetail.edges + nodes: defaultData.nodes, + edges: defaultData.edges, + chatConfig: defaultData.chatConfig }) } iconSrc="core/workflow/versionHistories" @@ -99,7 +110,7 @@ const PublishHistoriesSlider = () => { maxW={'300px'} px={0} showMask={false} - mt={'60px'} + top={'60px'} overflow={'unset'} > {list.map((data, index) => { diff --git a/projects/app/src/pages/app/detail/components/RouteTab.tsx b/projects/app/src/pages/app/detail/components/RouteTab.tsx new file mode 100644 index 000000000..332d06ed8 --- /dev/null +++ b/projects/app/src/pages/app/detail/components/RouteTab.tsx @@ -0,0 +1,75 @@ +import { Box, HStack } from '@chakra-ui/react'; +import React, { useCallback, useMemo } from 'react'; +import { AppContext, TabEnum } from './context'; +import { useRouter } from 'next/router'; +import { useTranslation } from 'next-i18next'; +import { useI18n } from '@/web/context/I18n'; +import { useContextSelector } from 'use-context-selector'; +import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; + +const RouteTab = () => { + const { t } = useTranslation(); + const { appT } = useI18n(); + const router = useRouter(); + const { appDetail, currentTab } = useContextSelector(AppContext, (v) => v); + + const setCurrentTab = useCallback( + (tab: TabEnum) => { + router.push({ + query: { + ...router.query, + currentTab: tab + } + }); + }, + [router] + ); + + const tabList = useMemo( + () => [ + { + label: appDetail.type === AppTypeEnum.plugin ? appT('Setting plugin') : appT('Setting app'), + id: TabEnum.appEdit + }, + ...(appDetail.permission.hasManagePer + ? [ + { + label: appT('Publish channel'), + id: TabEnum.publish + }, + { label: appT('Chat logs'), id: TabEnum.logs } + ] + : []) + ], + [appDetail.permission.hasManagePer, appT] + ); + + return ( + + {tabList.map((tab) => ( + setCurrentTab(tab.id) + })} + > + {tab.label} + + ))} + + ); +}; + +export default RouteTab; diff --git a/projects/app/src/pages/app/detail/components/SimpleApp/AppCard.tsx b/projects/app/src/pages/app/detail/components/SimpleApp/AppCard.tsx new file mode 100644 index 000000000..4070479e6 --- /dev/null +++ b/projects/app/src/pages/app/detail/components/SimpleApp/AppCard.tsx @@ -0,0 +1,187 @@ +import React, { useState } from 'react'; +import { + Box, + Flex, + Button, + IconButton, + HStack, + Modal, + ModalBody, + Checkbox, + ModalFooter +} from '@chakra-ui/react'; +import { DragHandleIcon } from '@chakra-ui/icons'; +import { useRouter } from 'next/router'; +import { AppSchema } from '@fastgpt/global/core/app/type.d'; +import { useTranslation } from 'next-i18next'; +import Avatar from '@/components/Avatar'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import TagsEditModal from '../TagsEditModal'; +import { useSystemStore } from '@/web/common/system/useSystemStore'; +import { AppContext } from '@/pages/app/detail/components/context'; +import { useContextSelector } from 'use-context-selector'; +import PermissionIconText from '@/components/support/permission/IconText'; +import MyTag from '@fastgpt/web/components/common/Tag/index'; +import MyMenu from '@fastgpt/web/components/common/MyMenu'; +import { useI18n } from '@/web/context/I18n'; +import MyModal from '@fastgpt/web/components/common/MyModal'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { postTransition2Workflow } from '@/web/core/app/api/app'; +import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; + +const AppCard = () => { + const router = useRouter(); + const { t } = useTranslation(); + const { appT } = useI18n(); + + const { appDetail, setAppDetail, onOpenInfoEdit, onDelApp } = useContextSelector( + AppContext, + (v) => v + ); + const appId = appDetail._id; + const { feConfigs } = useSystemStore(); + const [TeamTagsSet, setTeamTagsSet] = useState(); + + // transition to workflow + const [transitionCreateNew, setTransitionCreateNew] = useState(); + const { runAsync: onTransition, loading: transiting } = useRequest2( + () => postTransition2Workflow({ appId, createNew: transitionCreateNew }), + { + onSuccess: ({ id }) => { + if (id) { + router.replace({ + query: { + appId: id + } + }); + } else { + setAppDetail((state) => ({ + ...state, + type: AppTypeEnum.workflow + })); + } + }, + successToast: t('common.Success') + } + ); + + return ( + <> + {/* basic info */} + + + + + {appDetail.name} + + + + {appDetail.intro || t('core.app.tip.Add a intro to app')} + + + + {appDetail.permission.hasWritePer && feConfigs?.show_team_chat && ( + + )} + {appDetail.permission.hasManagePer && ( + + )} + {appDetail.permission.isOwner && ( + } + aria-label={''} + /> + } + menuList={[ + { + children: [ + { + icon: 'core/app/type/workflow', + label: appT('Transition to workflow'), + onClick: () => setTransitionCreateNew(true) + } + ] + }, + { + children: [ + { + icon: 'delete', + type: 'danger', + label: t('common.Delete'), + onClick: onDelApp + } + ] + } + ]} + /> + )} + + (appDetail.permission.hasManagePer ? onOpenInfoEdit() : undefined)} + > + + + + + {TeamTagsSet && setTeamTagsSet(undefined)} />} + {transitionCreateNew !== undefined && ( + + + {appT('Transition to workflow create new tip')} + setTransitionCreateNew((state) => !state)}> + + {appT('Transition to workflow create new placeholder')} + + + + + + + + )} + + ); +}; + +export default React.memo(AppCard); diff --git a/projects/app/src/pages/app/detail/components/SimpleApp/ChatTest.tsx b/projects/app/src/pages/app/detail/components/SimpleApp/ChatTest.tsx new file mode 100644 index 000000000..fc817ed92 --- /dev/null +++ b/projects/app/src/pages/app/detail/components/SimpleApp/ChatTest.tsx @@ -0,0 +1,63 @@ +import { Box, Flex, IconButton } from '@chakra-ui/react'; +import { useTranslation } from 'next-i18next'; +import React, { useEffect } from 'react'; +import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; +import MyIcon from '@fastgpt/web/components/common/Icon'; + +import { useSafeState } from 'ahooks'; +import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type'; +import { form2AppWorkflow } from '@/web/core/app/utils'; +import { useI18n } from '@/web/context/I18n'; +import { useContextSelector } from 'use-context-selector'; +import { AppContext } from '../context'; +import { useChatTest } from '../useChatTest'; + +const ChatTest = ({ appForm }: { appForm: AppSimpleEditFormType }) => { + const { t } = useTranslation(); + const { appT } = useI18n(); + + const { appDetail } = useContextSelector(AppContext, (v) => v); + + const [workflowData, setWorkflowData] = useSafeState({ + nodes: appDetail.modules || [], + edges: appDetail.edges || [] + }); + useEffect(() => { + const { nodes, edges } = form2AppWorkflow(appForm); + setWorkflowData({ nodes, edges }); + }, [appForm, setWorkflowData]); + + const { resetChatBox, ChatBox } = useChatTest({ + ...workflowData, + chatConfig: appForm.chatConfig + }); + + return ( + + + + {appT('Chat Debug')} + + + } + variant={'whiteDanger'} + borderRadius={'md'} + aria-label={'delete'} + onClick={(e) => { + e.stopPropagation(); + resetChatBox(); + }} + /> + + + + + + + ); +}; + +export default React.memo(ChatTest); diff --git a/projects/app/src/pages/app/detail/components/SimpleApp/Edit.tsx b/projects/app/src/pages/app/detail/components/SimpleApp/Edit.tsx new file mode 100644 index 000000000..46df21ebd --- /dev/null +++ b/projects/app/src/pages/app/detail/components/SimpleApp/Edit.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import { Box } from '@chakra-ui/react'; +import { useSystemStore } from '@/web/common/system/useSystemStore'; +import { useMount } from 'ahooks'; +import { useDatasetStore } from '@/web/core/dataset/store/dataset'; +import { appWorkflow2Form } from '@fastgpt/global/core/app/utils'; + +import ChatTest from './ChatTest'; +import AppCard from './AppCard'; +import EditForm from './EditForm'; +import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type'; +import { v1Workflow2V2 } from '@/web/core/workflow/adapt'; +import { AppContext } from '@/pages/app/detail/components/context'; +import { useContextSelector } from 'use-context-selector'; +import { cardStyles } from '../constants'; + +import styles from './styles.module.scss'; + +const Edit = ({ + appForm, + setAppForm +}: { + appForm: AppSimpleEditFormType; + setAppForm: React.Dispatch>; +}) => { + const { isPc } = useSystemStore(); + const { loadAllDatasets } = useDatasetStore(); + const { appDetail } = useContextSelector(AppContext, (v) => v); + + // show selected dataset + useMount(() => { + loadAllDatasets(); + + setAppForm( + appWorkflow2Form({ + nodes: appDetail.modules, + chatConfig: appDetail.chatConfig + }) + ); + + if (appDetail.version !== 'v2') { + setAppForm( + appWorkflow2Form({ + nodes: v1Workflow2V2((appDetail.modules || []) as any)?.nodes, + chatConfig: appDetail.chatConfig + }) + ); + } + }); + + return ( + + + + + + + + + + + {isPc && ( + + + + )} + + ); +}; + +export default React.memo(Edit); diff --git a/projects/app/src/pages/app/detail/components/SimpleApp/EditForm.tsx b/projects/app/src/pages/app/detail/components/SimpleApp/EditForm.tsx new file mode 100644 index 000000000..66b0ea283 --- /dev/null +++ b/projects/app/src/pages/app/detail/components/SimpleApp/EditForm.tsx @@ -0,0 +1,480 @@ +import React, { useEffect, useMemo, useTransition } from 'react'; +import { + Box, + Flex, + Grid, + BoxProps, + useTheme, + useDisclosure, + Button, + HStack +} from '@chakra-ui/react'; +import { AddIcon, SmallAddIcon } from '@chakra-ui/icons'; +import { useFieldArray, UseFormReturn } from 'react-hook-form'; +import { useSystemStore } from '@/web/common/system/useSystemStore'; +import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d'; +import { useRouter } from 'next/router'; +import { useTranslation } from 'next-i18next'; +import { useDatasetStore } from '@/web/core/dataset/store/dataset'; + +import dynamic from 'next/dynamic'; +import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; +import Avatar from '@/components/Avatar'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import VariableEdit from '@/components/core/app/VariableEdit'; +import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor'; +import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils'; +import SearchParamsTip from '@/components/core/dataset/SearchParamsTip'; +import SettingLLMModel from '@/components/core/ai/SettingLLMModel'; +import type { SettingAIDataType } from '@fastgpt/global/core/app/type.d'; +import DeleteIcon, { hoverDeleteStyles } from '@fastgpt/web/components/common/Icon/delete'; +import { TTSTypeEnum } from '@/web/core/app/constants'; +import { getSystemVariables } from '@/web/core/app/utils'; +import { useUpdate } from 'ahooks'; +import { useI18n } from '@/web/context/I18n'; +import { useContextSelector } from 'use-context-selector'; +import { AppContext } from '@/pages/app/detail/components/context'; +import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; +import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; + +const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal')); +const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal')); +const ToolSelectModal = dynamic(() => import('./components/ToolSelectModal')); +const TTSSelect = dynamic(() => import('@/components/core/app/TTSSelect')); +const QGSwitch = dynamic(() => import('@/components/core/app/QGSwitch')); +const WhisperConfig = dynamic(() => import('@/components/core/app/WhisperConfig')); +const InputGuideConfig = dynamic(() => import('@/components/core/app/InputGuideConfig')); +const ScheduledTriggerConfig = dynamic( + () => import('@/components/core/app/ScheduledTriggerConfig') +); +const WelcomeTextConfig = dynamic(() => import('@/components/core/app/WelcomeTextConfig')); + +const BoxStyles: BoxProps = { + px: 5, + py: '16px', + borderBottomWidth: '1px', + borderBottomColor: 'borderColor.low' +}; +const LabelStyles: BoxProps = { + w: ['60px', '100px'], + flexShrink: 0, + fontSize: 'xs' +}; + +const EditForm = ({ + appForm, + setAppForm +}: { + appForm: AppSimpleEditFormType; + setAppForm: React.Dispatch>; +}) => { + const theme = useTheme(); + const router = useRouter(); + const { t } = useTranslation(); + const { appT } = useI18n(); + + const { appDetail } = useContextSelector(AppContext, (v) => v); + + const { allDatasets } = useDatasetStore(); + const { llmModelList } = useSystemStore(); + const [, startTst] = useTransition(); + + const selectDatasets = useMemo( + () => + allDatasets.filter((item) => + appForm.dataset?.datasets.find((dataset) => dataset.datasetId === item._id) + ), + [allDatasets, appForm?.dataset?.datasets] + ); + + const { + isOpen: isOpenDatasetSelect, + onOpen: onOpenKbSelect, + onClose: onCloseKbSelect + } = useDisclosure(); + const { + isOpen: isOpenDatasetParams, + onOpen: onOpenDatasetParams, + onClose: onCloseDatasetParams + } = useDisclosure(); + const { + isOpen: isOpenToolsSelect, + onOpen: onOpenToolsSelect, + onClose: onCloseToolsSelect + } = useDisclosure(); + + const formatVariables: any = useMemo( + () => + formatEditorVariablePickerIcon([ + ...getSystemVariables(t), + ...(appForm.chatConfig.variables || []) + ]), + [appForm.chatConfig.variables, t] + ); + + const tokenLimit = useMemo(() => { + return ( + llmModelList.find((item) => item.model === appForm.aiSettings.model)?.quoteMaxToken || 3000 + ); + }, [llmModelList, appForm.aiSettings.model]); + + return ( + <> + + {/* ai */} + + + + + {appT('AI Settings')} + + + + {t('core.ai.Model')} + + { + setAppForm((state) => ({ + ...state, + aiSettings: { + ...state.aiSettings, + model, + temperature, + maxToken, + maxHistories: maxHistories ?? 6 + } + })); + }} + /> + + + + + + {t('core.ai.Prompt')} + + + + { + startTst(() => { + setAppForm((state) => ({ + ...state, + aiSettings: { + ...state.aiSettings, + systemPrompt: text + } + })); + }); + }} + variables={formatVariables} + placeholder={t('core.app.tip.chatNodeSystemPromptTip')} + title={t('core.ai.Prompt')} + /> + + + + + {/* dataset */} + + + + + {t('core.dataset.Choose Dataset')} + + + + + {appForm.dataset.datasets?.length > 0 && ( + + + + )} + + {selectDatasets.map((item) => ( + + + router.push({ + pathname: '/dataset/detail', + query: { + datasetId: item._id + } + }) + } + > + + + {item.name} + + + + ))} + + + + {/* tool choice */} + + + + + {t('core.app.Tool call')}(实验功能) + + + + + 0 ? 2 : 0} + gridTemplateColumns={'repeat(2, minmax(0, 1fr))'} + gridGap={[2, 4]} + > + {appForm.selectedTools.map((item) => ( + + + + + {item.name} + + { + setAppForm((state) => ({ + ...state, + selectedTools: state.selectedTools.filter((tool) => tool.id !== item.id) + })); + }} + /> + + + ))} + + + + {/* variable */} + + { + appForm.chatConfig.variables = e; + }} + /> + + + {/* welcome */} + + { + setAppForm((state) => ({ + ...state, + chatConfig: { + ...state.chatConfig, + welcomeText: e.target.value + } + })); + }} + /> + + + {/* tts */} + + { + setAppForm((state) => ({ + ...state, + chatConfig: { + ...state.chatConfig, + ttsConfig: e + } + })); + }} + /> + + + {/* whisper */} + + { + setAppForm((state) => ({ + ...state, + chatConfig: { + ...state.chatConfig, + whisperConfig: e + } + })); + }} + /> + + + {/* question guide */} + + { + setAppForm((state) => ({ + ...state, + chatConfig: { + ...state.chatConfig, + questionGuide: e.target.checked + } + })); + }} + /> + + + {/* question tips */} + + { + setAppForm((state) => ({ + ...state, + chatConfig: { + ...state.chatConfig, + chatInputGuide: e + } + })); + }} + /> + + + {/* timer trigger */} + + { + setAppForm((state) => ({ + ...state, + chatConfig: { + ...state.chatConfig, + scheduledTriggerConfig: e + } + })); + }} + /> + + + + {isOpenDatasetSelect && ( + ({ + datasetId: item._id, + vectorModel: item.vectorModel + }))} + onClose={onCloseKbSelect} + onChange={(e) => { + setAppForm((state) => ({ + ...state, + dataset: { + ...state.dataset, + datasets: e + } + })); + }} + /> + )} + {isOpenDatasetParams && ( + { + setAppForm((state) => ({ + ...state, + dataset: { + ...state.dataset, + ...e + } + })); + }} + /> + )} + {isOpenToolsSelect && ( + { + setAppForm((state) => ({ + ...state, + selectedTools: [...state.selectedTools, e] + })); + }} + onRemoveTool={(e) => { + setAppForm((state) => ({ + ...state, + selectedTools: state.selectedTools.filter((item) => item.id !== e.id) + })); + }} + onClose={onCloseToolsSelect} + /> + )} + + ); +}; + +export default React.memo(EditForm); diff --git a/projects/app/src/pages/app/detail/components/SimpleApp/Header.tsx b/projects/app/src/pages/app/detail/components/SimpleApp/Header.tsx new file mode 100644 index 000000000..6f2c29db9 --- /dev/null +++ b/projects/app/src/pages/app/detail/components/SimpleApp/Header.tsx @@ -0,0 +1,164 @@ +import React, { useCallback, useMemo, useState } from 'react'; +import { useContextSelector } from 'use-context-selector'; +import { AppContext } from '../context'; +import FolderPath from '@/components/common/folder/Path'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { getAppFolderPath } from '@/web/core/app/api/app'; +import { Box, Button, Flex, IconButton } from '@chakra-ui/react'; +import { useRouter } from 'next/router'; +import RouteTab from '../RouteTab'; +import { useTranslation } from 'next-i18next'; +import PopoverConfirm from '@fastgpt/web/components/common/MyPopover/PopoverConfirm'; +import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type'; +import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; +import { form2AppWorkflow } from '@/web/core/app/utils'; +import { useSystemStore } from '@/web/common/system/useSystemStore'; +import { TabEnum } from '../context'; +import PublishHistoriesSlider, { type InitProps } from '../PublishHistoriesSlider'; +import { appWorkflow2Form } from '@fastgpt/global/core/app/utils'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import { compareWorkflow } from '@/web/core/workflow/utils'; +import MyTag from '@fastgpt/web/components/common/Tag/index'; +import { publishStatusStyle } from '../constants'; + +const Header = ({ + appForm, + setAppForm +}: { + appForm: AppSimpleEditFormType; + setAppForm: React.Dispatch>; +}) => { + const { t } = useTranslation(); + const { isPc } = useSystemStore(); + const router = useRouter(); + const { appId, appDetail, onPublish, currentTab } = useContextSelector(AppContext, (v) => v); + + const { data: paths = [] } = useRequest2(() => getAppFolderPath(appId), { + manual: false, + refreshDeps: [appId] + }); + const onclickRoute = useCallback( + (parentId: string) => { + router.push({ + pathname: '/app/list', + query: { + parentId + } + }); + }, + [router] + ); + + const isPublished = useMemo(() => { + const data = form2AppWorkflow(appForm); + + return compareWorkflow( + { + nodes: appDetail.modules, + edges: [], + chatConfig: appDetail.chatConfig + }, + { + nodes: data.nodes, + edges: [], + chatConfig: data.chatConfig + } + ); + }, [appDetail.chatConfig, appDetail.modules, appForm]); + + const onSubmitPublish = useCallback( + async (data: AppSimpleEditFormType) => { + const { nodes, edges } = form2AppWorkflow(data); + await onPublish({ + nodes, + edges, + chatConfig: data.chatConfig, + type: AppTypeEnum.simple + }); + }, + [onPublish] + ); + + const [historiesDefaultData, setHistoriesDefaultData] = useState(); + + return ( + + {!isPc && ( + + + + )} + + + + + {isPc && ( + + + + )} + {currentTab === TabEnum.appEdit && ( + + {!historiesDefaultData && ( + <> + + {isPublished + ? publishStatusStyle.published.text + : publishStatusStyle.unPublish.text} + + } + aria-label={''} + size={'sm'} + w={'30px'} + variant={'whitePrimary'} + onClick={() => { + const { nodes, edges } = form2AppWorkflow(appForm); + setHistoriesDefaultData({ + nodes, + edges, + chatConfig: appForm.chatConfig + }); + }} + /> + {t('core.app.Publish')}} + onConfirm={() => onSubmitPublish(appForm)} + /> + + )} + + )} + + + {!!historiesDefaultData && ( + { + setAppForm( + appWorkflow2Form({ + nodes, + chatConfig + }) + ); + }} + onClose={() => setHistoriesDefaultData(undefined)} + defaultData={historiesDefaultData} + /> + )} + + ); +}; + +export default Header; diff --git a/projects/app/src/pages/app/detail/components/SimpleEdit/ToolSelectModal.tsx b/projects/app/src/pages/app/detail/components/SimpleApp/components/ToolSelectModal.tsx similarity index 80% rename from projects/app/src/pages/app/detail/components/SimpleEdit/ToolSelectModal.tsx rename to projects/app/src/pages/app/detail/components/SimpleApp/components/ToolSelectModal.tsx index 61edf9e6a..aaabfd3d0 100644 --- a/projects/app/src/pages/app/detail/components/SimpleEdit/ToolSelectModal.tsx +++ b/projects/app/src/pages/app/detail/components/SimpleApp/components/ToolSelectModal.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import MyModal from '@fastgpt/web/components/common/MyModal'; import { useTranslation } from 'next-i18next'; @@ -20,24 +20,25 @@ import { Textarea } from '@chakra-ui/react'; import RowTabs from '@fastgpt/web/components/common/Tabs/RowTabs'; -import { useWorkflowStore } from '@/web/core/workflow/store/workflow'; -import { useRequest } from '@fastgpt/web/hooks/useRequest'; -import { useQuery } from '@tanstack/react-query'; +import { useRequest, useRequest2 } from '@fastgpt/web/hooks/useRequest'; import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/index.d'; import Avatar from '@/components/Avatar'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { AddIcon } from '@chakra-ui/icons'; -import { getPreviewPluginModule } from '@/web/core/plugin/api'; +import { getPreviewPluginNode, getSystemPlugTemplates } from '@/web/core/app/api/plugin'; import MyBox from '@fastgpt/web/components/common/MyBox'; import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; -import ParentPaths from '@/components/common/ParentPaths'; import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants'; -import { debounce } from 'lodash'; import { useForm } from 'react-hook-form'; import JsonEditor from '@fastgpt/web/components/common/Textarea/JsonEditor'; import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; +import { getTeamPlugTemplates } from '@/web/core/app/api/plugin'; +import { ParentIdType } from '@fastgpt/global/common/parentFolder/type'; +import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; +import { getAppFolderPath } from '@/web/core/app/api/app'; +import FolderPath from '@/components/common/folder/Path'; type Props = { selectedTools: FlowNodeTemplateType[]; @@ -52,55 +53,38 @@ enum TemplateTypeEnum { const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void }) => { const { t } = useTranslation(); - const { - systemNodeTemplates, - loadSystemNodeTemplates, - teamPluginNodeTemplates, - loadTeamPluginNodeTemplates - } = useWorkflowStore(); const [templateType, setTemplateType] = useState(TemplateTypeEnum.teamPlugin); - const [currentParent, setCurrentParent] = useState<{ - parentId: string; - parentName: string; - }>(); + const [parentId, setParentId] = useState(''); const [searchKey, setSearchKey] = useState(''); - const templates = useMemo(() => { - const map = { - [TemplateTypeEnum.systemPlugin]: systemNodeTemplates.filter( - (item) => item.isTool && item.name.toLowerCase().includes(searchKey.toLowerCase()) - ), - [TemplateTypeEnum.teamPlugin]: teamPluginNodeTemplates.filter((item) => - searchKey ? item.pluginType !== PluginTypeEnum.folder : true - ) - }; - return map[templateType]; - }, [searchKey, systemNodeTemplates, teamPluginNodeTemplates, templateType]); - - const { mutate: onChangeTab } = useRequest({ - mutationFn: async (e: any) => { - const val = e as TemplateTypeEnum; - if (val === TemplateTypeEnum.systemPlugin) { - await loadSystemNodeTemplates(); - } else if (val === TemplateTypeEnum.teamPlugin) { - await loadTeamPluginNodeTemplates({ - parentId: currentParent?.parentId + const { data: templates = [], loading: isLoading } = useRequest2( + async () => { + if (templateType === TemplateTypeEnum.systemPlugin) { + return (await getSystemPlugTemplates()).filter( + (item) => item.isTool && item.name.toLowerCase().includes(searchKey.toLowerCase()) + ); + } else if (templateType === TemplateTypeEnum.teamPlugin) { + return getTeamPlugTemplates({ + parentId, + searchKey, + type: [AppTypeEnum.folder, AppTypeEnum.httpPlugin, AppTypeEnum.plugin] }); } - setTemplateType(val); }, - errorToast: t('core.module.templates.Load plugin error') - }); - - const { isLoading } = useQuery(['teamNodeTemplate', currentParent?.parentId, searchKey], () => - loadTeamPluginNodeTemplates({ - parentId: currentParent?.parentId, - searchKey, - init: true - }) + { + manual: false, + throttleWait: 300, + refreshDeps: [templateType, searchKey, parentId], + errorToast: t('core.module.templates.Load plugin error') + } ); + const { data: paths = [] } = useRequest2(() => getAppFolderPath(parentId), { + manual: false, + refreshDeps: [parentId] + }); + return ( void }) py={'5px'} px={'15px'} value={templateType} - onChange={onChangeTab} + onChange={(e) => setTemplateType(e as TemplateTypeEnum)} /> @@ -138,20 +122,19 @@ const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void }) setSearchKey(e.target.value), 200)} + onChange={(e) => setSearchKey(e.target.value)} /> {/* route components */} - {templateType === TemplateTypeEnum.teamPlugin && !searchKey && currentParent && ( + {templateType === TemplateTypeEnum.teamPlugin && !searchKey && parentId && ( - { - setCurrentParent(undefined); + setParentId(null); }} - fontSize="md" /> )} @@ -159,7 +142,7 @@ const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void }) @@ -175,11 +158,11 @@ const RenderList = React.memo(function RenderList({ isLoadingData, onAddTool, onRemoveTool, - setCurrentParent + setParentId }: Props & { templates: FlowNodeTemplateType[]; isLoadingData: boolean; - setCurrentParent: (e: { parentId: string; parentName: string }) => void; + setParentId: React.Dispatch>; }) { const { t } = useTranslation(); const [configTool, setConfigTool] = useState(); @@ -204,7 +187,7 @@ const RenderList = React.memo(function RenderList({ const { mutate: onClickAdd, isLoading } = useRequest({ mutationFn: async (template: FlowNodeTemplateType) => { - const res = await getPreviewPluginModule(template.id); + const res = await getPreviewPluginNode({ appId: template.id }); if (!checkToolInputValid(res)) { return Promise.reject(t('core.app.ToolCall.This plugin cannot be called as a tool')); @@ -250,7 +233,7 @@ const RenderList = React.memo(function RenderList({ {t(item.name)} {item.intro && ( - + {t(item.intro)} )} @@ -264,12 +247,9 @@ const RenderList = React.memo(function RenderList({ > {t('common.Remove')} - ) : item.pluginType === PluginTypeEnum.folder ? ( - ) : ( diff --git a/projects/app/src/pages/app/detail/components/SimpleApp/index.tsx b/projects/app/src/pages/app/detail/components/SimpleApp/index.tsx new file mode 100644 index 000000000..8246c25ea --- /dev/null +++ b/projects/app/src/pages/app/detail/components/SimpleApp/index.tsx @@ -0,0 +1,34 @@ +import React, { useState } from 'react'; +import { getDefaultAppForm } from '@fastgpt/global/core/app/utils'; + +import Header from './Header'; +import Edit from './Edit'; +import { useContextSelector } from 'use-context-selector'; +import { AppContext, TabEnum } from '../context'; +import dynamic from 'next/dynamic'; +import { Flex } from '@chakra-ui/react'; + +const Logs = dynamic(() => import('../Logs/index')); +const PublishChannel = dynamic(() => import('../Publish')); + +const SimpleEdit = () => { + const { currentTab } = useContextSelector(AppContext, (v) => v); + + const [appForm, setAppForm] = useState(getDefaultAppForm()); + + return ( + +
+ {currentTab === TabEnum.appEdit ? ( + + ) : ( + + {currentTab === TabEnum.publish && } + {currentTab === TabEnum.logs && } + + )} + + ); +}; + +export default React.memo(SimpleEdit); diff --git a/projects/app/src/pages/app/detail/components/SimpleApp/styles.module.scss b/projects/app/src/pages/app/detail/components/SimpleApp/styles.module.scss new file mode 100644 index 000000000..f07d856e4 --- /dev/null +++ b/projects/app/src/pages/app/detail/components/SimpleApp/styles.module.scss @@ -0,0 +1,10 @@ +.EditAppBox { + &::-webkit-scrollbar-thumb { + background: #dfe2ea !important; + transition: background 1s; + } + + &::-webkit-scrollbar-thumb:hover { + background: var(--chakra-colors-gray-300) !important; + } +} diff --git a/projects/app/src/pages/app/detail/components/SimpleEdit/AppCard.tsx b/projects/app/src/pages/app/detail/components/SimpleEdit/AppCard.tsx deleted file mode 100644 index ceaeacc52..000000000 --- a/projects/app/src/pages/app/detail/components/SimpleEdit/AppCard.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import React, { useState } from 'react'; -import { Box, Flex, Button, IconButton, useDisclosure } from '@chakra-ui/react'; -import { DragHandleIcon } from '@chakra-ui/icons'; -import { useRequest } from '@fastgpt/web/hooks/useRequest'; -import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; -import { useRouter } from 'next/router'; -import { useToast } from '@fastgpt/web/hooks/useToast'; -import { AppSchema } from '@fastgpt/global/core/app/type.d'; -import { delAppById } from '@/web/core/app/api'; -import { useTranslation } from 'next-i18next'; -import PermissionIconText from '@/components/support/permission/IconText'; -import dynamic from 'next/dynamic'; -import Avatar from '@/components/Avatar'; -import MyIcon from '@fastgpt/web/components/common/Icon'; -import TagsEditModal from './TagsEditModal'; -import { useSystemStore } from '@/web/common/system/useSystemStore'; -import { useI18n } from '@/web/context/I18n'; -import { AppContext } from '@/web/core/app/context/appContext'; -import { useContextSelector } from 'use-context-selector'; -const InfoModal = dynamic(() => import('../InfoModal')); - -const AppCard = () => { - const router = useRouter(); - const { t } = useTranslation(); - const { appT } = useI18n(); - - const { toast } = useToast(); - const { appDetail } = useContextSelector(AppContext, (v) => v); - const appId = appDetail._id; - const { feConfigs } = useSystemStore(); - const [TeamTagsSet, setTeamTagsSet] = useState(); - - const { - isOpen: isOpenInfoEdit, - onOpen: onOpenInfoEdit, - onClose: onCloseInfoEdit - } = useDisclosure(); - - const { openConfirm: openConfirmDel, ConfirmModal: ConfirmDelModal } = useConfirm({ - content: appT('Confirm Del App Tip'), - type: 'delete' - }); - - /* 点击删除 */ - const { mutate: handleDelModel, isLoading } = useRequest({ - mutationFn: async () => { - if (!appDetail) return null; - await delAppById(appDetail._id); - return 'success'; - }, - onSuccess(res) { - if (!res) return; - toast({ - title: t('common.Delete Success'), - status: 'success' - }); - router.replace(`/app/list`); - }, - errorToast: t('common.Delete Failed') - }); - - return ( - <> - - - - - - - AppId:{' '} - - {appId} - - - - {/* basic info */} - - - - - {appDetail.name} - - {appDetail.permission.isOwner && ( - } - variant={'whiteDanger'} - borderRadius={'md'} - aria-label={'delete'} - isLoading={isLoading} - onClick={openConfirmDel(handleDelModel)} - /> - )} - - - {appDetail.intro || t('core.app.tip.Add a intro to app')} - - - - - {appDetail.permission.hasWritePer && feConfigs?.show_team_chat && ( - - )} - {appDetail.permission.hasManagePer && ( - - )} - - - - - {isOpenInfoEdit && } - {TeamTagsSet && setTeamTagsSet(undefined)} />} - - ); -}; - -export default React.memo(AppCard); diff --git a/projects/app/src/pages/app/detail/components/SimpleEdit/ChatTest.tsx b/projects/app/src/pages/app/detail/components/SimpleEdit/ChatTest.tsx deleted file mode 100644 index fc5a413be..000000000 --- a/projects/app/src/pages/app/detail/components/SimpleEdit/ChatTest.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import { useUserStore } from '@/web/support/user/useUserStore'; -import { Box, Flex, IconButton } from '@chakra-ui/react'; -import { useTranslation } from 'next-i18next'; -import React, { useCallback, useEffect, useRef } from 'react'; -import ChatBox from '@/components/ChatBox'; -import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d'; -import { streamFetch } from '@/web/common/api/fetch'; -import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; -import MyIcon from '@fastgpt/web/components/common/Icon'; -import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils'; -import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; -import { - getDefaultEntryNodeIds, - getMaxHistoryLimitFromNodes, - initWorkflowEdgeStatus, - storeNodes2RuntimeNodes -} from '@fastgpt/global/core/workflow/runtime/utils'; -import { useMemoizedFn, useSafeState } from 'ahooks'; -import { UseFormReturn } from 'react-hook-form'; -import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type'; -import { form2AppWorkflow } from '@/web/core/app/utils'; -import { useI18n } from '@/web/context/I18n'; -import { useContextSelector } from 'use-context-selector'; -import { AppContext } from '@/web/core/app/context/appContext'; - -const ChatTest = ({ - editForm, - appId -}: { - editForm: UseFormReturn; - appId: string; -}) => { - const { t } = useTranslation(); - const { appT } = useI18n(); - - const { userInfo } = useUserStore(); - const ChatBoxRef = useRef(null); - const { appDetail } = useContextSelector(AppContext, (v) => v); - - const { watch } = editForm; - const chatConfig = watch('chatConfig'); - - const [workflowData, setWorkflowData] = useSafeState({ - nodes: appDetail.modules || [], - edges: appDetail.edges || [] - }); - - const startChat = useMemoizedFn( - async ({ chatList, controller, generatingMessage, variables }: StartChatFnProps) => { - if (!workflowData) return Promise.reject('workflowData is empty'); - - /* get histories */ - let historyMaxLen = getMaxHistoryLimitFromNodes(workflowData.nodes); - - const history = chatList.slice(-historyMaxLen - 2, -2); - - // 流请求,获取数据 - const { responseText, responseData } = await streamFetch({ - url: '/api/core/chat/chatTest', - data: { - history, - prompt: chatList[chatList.length - 2].value, - nodes: storeNodes2RuntimeNodes( - workflowData.nodes, - getDefaultEntryNodeIds(workflowData.nodes) - ), - edges: initWorkflowEdgeStatus(workflowData.edges), - variables, - appId, - appName: `调试-${appDetail.name}` - }, - onMessage: generatingMessage, - abortCtrl: controller - }); - - return { responseText, responseData }; - } - ); - - const resetChatBox = useCallback(() => { - ChatBoxRef.current?.resetHistory([]); - ChatBoxRef.current?.resetVariables(); - }, []); - - useEffect(() => { - const wat = watch((data) => { - const { nodes, edges } = form2AppWorkflow(data as AppSimpleEditFormType); - setWorkflowData({ nodes, edges }); - }); - - return () => { - wat.unsubscribe(); - }; - }, [setWorkflowData, watch]); - - return ( - - - - {appT('Chat Debug')} - - - } - variant={'whiteDanger'} - borderRadius={'md'} - aria-label={'delete'} - onClick={(e) => { - e.stopPropagation(); - resetChatBox(); - }} - /> - - - - {}} - /> - - {appDetail.type !== AppTypeEnum.simple && ( - - {appT('Advance App TestTip')} - - )} - - ); -}; - -export default React.memo(ChatTest); diff --git a/projects/app/src/pages/app/detail/components/SimpleEdit/EditForm.tsx b/projects/app/src/pages/app/detail/components/SimpleEdit/EditForm.tsx deleted file mode 100644 index 6049ca686..000000000 --- a/projects/app/src/pages/app/detail/components/SimpleEdit/EditForm.tsx +++ /dev/null @@ -1,521 +0,0 @@ -import React, { useEffect, useMemo, useTransition } from 'react'; -import { - Box, - Flex, - Grid, - BoxProps, - useTheme, - useDisclosure, - Button, - HStack -} from '@chakra-ui/react'; -import { AddIcon, SmallAddIcon } from '@chakra-ui/icons'; -import { useFieldArray, UseFormReturn } from 'react-hook-form'; -import { useSystemStore } from '@/web/common/system/useSystemStore'; -import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d'; -import { useRequest } from '@fastgpt/web/hooks/useRequest'; -import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; -import { useRouter } from 'next/router'; -import { useTranslation } from 'next-i18next'; -import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; -import { useDatasetStore } from '@/web/core/dataset/store/dataset'; -import { form2AppWorkflow } from '@/web/core/app/utils'; - -import dynamic from 'next/dynamic'; -import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; -import Avatar from '@/components/Avatar'; -import MyIcon from '@fastgpt/web/components/common/Icon'; -import VariableEdit from '@/components/core/app/VariableEdit'; -import MyTextarea from '@/components/common/Textarea/MyTextarea/index'; -import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor'; -import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils'; -import SearchParamsTip from '@/components/core/dataset/SearchParamsTip'; -import SettingLLMModel from '@/components/core/ai/SettingLLMModel'; -import type { SettingAIDataType } from '@fastgpt/global/core/app/type.d'; -import DeleteIcon, { hoverDeleteStyles } from '@fastgpt/web/components/common/Icon/delete'; -import { TTSTypeEnum } from '@/web/core/app/constants'; -import { getSystemVariables } from '@/web/core/app/utils'; -import { useUpdate } from 'ahooks'; -import { useI18n } from '@/web/context/I18n'; -import { useContextSelector } from 'use-context-selector'; -import { AppContext } from '@/web/core/app/context/appContext'; -import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; -import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; - -const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal')); -const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal')); -const ToolSelectModal = dynamic(() => import('./ToolSelectModal')); -const TTSSelect = dynamic(() => import('@/components/core/app/TTSSelect')); -const QGSwitch = dynamic(() => import('@/components/core/app/QGSwitch')); -const WhisperConfig = dynamic(() => import('@/components/core/app/WhisperConfig')); -const InputGuideConfig = dynamic(() => import('@/components/core/app/InputGuideConfig')); -const ScheduledTriggerConfig = dynamic( - () => import('@/components/core/app/ScheduledTriggerConfig') -); -const WelcomeTextConfig = dynamic(() => import('@/components/core/app/WelcomeTextConfig')); - -const BoxStyles: BoxProps = { - px: 5, - py: '16px', - borderBottomWidth: '1px', - borderBottomColor: 'borderColor.low' -}; -const LabelStyles: BoxProps = { - w: ['60px', '100px'], - flexShrink: 0, - fontSize: 'xs' -}; - -const EditForm = ({ - editForm, - divRef, - isSticky -}: { - editForm: UseFormReturn; - divRef: React.RefObject; - isSticky: boolean; -}) => { - const theme = useTheme(); - const router = useRouter(); - const { t } = useTranslation(); - const { appT } = useI18n(); - - const { appDetail, publishApp } = useContextSelector(AppContext, (v) => v); - - const { allDatasets } = useDatasetStore(); - const { llmModelList } = useSystemStore(); - const [, startTst] = useTransition(); - const refresh = useUpdate(); - - const { setValue, getValues, handleSubmit, control, watch } = editForm; - - const { fields: datasets, replace: replaceDatasetList } = useFieldArray({ - control, - name: 'dataset.datasets' - }); - const selectDatasets = useMemo( - () => allDatasets.filter((item) => datasets.find((dataset) => dataset.datasetId === item._id)), - [allDatasets, datasets] - ); - // useEffect(() => { - // if (selectDatasets.length !== datasets.length) { - // replaceDatasetList( - // selectDatasets.map((item) => ({ - // datasetId: item._id - // })) - // ); - // } - // }, [datasets, replaceDatasetList, selectDatasets]); - - const { - isOpen: isOpenDatasetSelect, - onOpen: onOpenKbSelect, - onClose: onCloseKbSelect - } = useDisclosure(); - const { - isOpen: isOpenDatasetParams, - onOpen: onOpenDatasetParams, - onClose: onCloseDatasetParams - } = useDisclosure(); - const { - isOpen: isOpenToolsSelect, - onOpen: onOpenToolsSelect, - onClose: onCloseToolsSelect - } = useDisclosure(); - - const { openConfirm: openConfirmSave, ConfirmModal: ConfirmSaveModal } = useConfirm({ - content: t('core.app.edit.Confirm Save App Tip') - }); - - const aiSystemPrompt = watch('aiSettings.systemPrompt'); - const selectLLMModel = watch('aiSettings.model'); - const datasetSearchSetting = watch('dataset'); - const variables = watch('chatConfig.variables'); - - const formatVariables: any = useMemo( - () => formatEditorVariablePickerIcon([...getSystemVariables(t), ...(variables || [])]), - [t, variables] - ); - const tts = getValues('chatConfig.ttsConfig'); - const whisperConfig = getValues('chatConfig.whisperConfig'); - const postQuestionGuide = getValues('chatConfig.questionGuide'); - const selectedTools = watch('selectedTools'); - const inputGuideConfig = watch('chatConfig.chatInputGuide'); - const scheduledTriggerConfig = watch('chatConfig.scheduledTriggerConfig'); - const searchMode = watch('dataset.searchMode'); - - const tokenLimit = useMemo(() => { - return llmModelList.find((item) => item.model === selectLLMModel)?.quoteMaxToken || 3000; - }, [selectLLMModel, llmModelList]); - - /* on save app */ - const { mutate: onSubmitPublish, isLoading: isSaving } = useRequest({ - mutationFn: async (data: AppSimpleEditFormType) => { - const { nodes, edges } = form2AppWorkflow(data); - await publishApp({ - nodes, - edges, - chatConfig: data.chatConfig, - type: AppTypeEnum.simple - }); - }, - successToast: t('common.Save Success'), - errorToast: t('common.Save Failed') - }); - - useEffect(() => { - const wat = watch((data) => { - refresh(); - }); - - return () => { - wat.unsubscribe(); - }; - }, []); - - return ( - - {/* title */} - - - {t('core.app.App params config')} - - - - - - - - {/* ai */} - - - - - {appT('AI Settings')} - - - - {t('core.ai.Model')} - - { - setValue('aiSettings.model', model); - setValue('aiSettings.maxToken', maxToken); - setValue('aiSettings.temperature', temperature); - setValue('aiSettings.maxHistories', maxHistories ?? 6); - }} - /> - - - - - - {t('core.ai.Prompt')} - - - - { - startTst(() => { - setValue('aiSettings.systemPrompt', text); - }); - }} - variables={formatVariables} - placeholder={t('core.app.tip.chatNodeSystemPromptTip')} - title={t('core.ai.Prompt')} - /> - - - - - {/* dataset */} - - - - - {t('core.dataset.Choose Dataset')} - - - - - {datasetSearchSetting.datasets?.length > 0 && ( - - - - )} - - {selectDatasets.map((item) => ( - - - router.push({ - pathname: '/dataset/detail', - query: { - datasetId: item._id - } - }) - } - > - - - {item.name} - - - - ))} - - - - {/* tool choice */} - - - - - {t('core.app.Tool call')}(实验功能) - - - - - 0 ? 2 : 0} - gridTemplateColumns={'repeat(2, minmax(0, 1fr))'} - gridGap={[2, 4]} - > - {selectedTools.map((item) => ( - - - - {item.name} - - { - setValue( - 'selectedTools', - selectedTools.filter((tool) => tool.id !== item.id) - ); - }} - /> - - ))} - - - - {/* variable */} - - { - setValue('chatConfig.variables', e); - }} - /> - - - {/* welcome */} - - { - setValue('chatConfig.welcomeText', e.target.value || ''); - }} - /> - - - {/* tts */} - - { - setValue('chatConfig.ttsConfig', e); - }} - /> - - - {/* whisper */} - - { - setValue('chatConfig.whisperConfig', e); - }} - /> - - - {/* question guide */} - - { - setValue('chatConfig.questionGuide', e.target.checked); - }} - /> - - - {/* question tips */} - - { - setValue('chatConfig.chatInputGuide', e); - }} - /> - - - {/* timer trigger */} - - { - setValue('chatConfig.scheduledTriggerConfig', e); - }} - /> - - - - - - {isOpenDatasetSelect && ( - ({ - datasetId: item._id, - vectorModel: item.vectorModel - }))} - onClose={onCloseKbSelect} - onChange={replaceDatasetList} - /> - )} - {isOpenDatasetParams && ( - { - setValue('dataset', { - ...getValues('dataset'), - ...e - }); - }} - /> - )} - {isOpenToolsSelect && ( - { - setValue('selectedTools', [...selectedTools, e]); - }} - onRemoveTool={(e) => { - setValue( - 'selectedTools', - selectedTools.filter((item) => item.pluginId !== e.pluginId) - ); - }} - onClose={onCloseToolsSelect} - /> - )} - - ); -}; - -export default React.memo(EditForm); diff --git a/projects/app/src/pages/app/detail/components/SimpleEdit/index.tsx b/projects/app/src/pages/app/detail/components/SimpleEdit/index.tsx deleted file mode 100644 index bf4a8a6e0..000000000 --- a/projects/app/src/pages/app/detail/components/SimpleEdit/index.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React from 'react'; -import { Box, Grid } from '@chakra-ui/react'; -import { useSystemStore } from '@/web/common/system/useSystemStore'; -import { useSticky } from '@/web/common/hooks/useSticky'; -import { useMount } from 'ahooks'; -import { useDatasetStore } from '@/web/core/dataset/store/dataset'; -import { useForm } from 'react-hook-form'; -import { appWorkflow2Form, getDefaultAppForm } from '@fastgpt/global/core/app/utils'; - -import ChatTest from './ChatTest'; -import AppCard from './AppCard'; -import EditForm from './EditForm'; -import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type'; -import { v1Workflow2V2 } from '@/web/core/workflow/adapt'; -import { AppContext } from '@/web/core/app/context/appContext'; -import { useContextSelector } from 'use-context-selector'; - -const SimpleEdit = ({ appId }: { appId: string }) => { - const { isPc } = useSystemStore(); - const { parentRef, divRef, isSticky } = useSticky(); - const { loadAllDatasets } = useDatasetStore(); - const { appDetail } = useContextSelector(AppContext, (v) => v); - - const editForm = useForm({ - defaultValues: getDefaultAppForm() - }); - - // show selected dataset - useMount(() => { - loadAllDatasets(); - editForm.reset( - appWorkflow2Form({ - nodes: appDetail.modules, - chatConfig: appDetail.chatConfig - }) - ); - - if (appDetail.version !== 'v2') { - editForm.reset( - appWorkflow2Form({ - nodes: v1Workflow2V2((appDetail.modules || []) as any)?.nodes, - chatConfig: appDetail.chatConfig - }) - ); - } - }); - - return ( - - - - - - - - - {isPc && } - - ); -}; - -export default React.memo(SimpleEdit); diff --git a/projects/app/src/pages/app/detail/components/SimpleEdit/TagsEditModal.tsx b/projects/app/src/pages/app/detail/components/TagsEditModal.tsx similarity index 98% rename from projects/app/src/pages/app/detail/components/SimpleEdit/TagsEditModal.tsx rename to projects/app/src/pages/app/detail/components/TagsEditModal.tsx index 46c906353..c98ceb93d 100644 --- a/projects/app/src/pages/app/detail/components/SimpleEdit/TagsEditModal.tsx +++ b/projects/app/src/pages/app/detail/components/TagsEditModal.tsx @@ -23,7 +23,7 @@ import { useRequest } from '@fastgpt/web/hooks/useRequest'; import { getTeamsTags } from '@/web/support/user/team/api'; import { useQuery } from '@tanstack/react-query'; import { useContextSelector } from 'use-context-selector'; -import { AppContext } from '@/web/core/app/context/appContext'; +import { AppContext } from '@/pages/app/detail/components/context'; const TagsEditModal = ({ onClose }: { onClose: () => void }) => { const { t } = useTranslation(); diff --git a/projects/app/src/pages/app/detail/components/Workflow/Header.tsx b/projects/app/src/pages/app/detail/components/Workflow/Header.tsx new file mode 100644 index 000000000..378d9ddeb --- /dev/null +++ b/projects/app/src/pages/app/detail/components/Workflow/Header.tsx @@ -0,0 +1,198 @@ +import React, { useCallback, useMemo } from 'react'; +import { Box, Flex, Button, IconButton } from '@chakra-ui/react'; +import { useTranslation } from 'next-i18next'; +import dynamic from 'next/dynamic'; + +import MyIcon from '@fastgpt/web/components/common/Icon'; +import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext, getWorkflowStore } from '../WorkflowComponents/context'; +import { useInterval } from 'ahooks'; +import { AppContext, TabEnum } from '../context'; +import RouteTab from '../RouteTab'; +import { useSystemStore } from '@/web/common/system/useSystemStore'; +import PopoverConfirm from '@fastgpt/web/components/common/MyPopover/PopoverConfirm'; +import { useRouter } from 'next/router'; + +import AppCard from '../WorkflowComponents/AppCard'; +import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils'; +const PublishHistories = dynamic(() => import('../PublishHistoriesSlider')); + +const Header = () => { + const { t } = useTranslation(); + const { isPc } = useSystemStore(); + const router = useRouter(); + + const { appDetail, onPublish, currentTab } = useContextSelector(AppContext, (v) => v); + const isV2Workflow = appDetail?.version === 'v2'; + + const { + flowData2StoreDataAndCheck, + setWorkflowTestData, + onSaveWorkflow, + setHistoriesDefaultData, + historiesDefaultData, + initData + } = useContextSelector(WorkflowContext, (v) => v); + + const onclickPublish = useCallback(async () => { + const data = flowData2StoreDataAndCheck(); + if (data) { + await onPublish({ + ...data, + chatConfig: appDetail.chatConfig, + //@ts-ignore + version: 'v2' + }); + } + }, [flowData2StoreDataAndCheck, onPublish, appDetail.chatConfig]); + + const saveAndBack = useCallback(async () => { + try { + await onSaveWorkflow(); + router.push('/app/list'); + } catch (error) {} + }, [onSaveWorkflow, router]); + // effect + useBeforeunload({ + callback: onSaveWorkflow, + tip: t('core.common.tip.leave page') + }); + useInterval(() => { + if (!appDetail._id) return; + onSaveWorkflow(); + }, 40000); + + const Render = useMemo(() => { + return ( + <> + {!isPc && ( + + + + )} + + {/* back */} + + {/* app info */} + + + + + {isPc && ( + + + + )} + + + {currentTab === TabEnum.appEdit && ( + <> + {!historiesDefaultData && ( + } + aria-label={''} + size={'sm'} + w={'30px'} + variant={'whitePrimary'} + onClick={async () => { + const { nodes, edges } = uiWorkflow2StoreWorkflow(await getWorkflowStore()); + + setHistoriesDefaultData({ + nodes, + edges, + chatConfig: appDetail.chatConfig + }); + }} + /> + )} + + + {!historiesDefaultData && ( + } + > + {t('core.app.Publish')} + + } + onConfirm={() => onclickPublish()} + /> + )} + + )} + + {historiesDefaultData && ( + { + setHistoriesDefaultData(undefined); + }} + defaultData={historiesDefaultData} + /> + )} + + ); + }, [ + isPc, + currentTab, + saveAndBack, + historiesDefaultData, + isV2Workflow, + t, + initData, + setHistoriesDefaultData, + appDetail.chatConfig, + flowData2StoreDataAndCheck, + setWorkflowTestData, + onclickPublish + ]); + + return Render; +}; + +export default React.memo(Header); diff --git a/projects/app/src/pages/app/detail/components/FlowEdit/index.tsx b/projects/app/src/pages/app/detail/components/Workflow/index.tsx similarity index 52% rename from projects/app/src/pages/app/detail/components/FlowEdit/index.tsx rename to projects/app/src/pages/app/detail/components/Workflow/index.tsx index 84bba24b7..b075384f5 100644 --- a/projects/app/src/pages/app/detail/components/FlowEdit/index.tsx +++ b/projects/app/src/pages/app/detail/components/Workflow/index.tsx @@ -1,21 +1,25 @@ -import React, { useEffect, useMemo } from 'react'; -import { AppSchema } from '@fastgpt/global/core/app/type.d'; -import Header from './Header'; -import Flow from '@/components/core/workflow/Flow'; +import React from 'react'; import { appSystemModuleTemplates } from '@fastgpt/global/core/workflow/template/constants'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { v1Workflow2V2 } from '@/web/core/workflow/adapt'; -import WorkflowContextProvider, { WorkflowContext } from '@/components/core/workflow/context'; +import WorkflowContextProvider, { WorkflowContext } from '../WorkflowComponents/context'; import { useContextSelector } from 'use-context-selector'; -import { AppContext } from '@/web/core/app/context/appContext'; +import { AppContext, TabEnum } from '../context'; import { useMount } from 'ahooks'; +import Header from './Header'; +import { Flex } from '@chakra-ui/react'; +import { workflowBoxStyles } from '../constants'; +import dynamic from 'next/dynamic'; +import { cloneDeep } from 'lodash'; -type Props = { onClose: () => void }; - -const Render = ({ onClose }: Props) => { - const appDetail = useContextSelector(AppContext, (e) => e.appDetail); +import Flow from '../WorkflowComponents/Flow'; +const Logs = dynamic(() => import('../Logs/index')); +const PublishChannel = dynamic(() => import('../Publish')); +const WorkflowEdit = () => { + const { appDetail, currentTab } = useContextSelector(AppContext, (e) => e); const isV2Workflow = appDetail?.version === 'v2'; + const { openConfirm, ConfirmModal } = useConfirm({ showCancel: false, content: @@ -24,47 +28,45 @@ const Render = ({ onClose }: Props) => { const initData = useContextSelector(WorkflowContext, (v) => v.initData); - const workflowStringData = JSON.stringify({ - nodes: appDetail.modules || [], - edges: appDetail.edges || [] - }); - useMount(() => { if (!isV2Workflow) { openConfirm(() => { initData(JSON.parse(JSON.stringify(v1Workflow2V2((appDetail.modules || []) as any)))); })(); } else { - initData(JSON.parse(workflowStringData)); + initData( + cloneDeep({ + nodes: appDetail.modules || [], + edges: appDetail.edges || [] + }) + ); } }); - const memoRender = useMemo(() => { - return } />; - }, [onClose]); - return ( - <> - {memoRender} + +
+ + {currentTab === TabEnum.appEdit ? ( + + ) : ( + + {currentTab === TabEnum.publish && } + {currentTab === TabEnum.logs && } + + )} + {!isV2Workflow && } - + ); }; -export default React.memo(function FlowEdit(props: Props) { - const appDetail = useContextSelector(AppContext, (e) => e.appDetail); - const filterAppIds = useMemo(() => [appDetail._id], [appDetail._id]); - +const Render = () => { return ( - - + + ); -}); +}; + +export default React.memo(Render); diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/AppCard.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/AppCard.tsx new file mode 100644 index 000000000..6aebfd80e --- /dev/null +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/AppCard.tsx @@ -0,0 +1,222 @@ +import React, { useCallback, useMemo } from 'react'; +import { Box, Flex, HStack, useDisclosure } from '@chakra-ui/react'; +import { useContextSelector } from 'use-context-selector'; +import { AppContext, TabEnum } from '../context'; +import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; +import { useTranslation } from 'next-i18next'; +import Avatar from '@/components/Avatar'; +import MyMenu from '@fastgpt/web/components/common/MyMenu'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import { useI18n } from '@/web/context/I18n'; +import { WorkflowContext } from './context'; +import { compareWorkflow, filterSensitiveNodesData } from '@/web/core/workflow/utils'; +import dynamic from 'next/dynamic'; +import { useCopyData } from '@/web/common/hooks/useCopyData'; +import { useSystemStore } from '@/web/common/system/useSystemStore'; +import MyTag from '@fastgpt/web/components/common/Tag/index'; +import { publishStatusStyle } from '../constants'; + +const ImportSettings = dynamic(() => import('./Flow/ImportSettings')); + +const AppCard = ({ showSaveStatus }: { showSaveStatus: boolean }) => { + const { t } = useTranslation(); + const { appT } = useI18n(); + const { copyData } = useCopyData(); + const { feConfigs } = useSystemStore(); + + const { appDetail, appLatestVersion, onOpenInfoEdit, onOpenTeamTagModal, onDelApp, currentTab } = + useContextSelector(AppContext, (v) => v); + const { historiesDefaultData, flowData2StoreDataAndCheck, onSaveWorkflow, isSaving, saveLabel } = + useContextSelector(WorkflowContext, (v) => v); + + const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure(); + const onExportWorkflow = useCallback(async () => { + const data = flowData2StoreDataAndCheck(); + if (data) { + copyData( + JSON.stringify( + { + nodes: filterSensitiveNodesData(data.nodes), + edges: data.edges, + chatConfig: appDetail.chatConfig + }, + null, + 2 + ), + appT('Export Config Successful') + ); + } + }, [appDetail.chatConfig, appT, copyData, flowData2StoreDataAndCheck]); + + const isPublished = (() => { + const data = flowData2StoreDataAndCheck(true); + if (!appLatestVersion) return true; + + if (data) { + return compareWorkflow( + { + nodes: appLatestVersion.nodes, + edges: appLatestVersion.edges, + chatConfig: appLatestVersion.chatConfig + }, + { + nodes: data.nodes, + edges: data.edges, + chatConfig: appDetail.chatConfig + } + ); + } + return false; + })(); + + const InfoMenu = useCallback( + ({ children }: { children: React.ReactNode }) => { + return ( + + ); + }, + [ + appDetail.permission.hasWritePer, + appDetail.permission.isOwner, + appT, + currentTab, + feConfigs?.show_team_chat, + historiesDefaultData, + onDelApp, + onExportWorkflow, + onOpenImport, + onOpenInfoEdit, + onOpenTeamTagModal, + t + ] + ); + + const Render = useMemo(() => { + return ( + + + + + + + + {appDetail.name} + + + + {showSaveStatus && ( + + + {isSaving && } + {saveLabel} + + {isPublished + ? publishStatusStyle.published.text + : publishStatusStyle.unPublish.text} + + + + )} + + + {isOpenImport && } + + ); + }, [ + InfoMenu, + appDetail.avatar, + appDetail.name, + isOpenImport, + isPublished, + isSaving, + onCloseImport, + onSaveWorkflow, + saveLabel, + showSaveStatus, + t + ]); + + return Render; +}; + +export default AppCard; diff --git a/projects/app/src/components/core/workflow/Flow/ChatTest.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/ChatTest.tsx similarity index 54% rename from projects/app/src/components/core/workflow/Flow/ChatTest.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/ChatTest.tsx index 7b654d5a1..8c9c80824 100644 --- a/projects/app/src/components/core/workflow/Flow/ChatTest.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/ChatTest.tsx @@ -1,36 +1,17 @@ import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d'; -import { AppSchema } from '@fastgpt/global/core/app/type.d'; -import React, { - useMemo, - useCallback, - useRef, - forwardRef, - useImperativeHandle, - ForwardedRef -} from 'react'; +import React, { useRef, forwardRef, ForwardedRef, useImperativeHandle } from 'react'; import { SmallCloseIcon } from '@chakra-ui/icons'; import { Box, Flex, IconButton } from '@chakra-ui/react'; import MyIcon from '@fastgpt/web/components/common/Icon'; -import { streamFetch } from '@/web/common/api/fetch'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import { useUserStore } from '@/web/support/user/useUserStore'; -import ChatBox from '@/components/ChatBox'; -import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d'; -import { - checkChatSupportSelectFileByModules, - getAppQuestionGuidesByModules -} from '@/web/core/chat/utils'; -import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; +import type { ComponentRef } from '@/components/ChatBox/type.d'; import { useTranslation } from 'next-i18next'; import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; -import { - getDefaultEntryNodeIds, - getMaxHistoryLimitFromNodes, - initWorkflowEdgeStatus, - storeNodes2RuntimeNodes -} from '@fastgpt/global/core/workflow/runtime/utils'; + import { useContextSelector } from 'use-context-selector'; -import { AppContext } from '@/web/core/app/context/appContext'; +import { AppContext } from '@/pages/app/detail/components/context'; +import { useChatTest } from '@/pages/app/detail/components/useChatTest'; export type ChatTestComponentRef = { resetChatTest: () => void; @@ -52,48 +33,32 @@ const ChatTest = ( ) => { const { t } = useTranslation(); const ChatBoxRef = useRef(null); - const { userInfo } = useUserStore(); const { appDetail } = useContextSelector(AppContext, (v) => v); - const startChat = useCallback( - async ({ chatList, controller, generatingMessage, variables }: StartChatFnProps) => { - /* get histories */ - let historyMaxLen = getMaxHistoryLimitFromNodes(nodes); - const history = chatList.slice(-historyMaxLen - 2, -2); - - // 流请求,获取数据 - const { responseText, responseData } = await streamFetch({ - url: '/api/core/chat/chatTest', - data: { - history, - prompt: chatList[chatList.length - 2].value, - nodes: storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes)), - edges: initWorkflowEdgeStatus(edges), - variables, - appId: appDetail._id, - appName: `调试-${appDetail.name}`, - mode: 'test' - }, - onMessage: generatingMessage, - abortCtrl: controller - }); - - return { responseText, responseData }; - }, - [appDetail._id, appDetail.name, edges, nodes] - ); + const { resetChatBox, ChatBox } = useChatTest({ + nodes, + edges, + chatConfig: appDetail.chatConfig + }); useImperativeHandle(ref, () => ({ - resetChatTest() { - ChatBoxRef.current?.resetHistory([]); - ChatBoxRef.current?.resetVariables(); - } + resetChatTest: resetChatBox })); return ( <> + } variant={'grayBase'} size={'smSquare'} @@ -137,29 +102,9 @@ const ChatTest = ( - {}} - /> + - ); }; diff --git a/projects/app/src/components/core/workflow/Flow/ImportSettings.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/ImportSettings.tsx similarity index 100% rename from projects/app/src/components/core/workflow/Flow/ImportSettings.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/ImportSettings.tsx diff --git a/projects/app/src/components/core/workflow/Flow/NodeTemplatesModal.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx similarity index 52% rename from projects/app/src/components/core/workflow/Flow/NodeTemplatesModal.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx index 8739f85d0..aff4fff1b 100644 --- a/projects/app/src/components/core/workflow/Flow/NodeTemplatesModal.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx @@ -11,24 +11,24 @@ import { nodeTemplate2FlowNode } from '@/web/core/workflow/utils'; import { useTranslation } from 'next-i18next'; import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; -import { getPreviewPluginModule } from '@/web/core/plugin/api'; +import { getPreviewPluginNode, getSystemPlugTemplates } from '@/web/core/app/api/plugin'; import { useToast } from '@fastgpt/web/hooks/useToast'; import { getErrText } from '@fastgpt/global/common/error/utils'; import { workflowNodeTemplateList } from '@fastgpt/web/core/workflow/constants'; import RowTabs from '@fastgpt/web/components/common/Tabs/RowTabs'; -import { useWorkflowStore } from '@/web/core/workflow/store/workflow'; -import { useRequest } from '@fastgpt/web/hooks/useRequest'; -import ParentPaths from '@/components/common/ParentPaths'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { useRouter } from 'next/router'; -import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants'; -import { useQuery } from '@tanstack/react-query'; -import { debounce } from 'lodash'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import { useContextSelector } from 'use-context-selector'; import { WorkflowContext } from '../context'; -import { useCreation } from 'ahooks'; import { useI18n } from '@/web/context/I18n'; +import { getTeamPlugTemplates } from '@/web/core/app/api/plugin'; +import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; +import { ParentIdType } from '@fastgpt/global/common/parentFolder/type'; +import MyBox from '@fastgpt/web/components/common/MyBox'; +import FolderPath from '@/components/common/folder/Path'; +import { getAppFolderPath } from '@/web/core/app/api/app'; type ModuleTemplateListProps = { isOpen: boolean; @@ -37,8 +37,8 @@ type ModuleTemplateListProps = { type RenderListProps = { templates: FlowNodeTemplateType[]; onClose: () => void; - currentParent?: { parentId: string; parentName: string }; - setCurrentParent: (e: { parentId: string; parentName: string }) => void; + parentId: ParentIdType; + setParentId: React.Dispatch>; }; enum TemplateTypeEnum { @@ -52,195 +52,175 @@ const sliderWidth = 390; const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => { const { t } = useTranslation(); const router = useRouter(); - const [currentParent, setCurrentParent] = useState(); + const [parentId, setParentId] = useState(''); const [searchKey, setSearchKey] = useState(''); const { feConfigs } = useSystemStore(); - const basicNodeTemplates = useContextSelector(WorkflowContext, (v) => v.basicNodeTemplates); - const hasToolNode = useContextSelector(WorkflowContext, (v) => v.hasToolNode); - const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); + const { basicNodeTemplates, hasToolNode, nodeList } = useContextSelector( + WorkflowContext, + (v) => v + ); - const { - systemNodeTemplates, - loadSystemNodeTemplates, - teamPluginNodeTemplates, - loadTeamPluginNodeTemplates - } = useWorkflowStore(); const [templateType, setTemplateType] = useState(TemplateTypeEnum.basic); - const templates = useCreation(() => { - const map = { - [TemplateTypeEnum.basic]: basicNodeTemplates.filter((item) => { - // unique node filter - if (item.unique) { - const nodeExist = nodeList.some((node) => node.flowNodeType === item.flowNodeType); - if (nodeExist) { + const { data: templates = [], loading } = useRequest2( + async () => { + if (templateType === TemplateTypeEnum.basic) { + return basicNodeTemplates.filter((item) => { + // unique node filter + if (item.unique) { + const nodeExist = nodeList.some((node) => node.flowNodeType === item.flowNodeType); + if (nodeExist) { + return false; + } + } + // special node filter + if (item.flowNodeType === FlowNodeTypeEnum.lafModule && !feConfigs.lafEnv) { return false; } - } - // special node filter - if (item.flowNodeType === FlowNodeTypeEnum.lafModule && !feConfigs.lafEnv) { - return false; - } - // tool stop - if (!hasToolNode && item.flowNodeType === FlowNodeTypeEnum.stopTool) { - return false; - } - return true; - }), - [TemplateTypeEnum.systemPlugin]: systemNodeTemplates, - [TemplateTypeEnum.teamPlugin]: teamPluginNodeTemplates.filter((item) => - searchKey ? item.pluginType !== PluginTypeEnum.folder : true - ) - }; - return map[templateType]; - }, [ - basicNodeTemplates, - feConfigs.lafEnv, - hasToolNode, - nodeList, - searchKey, - systemNodeTemplates, - teamPluginNodeTemplates, - templateType - ]); - - const { mutate: onChangeTab } = useRequest({ - mutationFn: async (e: any) => { - const val = e as TemplateTypeEnum; - if (val === TemplateTypeEnum.systemPlugin) { - await loadSystemNodeTemplates(); - } else if (val === TemplateTypeEnum.teamPlugin) { - await loadTeamPluginNodeTemplates({ - parentId: currentParent?.parentId + // tool stop + if (!hasToolNode && item.flowNodeType === FlowNodeTypeEnum.stopTool) { + return false; + } + return true; }); } - setTemplateType(val); + if (templateType === TemplateTypeEnum.systemPlugin) { + return getSystemPlugTemplates(); + } + if (templateType === TemplateTypeEnum.teamPlugin) { + return getTeamPlugTemplates({ + parentId, + searchKey, + type: [AppTypeEnum.folder, AppTypeEnum.httpPlugin, AppTypeEnum.plugin] + }); + } + return []; }, - errorToast: t('core.module.templates.Load plugin error') + { + manual: false, + throttleWait: 300, + refreshDeps: [basicNodeTemplates, nodeList, hasToolNode, templateType, searchKey, parentId] + } + ); + + const { data: paths = [] } = useRequest2(() => getAppFolderPath(parentId), { + manual: false, + refreshDeps: [parentId] }); - useQuery(['teamNodeTemplate', currentParent?.parentId, searchKey], () => - loadTeamPluginNodeTemplates({ - parentId: currentParent?.parentId, - searchKey, - init: true - }) - ); - - return ( - <> - - - - - - {/* close icon */} - } - borderColor={'myGray.300'} - variant={'grayBase'} - aria-label={''} - onClick={onClose} - /> - - {templateType === TemplateTypeEnum.teamPlugin && ( - - - - - - setSearchKey(e.target.value), 200)} - /> - - - router.push('/plugin/list')} - > - 去创建 - - - - )} - {templateType === TemplateTypeEnum.teamPlugin && !searchKey && currentParent && ( - - { - setCurrentParent(undefined); - }} - fontSize="md" + const Render = useMemo(() => { + return ( + <> + + + + + setTemplateType(e as TemplateTypeEnum)} + /> + {/* close icon */} + } + borderColor={'myGray.300'} + variant={'grayBase'} + aria-label={''} + onClick={onClose} /> - )} - - - - - ); + {templateType === TemplateTypeEnum.teamPlugin && ( + + + + + + setSearchKey(e.target.value)} + /> + + + router.push('/app/list')} + > + 去创建 + + + + )} + {templateType === TemplateTypeEnum.teamPlugin && !searchKey && parentId && ( + + + + )} + + + + + ); + }, [isOpen, onClose, loading, t, templateType, searchKey, parentId, paths, templates, router]); + + return Render; }; export default React.memo(NodeTemplatesModal); @@ -248,8 +228,8 @@ export default React.memo(NodeTemplatesModal); const RenderList = React.memo(function RenderList({ templates, onClose, - currentParent, - setCurrentParent + parentId, + setParentId }: RenderListProps) { const { t } = useTranslation(); const { appT } = useI18n(); @@ -270,7 +250,7 @@ const RenderList = React.memo(function RenderList({ }); return copy.filter((item) => item.list.length > 0); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [templates, currentParent]); + }, [templates, parentId]); const onAddNode = useCallback( async ({ template, position }: { template: FlowNodeTemplateType; position: XYPosition }) => { @@ -281,7 +261,7 @@ const RenderList = React.memo(function RenderList({ // get plugin preview module if (template.flowNodeType === FlowNodeTypeEnum.pluginModule) { setLoading(true); - const res = await getPreviewPluginModule(template.id); + const res = await getPreviewPluginNode({ appId: template.id }); setLoading(false); return res; @@ -375,7 +355,7 @@ const RenderList = React.memo(function RenderList({ cursor={'pointer'} _hover={{ bg: 'myWhite.600' }} borderRadius={'sm'} - draggable={template.pluginType !== PluginTypeEnum.folder} + draggable={template.pluginType !== AppTypeEnum.folder} onDragEnd={(e) => { if (e.clientX < sliderWidth) return; onAddNode({ @@ -384,11 +364,11 @@ const RenderList = React.memo(function RenderList({ }); }} onClick={(e) => { - if (template.pluginType === PluginTypeEnum.folder) { - return setCurrentParent({ - parentId: template.id, - parentName: template.name - }); + if ( + template.pluginType === AppTypeEnum.folder || + template.pluginType === AppTypeEnum.httpPlugin + ) { + return setParentId(template.id); } if (isPc) { return onAddNode({ @@ -421,7 +401,7 @@ const RenderList = React.memo(function RenderList({ ); - }, [appT, formatTemplates, isPc, onAddNode, onClose, setCurrentParent, t, templates.length]); + }, [appT, formatTemplates, isPc, onAddNode, onClose, setParentId, t, templates.length]); return Render; }); diff --git a/projects/app/src/components/core/workflow/Flow/SelectAppModal.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/SelectAppModal.tsx similarity index 100% rename from projects/app/src/components/core/workflow/Flow/SelectAppModal.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/SelectAppModal.tsx diff --git a/projects/app/src/components/core/workflow/Flow/components/ButtonEdge.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ButtonEdge.tsx similarity index 99% rename from projects/app/src/components/core/workflow/Flow/components/ButtonEdge.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ButtonEdge.tsx index 3fa2c34b3..8f25483a7 100644 --- a/projects/app/src/components/core/workflow/Flow/components/ButtonEdge.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ButtonEdge.tsx @@ -93,7 +93,7 @@ const ButtonEdge = (props: EdgeProps) => { if (highlightEdge) return '#3370ff'; return '#94B5FF'; } - console.log(targetEdge); + // debug mode const colorMap = { [RuntimeEdgeStatusEnum.active]: '#39CC83', diff --git a/projects/app/src/components/core/workflow/Flow/components/Container.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/Container.tsx similarity index 100% rename from projects/app/src/components/core/workflow/Flow/components/Container.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/Container.tsx diff --git a/projects/app/src/components/core/workflow/Flow/components/Divider.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/Divider.tsx similarity index 100% rename from projects/app/src/components/core/workflow/Flow/components/Divider.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/Divider.tsx diff --git a/projects/app/src/components/core/workflow/Flow/components/IOTitle.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/IOTitle.tsx similarity index 100% rename from projects/app/src/components/core/workflow/Flow/components/IOTitle.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/IOTitle.tsx diff --git a/projects/app/src/components/core/workflow/Flow/hooks/useDebug.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useDebug.tsx similarity index 99% rename from projects/app/src/components/core/workflow/Flow/hooks/useDebug.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useDebug.tsx index 1c45ee0b3..af8aa0d7b 100644 --- a/projects/app/src/components/core/workflow/Flow/hooks/useDebug.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useDebug.tsx @@ -272,3 +272,7 @@ export const useDebug = () => { openDebugNode }; }; + +export default function Dom() { + return <>; +} diff --git a/projects/app/src/components/core/workflow/Flow/hooks/useKeyboard.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useKeyboard.tsx similarity index 92% rename from projects/app/src/components/core/workflow/Flow/hooks/useKeyboard.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useKeyboard.tsx index 2ae1f9cf0..ef8d06443 100644 --- a/projects/app/src/components/core/workflow/Flow/hooks/useKeyboard.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useKeyboard.tsx @@ -9,7 +9,7 @@ import { WorkflowContext, getWorkflowStore } from '../../context'; export const useKeyboard = () => { const { t } = useTranslation(); - const setNodes = useContextSelector(WorkflowContext, (v) => v.setNodes); + const { setNodes, onSaveWorkflow } = useContextSelector(WorkflowContext, (v) => v); const { copyData } = useCopyData(); const [isDowningCtrl, setIsDowningCtrl] = useState(false); @@ -81,6 +81,7 @@ export const useKeyboard = () => { (event: KeyboardEvent) => { if (event.ctrlKey || event.metaKey) { setIsDowningCtrl(true); + switch (event.key) { case 'c': onCopy(); @@ -88,12 +89,17 @@ export const useKeyboard = () => { case 'v': onParse(); break; + case 's': + event.preventDefault(); + + onSaveWorkflow(); + break; default: break; } } }, - [onCopy, onParse] + [onCopy, onParse, onSaveWorkflow] ); const handleKeyUp = useCallback((event: KeyboardEvent) => { @@ -118,3 +124,7 @@ export const useKeyboard = () => { isDowningCtrl }; }; + +export default function Dom() { + return <>; +} diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useWorkflow.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useWorkflow.tsx new file mode 100644 index 000000000..88233ec15 --- /dev/null +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useWorkflow.tsx @@ -0,0 +1,127 @@ +import React, { useCallback, useMemo } from 'react'; +import { Connection, NodeChange, OnConnectStartParams, addEdge, EdgeChange, Edge } from 'reactflow'; +import { EDGE_TYPE } from '@fastgpt/global/core/workflow/node/constant'; +import 'reactflow/dist/style.css'; +import { useToast } from '@fastgpt/web/hooks/useToast'; +import { useTranslation } from 'next-i18next'; +import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; +import { useKeyboard } from './useKeyboard'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '../../context'; + +export const useWorkflow = () => { + const { toast } = useToast(); + const { t } = useTranslation(); + const { openConfirm: onOpenConfirmDeleteNode, ConfirmModal: ConfirmDeleteModal } = useConfirm({ + content: t('core.module.Confirm Delete Node'), + type: 'delete' + }); + + const { isDowningCtrl } = useKeyboard(); + const { setConnectingEdge, nodes, onNodesChange, setEdges, onEdgesChange, setHoverEdgeId } = + useContextSelector(WorkflowContext, (v) => v); + + /* node */ + const handleNodesChange = useCallback( + (changes: NodeChange[]) => { + for (const change of changes) { + if (change.type === 'remove') { + const node = nodes.find((n) => n.id === change.id); + if (node && node.data.forbidDelete) { + return toast({ + status: 'warning', + title: t('core.workflow.Can not delete node') + }); + } else { + return onOpenConfirmDeleteNode(() => { + onNodesChange(changes); + setEdges((state) => + state.filter((edge) => edge.source !== change.id && edge.target !== change.id) + ); + })(); + } + } else if (change.type === 'select' && change.selected === false && isDowningCtrl) { + change.selected = true; + } + } + + onNodesChange(changes); + }, + [isDowningCtrl, nodes, onNodesChange, onOpenConfirmDeleteNode, setEdges, t, toast] + ); + const handleEdgeChange = useCallback( + (changes: EdgeChange[]) => { + onEdgesChange(changes.filter((change) => change.type !== 'remove')); + }, + [onEdgesChange] + ); + + /* connect */ + const onConnectStart = useCallback( + (event: any, params: OnConnectStartParams) => { + setConnectingEdge(params); + }, + [setConnectingEdge] + ); + const onConnectEnd = useCallback(() => { + setConnectingEdge(undefined); + }, [setConnectingEdge]); + const onConnect = useCallback( + ({ connect }: { connect: Connection }) => { + setEdges((state) => + addEdge( + { + ...connect, + type: EDGE_TYPE + }, + state + ) + ); + }, + [setEdges] + ); + const customOnConnect = useCallback( + (connect: Connection) => { + if (!connect.sourceHandle || !connect.targetHandle) { + return; + } + if (connect.source === connect.target) { + return toast({ + status: 'warning', + title: t('core.module.Can not connect self') + }); + } + onConnect({ + connect + }); + }, + [onConnect, t, toast] + ); + + /* edge */ + const onEdgeMouseEnter = useCallback( + (e: any, edge: Edge) => { + setHoverEdgeId(edge.id); + }, + [setHoverEdgeId] + ); + const onEdgeMouseLeave = useCallback(() => { + setHoverEdgeId(undefined); + }, [setHoverEdgeId]); + + return { + ConfirmDeleteModal, + handleNodesChange, + handleEdgeChange, + onConnectStart, + onConnectEnd, + onConnect, + customOnConnect, + onEdgeMouseEnter, + onEdgeMouseLeave + }; +}; + +export default function Dom() { + return <>; +} diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx new file mode 100644 index 000000000..60947d4fe --- /dev/null +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx @@ -0,0 +1,184 @@ +import React, { useMemo } from 'react'; +import ReactFlow, { + Background, + Controls, + ControlButton, + MiniMap, + NodeProps, + ReactFlowProvider, + useReactFlow +} from 'reactflow'; +import { Box, IconButton, useDisclosure } from '@chakra-ui/react'; +import { SmallCloseIcon } from '@chakra-ui/icons'; +import { EDGE_TYPE, FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; + +import dynamic from 'next/dynamic'; + +import ButtonEdge from './components/ButtonEdge'; +import NodeTemplatesModal from './NodeTemplatesModal'; + +import 'reactflow/dist/style.css'; +import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; +import { connectionLineStyle, defaultEdgeOptions } from '../constants'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '../context'; +import { useWorkflow } from './hooks/useWorkflow'; + +const NodeSimple = dynamic(() => import('./nodes/NodeSimple')); +const nodeTypes: Record = { + [FlowNodeTypeEnum.emptyNode]: NodeSimple, + [FlowNodeTypeEnum.globalVariable]: NodeSimple, + [FlowNodeTypeEnum.systemConfig]: dynamic(() => import('./nodes/NodeSystemConfig')), + [FlowNodeTypeEnum.workflowStart]: dynamic(() => import('./nodes/NodeWorkflowStart')), + [FlowNodeTypeEnum.chatNode]: NodeSimple, + [FlowNodeTypeEnum.datasetSearchNode]: NodeSimple, + [FlowNodeTypeEnum.datasetConcatNode]: dynamic(() => import('./nodes/NodeDatasetConcat')), + [FlowNodeTypeEnum.answerNode]: dynamic(() => import('./nodes/NodeAnswer')), + [FlowNodeTypeEnum.classifyQuestion]: dynamic(() => import('./nodes/NodeCQNode')), + [FlowNodeTypeEnum.contentExtract]: dynamic(() => import('./nodes/NodeExtract')), + [FlowNodeTypeEnum.httpRequest468]: dynamic(() => import('./nodes/NodeHttp')), + [FlowNodeTypeEnum.runApp]: NodeSimple, + [FlowNodeTypeEnum.pluginInput]: dynamic(() => import('./nodes/NodePluginInput')), + [FlowNodeTypeEnum.pluginOutput]: dynamic(() => import('./nodes/NodePluginOutput')), + [FlowNodeTypeEnum.pluginModule]: NodeSimple, + [FlowNodeTypeEnum.queryExtension]: NodeSimple, + [FlowNodeTypeEnum.tools]: dynamic(() => import('./nodes/NodeTools')), + [FlowNodeTypeEnum.stopTool]: (data: NodeProps) => ( + + ), + [FlowNodeTypeEnum.lafModule]: dynamic(() => import('./nodes/NodeLaf')), + [FlowNodeTypeEnum.ifElseNode]: dynamic(() => import('./nodes/NodeIfElse')), + [FlowNodeTypeEnum.variableUpdate]: dynamic(() => import('./nodes/NodeVariableUpdate')), + [FlowNodeTypeEnum.code]: dynamic(() => import('./nodes/NodeCode')) +}; +const edgeTypes = { + [EDGE_TYPE]: ButtonEdge +}; + +const Workflow = () => { + const { nodes, edges, reactFlowWrapper } = useContextSelector(WorkflowContext, (v) => v); + + const { + ConfirmDeleteModal, + handleNodesChange, + handleEdgeChange, + onConnectStart, + onConnectEnd, + customOnConnect, + onEdgeMouseEnter, + onEdgeMouseLeave + } = useWorkflow(); + + const { + isOpen: isOpenTemplate, + onOpen: onOpenTemplate, + onClose: onCloseTemplate + } = useDisclosure(); + + return ( + + { + e.preventDefault(); + return false; + }} + > + {/* open module template */} + <> + } + transform={isOpenTemplate ? '' : 'rotate(135deg)'} + transition={'0.2s ease'} + aria-label={''} + zIndex={1} + boxShadow={'2px 2px 6px #85b1ff'} + onClick={() => { + isOpenTemplate ? onCloseTemplate() : onOpenTemplate(); + }} + /> + + + + + + + + + + + ); +}; + +export default React.memo(Workflow); + +const FlowController = React.memo(function FlowController() { + const { fitView } = useReactFlow(); + + const Render = useMemo(() => { + return ( + <> + + + + fitView()}> + + + + + + + ); + }, [fitView]); + + return Render; +}); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeAnswer.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeAnswer.tsx similarity index 100% rename from projects/app/src/components/core/workflow/Flow/nodes/NodeAnswer.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeAnswer.tsx diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeCQNode.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeCQNode.tsx similarity index 100% rename from projects/app/src/components/core/workflow/Flow/nodes/NodeCQNode.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeCQNode.tsx diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeCode.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeCode.tsx similarity index 100% rename from projects/app/src/components/core/workflow/Flow/nodes/NodeCode.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeCode.tsx diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeDatasetConcat.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeDatasetConcat.tsx similarity index 100% rename from projects/app/src/components/core/workflow/Flow/nodes/NodeDatasetConcat.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeDatasetConcat.tsx diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeEmpty.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeEmpty.tsx similarity index 100% rename from projects/app/src/components/core/workflow/Flow/nodes/NodeEmpty.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeEmpty.tsx diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeExtract/ExtractFieldModal.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeExtract/ExtractFieldModal.tsx similarity index 100% rename from projects/app/src/components/core/workflow/Flow/nodes/NodeExtract/ExtractFieldModal.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeExtract/ExtractFieldModal.tsx diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeExtract/index.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeExtract/index.tsx similarity index 100% rename from projects/app/src/components/core/workflow/Flow/nodes/NodeExtract/index.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeExtract/index.tsx diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeHttp/CurlImportModal.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeHttp/CurlImportModal.tsx similarity index 100% rename from projects/app/src/components/core/workflow/Flow/nodes/NodeHttp/CurlImportModal.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeHttp/CurlImportModal.tsx diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeHttp/index.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeHttp/index.tsx similarity index 99% rename from projects/app/src/components/core/workflow/Flow/nodes/NodeHttp/index.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeHttp/index.tsx index 0c730b868..2b237e96f 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodeHttp/index.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeHttp/index.tsx @@ -38,7 +38,7 @@ import { useContextSelector } from 'use-context-selector'; import { WorkflowContext } from '../../../context'; import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils'; import { useMemoizedFn } from 'ahooks'; -import { AppContext } from '@/web/core/app/context/appContext'; +import { AppContext } from '@/pages/app/detail/components/context'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; const CurlImportModal = dynamic(() => import('./CurlImportModal')); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse/ListItem.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeIfElse/ListItem.tsx similarity index 99% rename from projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse/ListItem.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeIfElse/ListItem.tsx index 9c63f918f..987f23363 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse/ListItem.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeIfElse/ListItem.tsx @@ -30,7 +30,7 @@ import { SourceHandle } from '../render/Handle'; import { Position, useReactFlow } from 'reactflow'; import { getRefData } from '@/web/core/workflow/utils'; import DragIcon from '@fastgpt/web/components/common/DndDrag/DragIcon'; -import { AppContext } from '@/web/core/app/context/appContext'; +import { AppContext } from '@/pages/app/detail/components/context'; import { useI18n } from '@/web/context/I18n'; const ListItem = ({ diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse/index.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeIfElse/index.tsx similarity index 100% rename from projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse/index.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeIfElse/index.tsx diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeLaf.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeLaf.tsx similarity index 99% rename from projects/app/src/components/core/workflow/Flow/nodes/NodeLaf.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeLaf.tsx index 68b385d63..d23b39923 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodeLaf.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeLaf.tsx @@ -8,8 +8,8 @@ import { WorkflowIOValueTypeEnum, NodeInputKeyEnum } from '@fastgpt/global/core/ import { useTranslation } from 'next-i18next'; import { getLafAppDetail } from '@/web/support/laf/api'; import MySelect from '@fastgpt/web/components/common/MySelect'; -import { getApiSchemaByUrl } from '@/web/core/plugin/api'; -import { getType, str2OpenApiSchema } from '@fastgpt/global/core/plugin/httpPlugin/utils'; +import { getApiSchemaByUrl } from '@/web/core/app/api/plugin'; +import { getType, str2OpenApiSchema } from '@fastgpt/global/core/app/httpPlugin/utils'; import { useUserStore } from '@/web/support/user/useUserStore'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import { ChevronRightIcon } from '@chakra-ui/icons'; diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodePluginInput.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginInput.tsx similarity index 99% rename from projects/app/src/components/core/workflow/Flow/nodes/NodePluginInput.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginInput.tsx index 0913d4ae8..13a3a967e 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodePluginInput.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginInput.tsx @@ -23,7 +23,7 @@ import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType'; -import VariableTable from '../nodes/render/VariableTable'; +import VariableTable from './render/VariableTable'; import { useContextSelector } from 'use-context-selector'; import { WorkflowContext } from '../../context'; diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodePluginOutput.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginOutput.tsx similarity index 100% rename from projects/app/src/components/core/workflow/Flow/nodes/NodePluginOutput.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginOutput.tsx diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeSimple.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeSimple.tsx similarity index 100% rename from projects/app/src/components/core/workflow/Flow/nodes/NodeSimple.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeSimple.tsx diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeSystemConfig.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeSystemConfig.tsx similarity index 98% rename from projects/app/src/components/core/workflow/Flow/nodes/NodeSystemConfig.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeSystemConfig.tsx index 43a09408d..07ad43f6a 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodeSystemConfig.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeSystemConfig.tsx @@ -17,7 +17,7 @@ import { WorkflowContext } from '../../context'; import { AppChatConfigType, AppDetailType, VariableItemType } from '@fastgpt/global/core/app/type'; import { useMemoizedFn } from 'ahooks'; import VariableEdit from '@/components/core/app/VariableEdit'; -import { AppContext } from '@/web/core/app/context/appContext'; +import { AppContext } from '@/pages/app/detail/components/context'; import WelcomeTextConfig from '@/components/core/app/WelcomeTextConfig'; type ComponentProps = { diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeTools.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeTools.tsx similarity index 100% rename from projects/app/src/components/core/workflow/Flow/nodes/NodeTools.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeTools.tsx diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeVariableUpdate.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeVariableUpdate.tsx similarity index 98% rename from projects/app/src/components/core/workflow/Flow/nodes/NodeVariableUpdate.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeVariableUpdate.tsx index c012e2f07..cfb997611 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodeVariableUpdate.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeVariableUpdate.tsx @@ -18,7 +18,7 @@ import { import { TUpdateListItem } from '@fastgpt/global/core/workflow/template/system/variableUpdate/type'; import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext } from '@/components/core/workflow/context'; +import { WorkflowContext } from '../../context'; import { FlowNodeInputMap, FlowNodeInputTypeEnum @@ -32,7 +32,7 @@ import { ReferenceValueProps } from '@fastgpt/global/core/workflow/type/io'; import { ReferSelector, useReference } from './render/RenderInput/templates/Reference'; import { getRefData } from '@/web/core/workflow/utils'; import { isReferenceValue } from '@fastgpt/global/core/workflow/utils'; -import { AppContext } from '@/web/core/app/context/appContext'; +import { AppContext } from '@/pages/app/detail/components/context'; const NodeVariableUpdate = ({ data, selected }: NodeProps) => { const { inputs = [], nodeId } = data; diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeWorkflowStart.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeWorkflowStart.tsx similarity index 96% rename from projects/app/src/components/core/workflow/Flow/nodes/NodeWorkflowStart.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeWorkflowStart.tsx index 1f33076a5..f5a323823 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodeWorkflowStart.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeWorkflowStart.tsx @@ -13,7 +13,7 @@ import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils'; import { FlowNodeOutputItemType } from '@fastgpt/global/core/workflow/type/io'; import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; -import { AppContext } from '@/web/core/app/context/appContext'; +import { AppContext } from '@/pages/app/detail/components/context'; const NodeStart = ({ data, selected }: NodeProps) => { const { t } = useTranslation(); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/FieldEditModal.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/FieldEditModal.tsx similarity index 100% rename from projects/app/src/components/core/workflow/Flow/nodes/render/FieldEditModal.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/FieldEditModal.tsx diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/ConnectionHandle.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ConnectionHandle.tsx similarity index 98% rename from projects/app/src/components/core/workflow/Flow/nodes/render/Handle/ConnectionHandle.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ConnectionHandle.tsx index 66978beca..2dde87800 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/ConnectionHandle.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ConnectionHandle.tsx @@ -4,7 +4,7 @@ import { SourceHandle, TargetHandle } from '.'; import { getHandleId } from '@fastgpt/global/core/workflow/utils'; import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext } from '@/components/core/workflow/context'; +import { WorkflowContext } from '../../../../context'; export const ConnectionSourceHandle = ({ nodeId }: { nodeId: string }) => { const connectingEdge = useContextSelector(WorkflowContext, (ctx) => ctx.connectingEdge); @@ -183,4 +183,6 @@ export const ConnectionTargetHandle = ({ nodeId }: { nodeId: string }) => { ) : null; }; -export default <>; +export default function Dom() { + return <>; +} diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/ToolHandle.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ToolHandle.tsx similarity index 95% rename from projects/app/src/components/core/workflow/Flow/nodes/render/Handle/ToolHandle.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ToolHandle.tsx index 648ea2407..651151ad1 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/ToolHandle.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ToolHandle.tsx @@ -6,7 +6,7 @@ import { Connection, Handle, Position } from 'reactflow'; import { useCallback, useMemo } from 'react'; import { getHandleId } from '@fastgpt/global/core/workflow/utils'; import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext } from '@/components/core/workflow/context'; +import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; const handleSize = '14px'; type ToolHandleProps = BoxProps & { @@ -107,3 +107,7 @@ export const ToolSourceHandle = ({ nodeId }: ToolHandleProps) => { return Render; }; + +export default function Dom() { + return <>; +} diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/index.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/index.tsx similarity index 98% rename from projects/app/src/components/core/workflow/Flow/nodes/render/Handle/index.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/index.tsx index 977ac57eb..afaa5b3f4 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/index.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/index.tsx @@ -4,7 +4,7 @@ import { SmallAddIcon } from '@chakra-ui/icons'; import { handleHighLightStyle, sourceCommonStyle, handleConnectedStyle, handleSize } from './style'; import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext } from '@/components/core/workflow/context'; +import { WorkflowContext } from '../../../../context'; type Props = { nodeId: string; @@ -247,4 +247,6 @@ export const TargetHandle = (props: Props) => { ); }; -export default <>; +export default function Dom() { + return <>; +} diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/style.ts b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/style.tsx similarity index 91% rename from projects/app/src/components/core/workflow/Flow/nodes/render/Handle/style.ts rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/style.tsx index fa179f1ff..fa135e36d 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/style.ts +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/style.tsx @@ -23,3 +23,7 @@ export const handleHighLightStyle = { width: '18px', height: '18px' }; + +export default function Dom() { + return <>; +} diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/NodeCard.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx similarity index 96% rename from projects/app/src/components/core/workflow/Flow/nodes/render/NodeCard.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx index 5e7763d4c..97ea5aa36 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/NodeCard.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx @@ -15,7 +15,7 @@ import { ConnectionSourceHandle, ConnectionTargetHandle } from './Handle/Connect import { useDebug } from '../../hooks/useDebug'; import { ResponseBox } from '@/components/ChatBox/components/WholeResponseModal'; import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; -import { getPreviewPluginModule } from '@/web/core/plugin/api'; +import { getPreviewPluginNode } from '@/web/core/app/api/plugin'; import { storeNode2FlowNode, updateFlowNodeVersion } from '@/web/core/workflow/utils'; import { getNanoid } from '@fastgpt/global/common/string/tools'; import { useContextSelector } from 'use-context-selector'; @@ -25,6 +25,8 @@ import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/cons import { QuestionOutlineIcon } from '@chakra-ui/icons'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import { useSystemStore } from '@/web/common/system/useSystemStore'; +import { useMount } from 'ahooks'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; type Props = FlowNodeItemType & { children?: React.ReactNode | React.ReactNode[] | string; @@ -68,9 +70,6 @@ const NodeCard = (props: Props) => { const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const onResetNode = useContextSelector(WorkflowContext, (v) => v.onResetNode); - const [hasNewVersion, setHasNewVersion] = useState(false); - const { setLoading } = useSystemStore(); - // custom title edit const { onOpenModal: onOpenCustomTitleModal, EditModal: EditTitleModal } = useEditTitle({ title: t('common.Custom Title'), @@ -87,34 +86,35 @@ const NodeCard = (props: Props) => { content: appT('module.Confirm Sync') }); - useEffect(() => { - const fetchPluginModule = async () => { + const { data: newNodeVersion, runAsync: getNodeVersion } = useRequest2( + async () => { if (node?.flowNodeType === FlowNodeTypeEnum.pluginModule) { if (!node?.pluginId) return; - const template = await getPreviewPluginModule(node.pluginId); - setHasNewVersion(!!template.nodeVersion && node.nodeVersion !== template.nodeVersion); + const template = await getPreviewPluginNode({ appId: node.pluginId }); + return template.version; } else { const template = moduleTemplatesFlat.find( (item) => item.flowNodeType === node?.flowNodeType ); - setHasNewVersion(node?.version !== template?.version); + return template?.version; } - }; - - fetchPluginModule(); - }, [node]); + }, + { + manual: false + } + ); + const hasNewVersion = newNodeVersion && newNodeVersion !== node?.version; const template = moduleTemplatesFlat.find((item) => item.flowNodeType === node?.flowNodeType); const onClickSyncVersion = useCallback(async () => { try { - setLoading(true); if (!node || !template) return; if (node?.flowNodeType === 'pluginModule') { if (!node.pluginId) return; onResetNode({ id: nodeId, - node: await getPreviewPluginModule(node.pluginId) + node: await getPreviewPluginNode({ appId: node.pluginId }) }); } else { onResetNode({ @@ -122,11 +122,11 @@ const NodeCard = (props: Props) => { node: updateFlowNodeVersion(node, template) }); } + await getNodeVersion(); } catch (error) { console.error('Error fetching plugin module:', error); } - setLoading(false); - }, [node, nodeId, onResetNode, setLoading, template]); + }, [getNodeVersion, node, nodeId, onResetNode, template]); /* Node header */ const Header = useMemo(() => { diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/Label.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/Label.tsx similarity index 98% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/Label.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/Label.tsx index 6d7d4d302..959a62f99 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/Label.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/Label.tsx @@ -12,7 +12,7 @@ import { EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type'; import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import ValueTypeLabel from '../ValueTypeLabel'; import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext } from '@/components/core/workflow/context'; +import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; const FieldEditModal = dynamic(() => import('../FieldEditModal')); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/index.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/index.tsx similarity index 100% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/index.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/index.tsx diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/AddInputParam.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/AddInputParam.tsx similarity index 90% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/AddInputParam.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/AddInputParam.tsx index b11f94d48..03c65881d 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/AddInputParam.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/AddInputParam.tsx @@ -10,15 +10,17 @@ import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/consta import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io'; import Reference from './Reference'; import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext } from '@/components/core/workflow/context'; +import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; +import { AppContext } from '@/pages/app/detail/components/context'; +import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; const FieldEditModal = dynamic(() => import('../../FieldEditModal')); const AddInputParam = (props: RenderInputProps) => { const { item, inputs, nodeId } = props; const { t } = useTranslation(); + const appDetail = useContextSelector(AppContext, (v) => v.appDetail); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); - const mode = useContextSelector(WorkflowContext, (ctx) => ctx.mode); const inputValue = useMemo(() => (item.value || []) as FlowNodeInputItemType[], [item.value]); @@ -77,7 +79,7 @@ const AddInputParam = (props: RenderInputProps) => { {t('common.Add New')} - {mode === 'plugin' && ( + {appDetail.type === AppTypeEnum.plugin && ( @@ -95,12 +97,12 @@ const AddInputParam = (props: RenderInputProps) => { ); }, [ + appDetail.type, editField, inputValue, item.description, item.dynamicParamDefaultValue, item.editField, - mode, onAddField, props, t diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/JsonEditor.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/JsonEditor.tsx similarity index 93% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/JsonEditor.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/JsonEditor.tsx index 49e32bd9c..b1bdb31e5 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/JsonEditor.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/JsonEditor.tsx @@ -3,11 +3,11 @@ import type { RenderInputProps } from '../type'; import JSONEditor from '@fastgpt/web/components/common/Textarea/JsonEditor'; import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils'; import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext } from '@/components/core/workflow/context'; +import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils'; import { useCreation } from 'ahooks'; import { useTranslation } from 'next-i18next'; -import { AppContext } from '@/web/core/app/context/appContext'; +import { AppContext } from '@/pages/app/detail/components/context'; const JsonEditor = ({ inputs = [], item, nodeId }: RenderInputProps) => { const { t } = useTranslation(); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/NumberInput.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/NumberInput.tsx similarity index 92% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/NumberInput.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/NumberInput.tsx index 80a1f71ac..e62cbdaef 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/NumberInput.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/NumberInput.tsx @@ -8,7 +8,7 @@ import { NumberInputStepper } from '@chakra-ui/react'; import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext } from '@/components/core/workflow/context'; +import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; const NumberInputRender = ({ item, nodeId }: RenderInputProps) => { const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Reference.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Reference.tsx similarity index 97% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Reference.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Reference.tsx index fd1114c6d..9238464af 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Reference.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Reference.tsx @@ -12,9 +12,9 @@ import { import type { ReferenceValueProps } from '@fastgpt/global/core/workflow/type/io'; import dynamic from 'next/dynamic'; import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext } from '@/components/core/workflow/context'; +import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; -import { AppContext } from '@/web/core/app/context/appContext'; +import { AppContext } from '@/pages/app/detail/components/context'; const MultipleRowSelect = dynamic( () => import('@fastgpt/web/components/common/MySelect/MultipleRowSelect') diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Select.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Select.tsx similarity index 90% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Select.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Select.tsx index 09ab20ec3..071c59ce3 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Select.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Select.tsx @@ -1,7 +1,7 @@ import React, { useMemo } from 'react'; import type { RenderInputProps } from '../type'; import MySelect from '@fastgpt/web/components/common/MySelect'; -import { WorkflowContext } from '@/components/core/workflow/context'; +import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; import { useContextSelector } from 'use-context-selector'; const SelectRender = ({ item, nodeId }: RenderInputProps) => { diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectApp.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectApp.tsx similarity index 96% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectApp.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectApp.tsx index b614d69e9..430ef4ce8 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectApp.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectApp.tsx @@ -6,7 +6,7 @@ import Avatar from '@/components/Avatar'; import SelectAppModal from '../../../../SelectAppModal'; import { useTranslation } from 'next-i18next'; import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext } from '@/components/core/workflow/context'; +import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { getAppDetailById } from '@/web/core/app/api'; diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectDataset.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectDataset.tsx similarity index 97% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectDataset.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectDataset.tsx index 96275b2de..b2beb9a5f 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectDataset.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectDataset.tsx @@ -11,7 +11,7 @@ import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants'; import dynamic from 'next/dynamic'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext } from '@/components/core/workflow/context'; +import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal')); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectDatasetParams.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectDatasetParams.tsx similarity index 97% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectDatasetParams.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectDatasetParams.tsx index 85f8795ca..86ce974f5 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectDatasetParams.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectDatasetParams.tsx @@ -10,7 +10,7 @@ import DatasetParamsModal, { DatasetParamsProps } from '@/components/core/app/Da import { useSystemStore } from '@/web/common/system/useSystemStore'; import SearchParamsTip from '@/components/core/dataset/SearchParamsTip'; import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext } from '@/components/core/workflow/context'; +import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; const SelectDatasetParam = ({ inputs = [], nodeId }: RenderInputProps) => { const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectLLMModel.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectLLMModel.tsx similarity index 94% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectLLMModel.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectLLMModel.tsx index 7705847f5..b4c3dfca9 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectLLMModel.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectLLMModel.tsx @@ -4,7 +4,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore'; import { llmModelTypeFilterMap } from '@fastgpt/global/core/ai/constants'; import AIModelSelector from '@/components/Select/AIModelSelector'; import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext } from '@/components/core/workflow/context'; +import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; const SelectAiModelRender = ({ item, nodeId }: RenderInputProps) => { const { llmModelList } = useSystemStore(); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SettingLLMModel.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SettingLLMModel.tsx similarity index 95% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SettingLLMModel.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SettingLLMModel.tsx index 9e6281606..24e45fc98 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SettingLLMModel.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SettingLLMModel.tsx @@ -4,7 +4,7 @@ import type { SettingAIDataType } from '@fastgpt/global/core/app/type.d'; import SettingLLMModel from '@/components/core/ai/SettingLLMModel'; import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext } from '@/components/core/workflow/context'; +import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; const SelectAiModelRender = ({ item, inputs = [], nodeId }: RenderInputProps) => { const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SettingQuotePrompt.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SettingQuotePrompt.tsx similarity index 98% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SettingQuotePrompt.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SettingQuotePrompt.tsx index c96a27a5a..de282f104 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SettingQuotePrompt.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SettingQuotePrompt.tsx @@ -18,10 +18,10 @@ import MyIcon from '@fastgpt/web/components/common/Icon'; import Reference from './Reference'; import ValueTypeLabel from '../../ValueTypeLabel'; import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext } from '@/components/core/workflow/context'; +import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils'; import { useCreation } from 'ahooks'; -import { AppContext } from '@/web/core/app/context/appContext'; +import { AppContext } from '@/pages/app/detail/components/context'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Slider.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Slider.tsx similarity index 92% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Slider.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Slider.tsx index 7d0cd11e5..bcbbcd620 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Slider.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Slider.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'next-i18next'; import { Box } from '@chakra-ui/react'; import MySlider from '@/components/Slider'; import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext } from '@/components/core/workflow/context'; +import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; const SliderRender = ({ item, nodeId }: RenderInputProps) => { const { t } = useTranslation(); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Switch.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Switch.tsx similarity index 89% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Switch.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Switch.tsx index 6d1b124ee..5520d91af 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Switch.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Switch.tsx @@ -2,7 +2,7 @@ import React, { useMemo } from 'react'; import type { RenderInputProps } from '../type'; import { Switch } from '@chakra-ui/react'; import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext } from '@/components/core/workflow/context'; +import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; const SwitchRender = ({ item, nodeId }: RenderInputProps) => { const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/TextInput.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/TextInput.tsx similarity index 90% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/TextInput.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/TextInput.tsx index c93cea13f..a00294c2e 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/TextInput.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/TextInput.tsx @@ -2,7 +2,7 @@ import React, { useMemo } from 'react'; import type { RenderInputProps } from '../type'; import { Input } from '@chakra-ui/react'; import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext } from '@/components/core/workflow/context'; +import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; const TextInput = ({ item, nodeId }: RenderInputProps) => { const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Textarea.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Textarea.tsx similarity index 92% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Textarea.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Textarea.tsx index 10ab54fec..a7a70bc10 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Textarea.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Textarea.tsx @@ -4,10 +4,10 @@ import { useTranslation } from 'next-i18next'; import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor'; import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils'; import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext } from '@/components/core/workflow/context'; +import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils'; import { useCreation } from 'ahooks'; -import { AppContext } from '@/web/core/app/context/appContext'; +import { AppContext } from '@/pages/app/detail/components/context'; const TextareaRender = ({ inputs = [], item, nodeId }: RenderInputProps) => { const { t } = useTranslation(); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/type.d.ts b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/type.d.ts similarity index 100% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/type.d.ts rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/type.d.ts diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderOutput/Label.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderOutput/Label.tsx similarity index 100% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderOutput/Label.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderOutput/Label.tsx diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderOutput/index.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderOutput/index.tsx similarity index 98% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderOutput/index.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderOutput/index.tsx index 0cb05758a..543b5b1cf 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderOutput/index.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderOutput/index.tsx @@ -12,7 +12,7 @@ import { EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type'; import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType'; import { getNanoid } from '@fastgpt/global/common/string/tools'; import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext } from '@/components/core/workflow/context'; +import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; const RenderList: { diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderOutput/type.d.ts b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderOutput/type.d.ts similarity index 100% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderOutput/type.d.ts rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderOutput/type.d.ts diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderToolInput/EditFieldModal.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderToolInput/EditFieldModal.tsx similarity index 97% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderToolInput/EditFieldModal.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderToolInput/EditFieldModal.tsx index 72ee0853c..78b5be8e0 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderToolInput/EditFieldModal.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderToolInput/EditFieldModal.tsx @@ -19,7 +19,7 @@ import { useRequest } from '@fastgpt/web/hooks/useRequest'; import { useToast } from '@fastgpt/web/hooks/useToast'; import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d'; import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext } from '@/components/core/workflow/context'; +import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; import { fnValueTypeSelect } from '@/web/core/workflow/constants/dataType'; const EditFieldModal = ({ diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderToolInput/constants.ts b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderToolInput/constants.tsx similarity index 91% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderToolInput/constants.ts rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderToolInput/constants.tsx index a354dfc94..ae048b163 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderToolInput/constants.ts +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderToolInput/constants.tsx @@ -15,3 +15,7 @@ export const defaultEditFormData: FlowNodeInputItemType = { description: true } }; + +export default function Dom() { + return <>; +} diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderToolInput/index.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderToolInput/index.tsx similarity index 97% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderToolInput/index.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderToolInput/index.tsx index 0acd384c4..d546ccaed 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderToolInput/index.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderToolInput/index.tsx @@ -20,7 +20,7 @@ import MyIcon from '@fastgpt/web/components/common/Icon'; import dynamic from 'next/dynamic'; import { defaultEditFormData } from './constants'; import { useContextSelector } from 'use-context-selector'; -import { WorkflowContext } from '@/components/core/workflow/context'; +import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; const EditFieldModal = dynamic(() => import('./EditFieldModal')); const RenderToolInput = ({ diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderToolInput/type.d.ts b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderToolInput/type.d.ts similarity index 100% rename from projects/app/src/components/core/workflow/Flow/nodes/render/RenderToolInput/type.d.ts rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderToolInput/type.d.ts diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/ValueTypeLabel.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/ValueTypeLabel.tsx similarity index 100% rename from projects/app/src/components/core/workflow/Flow/nodes/render/ValueTypeLabel.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/ValueTypeLabel.tsx diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/VariableTable.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/VariableTable.tsx similarity index 100% rename from projects/app/src/components/core/workflow/Flow/nodes/render/VariableTable.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/VariableTable.tsx diff --git a/projects/app/src/components/core/workflow/constants.ts b/projects/app/src/pages/app/detail/components/WorkflowComponents/constants.tsx similarity index 91% rename from projects/app/src/components/core/workflow/constants.ts rename to projects/app/src/pages/app/detail/components/WorkflowComponents/constants.tsx index 1e3e05752..34b7396ba 100644 --- a/projects/app/src/components/core/workflow/constants.ts +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/constants.tsx @@ -21,3 +21,7 @@ export const defaultSkippedStatus: FlowNodeItemType['debugResult'] = { message: '', showResult: false }; + +export default function Dom() { + return <>; +} diff --git a/projects/app/src/components/core/workflow/context.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx similarity index 80% rename from projects/app/src/components/core/workflow/context.tsx rename to projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx index ea9c08488..d49ff621c 100644 --- a/projects/app/src/components/core/workflow/context.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx @@ -1,5 +1,9 @@ import { postWorkflowDebug } from '@/web/core/workflow/api'; -import { storeEdgesRenderEdge, storeNode2FlowNode } from '@/web/core/workflow/utils'; +import { + checkWorkflowNodeAndConnection, + storeEdgesRenderEdge, + storeNode2FlowNode +} from '@/web/core/workflow/utils'; import { getErrText } from '@fastgpt/global/common/error/utils'; import { NodeOutputKeyEnum, RuntimeEdgeStatusEnum } from '@fastgpt/global/core/workflow/constants'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; @@ -13,7 +17,7 @@ import { RuntimeEdgeItemType, StoreEdgeItemType } from '@fastgpt/global/core/wor import { FlowNodeChangeProps } from '@fastgpt/global/core/workflow/type/fe'; import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io'; import { useToast } from '@fastgpt/web/hooks/useToast'; -import { useMemoizedFn } from 'ahooks'; +import { useMemoizedFn, useUpdateEffect } from 'ahooks'; import React, { Dispatch, SetStateAction, @@ -38,13 +42,19 @@ import { checkNodeRunStatus } from '@fastgpt/global/core/workflow/runtime/utils' import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus'; import { getHandleId } from '@fastgpt/global/core/workflow/utils'; import { AppChatConfigType } from '@fastgpt/global/core/app/type'; -import { AppContext } from '@/web/core/app/context/appContext'; +import { AppContext } from '@/pages/app/detail/components/context'; +import ChatTest, { type ChatTestComponentRef } from './Flow/ChatTest'; +import { useDisclosure } from '@chakra-ui/react'; +import { uiWorkflow2StoreWorkflow } from './utils'; +import { useTranslation } from 'next-i18next'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { formatTime2HM } from '@fastgpt/global/common/string/time'; +import type { InitProps } from '@/pages/app/detail/components/PublishHistoriesSlider'; type OnChange = (changes: ChangesType[]) => void; type WorkflowContextType = { appId?: string; - mode: 'app' | 'plugin'; basicNodeTemplates: FlowNodeTemplateType[]; filterAppIds?: string[]; reactFlowWrapper: React.RefObject | null; @@ -92,6 +102,15 @@ type WorkflowContextType = { edges: StoreEdgeItemType[]; chatConfig?: AppChatConfigType; }) => Promise; + flowData2StoreDataAndCheck: (hideTip?: boolean) => + | { + nodes: StoreNodeItemType[]; + edges: StoreEdgeItemType[]; + } + | undefined; + onSaveWorkflow: () => Promise; + saveLabel: string; + isSaving: boolean; // debug workflowDebugData: @@ -114,16 +133,19 @@ type WorkflowContextType = { onStopNodeDebug: () => void; // version history - isShowVersionHistories: boolean; - setIsShowVersionHistories: React.Dispatch>; -}; + historiesDefaultData?: InitProps; + setHistoriesDefaultData: React.Dispatch>; -type ContextValueProps = Pick< - WorkflowContextType, - 'mode' | 'basicNodeTemplates' | 'filterAppIds' -> & { - appId?: string; - pluginId?: string; + // chat test + setWorkflowTestData: React.Dispatch< + React.SetStateAction< + | { + nodes: StoreNodeItemType[]; + edges: StoreEdgeItemType[]; + } + | undefined + > + >; }; type DebugDataType = { @@ -133,7 +155,7 @@ type DebugDataType = { }; export const WorkflowContext = createContext({ - mode: 'app', + isSaving: false, setConnectingEdge: function ( value: React.SetStateAction ): void { @@ -215,26 +237,44 @@ export const WorkflowContext = createContext({ onChangeNode: function (e: FlowNodeChangeProps): void { throw new Error('Function not implemented.'); }, - isShowVersionHistories: false, - setIsShowVersionHistories: function (value: React.SetStateAction): void { + setHoverEdgeId: function (value: React.SetStateAction): void { throw new Error('Function not implemented.'); }, - setHoverEdgeId: function (value: React.SetStateAction): void { + setWorkflowTestData: function ( + value: React.SetStateAction< + { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] } | undefined + > + ): void { + throw new Error('Function not implemented.'); + }, + flowData2StoreDataAndCheck: function (): + | { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] } + | undefined { + throw new Error('Function not implemented.'); + }, + onSaveWorkflow: function (): Promise { + throw new Error('Function not implemented.'); + }, + saveLabel: '', + historiesDefaultData: undefined, + setHistoriesDefaultData: function (value: React.SetStateAction): void { throw new Error('Function not implemented.'); } }); const WorkflowContextProvider = ({ children, - value + basicNodeTemplates }: { children: React.ReactNode; - value: ContextValueProps; + basicNodeTemplates: FlowNodeTemplateType[]; }) => { - const { appId, pluginId } = value; + const { t } = useTranslation(); const { toast } = useToast(); const reactFlowWrapper = useRef(null); - const setAppDetail = useContextSelector(AppContext, (v) => v.setAppDetail); + + const { appDetail, setAppDetail, updateAppDetail } = useContextSelector(AppContext, (v) => v); + const appId = appDetail._id; /* edge */ const [edges, setEdges, onEdgesChange] = useEdgesState([]); @@ -452,6 +492,57 @@ const WorkflowContextProvider = ({ } }); + /* ui flow to store data */ + const flowData2StoreDataAndCheck = useMemoizedFn((hideTip = false) => { + const checkResults = checkWorkflowNodeAndConnection({ nodes, edges }); + + if (!checkResults) { + const storeNodes = uiWorkflow2StoreWorkflow({ nodes, edges }); + + return storeNodes; + } else if (!hideTip) { + checkResults.forEach((nodeId) => onUpdateNodeError(nodeId, true)); + toast({ + status: 'warning', + title: t('core.workflow.Check Failed') + }); + } + }); + + /* save workflow */ + const [saveLabel, setSaveLabel] = useState(t('core.app.Onclick to save')); + const { runAsync: onSaveWorkflow, loading: isSaving } = useRequest2(async () => { + const { nodes } = await getWorkflowStore(); + + // version preview / debug mode, not save + if ( + appDetail.version !== 'v2' || + historiesDefaultData || + isSaving || + nodes.length === 0 || + !!workflowDebugData + ) + return; + + const storeWorkflow = uiWorkflow2StoreWorkflow({ nodes, edges }); + + try { + await updateAppDetail({ + ...storeWorkflow, + chatConfig: appDetail.chatConfig, + //@ts-ignore + version: 'v2' + }); + setSaveLabel( + t('core.app.Saved time', { + time: formatTime2HM() + }) + ); + } catch (error) {} + + return null; + }); + /* debug */ const [workflowDebugData, setWorkflowDebugData] = useState(); const onNextNodeDebug = useCallback( @@ -521,8 +612,7 @@ const WorkflowContextProvider = ({ nodes: runtimeNodes, edges: debugData.runtimeEdges, variables: {}, - appId, - pluginId + appId }); // console.log({ finishedEdges, finishedNodes, nextStepRunNodes, flowResponses }); // 5. Store debug result @@ -605,7 +695,7 @@ const WorkflowContextProvider = ({ console.log(error); } }, - [appId, onChangeNode, pluginId, setNodes, workflowDebugData] + [appId, onChangeNode, setNodes, workflowDebugData] ); const onStopNodeDebug = useCallback(() => { setWorkflowDebugData(undefined); @@ -644,65 +734,85 @@ const WorkflowContextProvider = ({ ); /* Version histories */ - const [isShowVersionHistories, setIsShowVersionHistories] = useState(false); + const [historiesDefaultData, setHistoriesDefaultData] = useState(); /* event bus */ useEffect(() => { eventBus.on(EventNameEnum.requestWorkflowStore, () => { eventBus.emit(EventNameEnum.receiveWorkflowStore, { - nodes + nodes, + edges }); }); return () => { eventBus.off(EventNameEnum.requestWorkflowStore); }; - }, [nodes]); + }, [edges, nodes]); + + /* chat test */ + const ChatTestRef = useRef(null); + const { isOpen: isOpenTest, onOpen: onOpenTest, onClose: onCloseTest } = useDisclosure(); + const [workflowTestData, setWorkflowTestData] = useState<{ + nodes: StoreNodeItemType[]; + edges: StoreEdgeItemType[]; + }>(); + useUpdateEffect(() => { + onOpenTest(); + }, [workflowTestData]); + + const value = { + appId, + reactFlowWrapper, + basicNodeTemplates, + // node + nodes, + setNodes, + onNodesChange, + nodeList, + hasToolNode, + hoverNodeId, + setHoverNodeId, + onUpdateNodeError, + onResetNode, + onChangeNode, + + // edge + edges, + setEdges, + hoverEdgeId, + setHoverEdgeId, + onEdgesChange, + connectingEdge, + setConnectingEdge, + onDelEdge, + + // function + onFixView, + splitToolInputs, + initData, + flowData2StoreDataAndCheck, + onSaveWorkflow, + isSaving, + saveLabel, + + // debug + workflowDebugData, + onNextNodeDebug, + onStartNodeDebug, + onStopNodeDebug, + + // version history + historiesDefaultData, + setHistoriesDefaultData, + + // chat test + setWorkflowTestData + }; return ( - + {children} + ); }; @@ -711,6 +821,7 @@ export default WorkflowContextProvider; type GetWorkflowStoreResponse = { nodes: Node[]; + edges: Edge[]; }; export const getWorkflowStore = () => new Promise((resolve) => { diff --git a/projects/app/src/components/core/workflow/utils.ts b/projects/app/src/pages/app/detail/components/WorkflowComponents/utils.tsx similarity index 96% rename from projects/app/src/components/core/workflow/utils.ts rename to projects/app/src/pages/app/detail/components/WorkflowComponents/utils.tsx index 1239d9f7f..b46947525 100644 --- a/projects/app/src/components/core/workflow/utils.ts +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/utils.tsx @@ -22,8 +22,7 @@ export const uiWorkflow2StoreWorkflow = ({ version: item.data.version, inputs: item.data.inputs, outputs: item.data.outputs, - pluginId: item.data.pluginId, - nodeVersion: item.data.nodeVersion + pluginId: item.data.pluginId })); // get all handle @@ -67,3 +66,7 @@ export const filterExportModules = (modules: StoreNodeItemType[]) => { return JSON.stringify(modules, null, 2); }; + +export default function Dom() { + return <>; +} diff --git a/projects/app/src/pages/app/detail/components/constants.tsx b/projects/app/src/pages/app/detail/components/constants.tsx new file mode 100644 index 000000000..fa25b22e6 --- /dev/null +++ b/projects/app/src/pages/app/detail/components/constants.tsx @@ -0,0 +1,33 @@ +import { BoxProps, FlexProps } from '@chakra-ui/react'; + +export const cardStyles: BoxProps = { + borderRadius: 'lg', + // overflow: 'hidden', + bg: 'white' +}; + +export const workflowBoxStyles: FlexProps = { + position: 'fixed', + top: 0, + right: 0, + bottom: 0, + left: 0, + flexDirection: 'column', + zIndex: 200, + bg: 'myGray.100' +}; + +export const publishStatusStyle = { + unPublish: { + colorSchema: 'adora' as any, + text: '未发布' + }, + published: { + colorSchema: 'green' as any, + text: '已发布' + } +}; + +export default function Dom() { + return <>; +} diff --git a/projects/app/src/pages/app/detail/components/context.tsx b/projects/app/src/pages/app/detail/components/context.tsx new file mode 100644 index 000000000..b77b01807 --- /dev/null +++ b/projects/app/src/pages/app/detail/components/context.tsx @@ -0,0 +1,207 @@ +import { Dispatch, ReactNode, SetStateAction, useCallback, useState } from 'react'; +import { createContext } from 'use-context-selector'; +import { defaultApp } from '@/web/core/app/constants'; +import { delAppById, getAppDetailById, putAppById } from '@/web/core/app/api'; +import { useRouter } from 'next/router'; +import { useTranslation } from 'next-i18next'; +import { AppChatConfigType, AppDetailType } from '@fastgpt/global/core/app/type'; +import { AppUpdateParams, PostPublishAppProps } from '@/global/core/app/api'; +import { postPublishApp } from '@/web/core/app/api/version'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import dynamic from 'next/dynamic'; +import { useDisclosure } from '@chakra-ui/react'; +import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; +import { useI18n } from '@/web/context/I18n'; +import { getAppLatestVersion } from '@/web/core/app/api/version'; +import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type'; +import type { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; + +const InfoModal = dynamic(() => import('./InfoModal')); +const TagsEditModal = dynamic(() => import('./TagsEditModal')); + +export enum TabEnum { + 'appEdit' = 'appEdit', + 'publish' = 'publish', + 'logs' = 'logs' +} + +type AppContextType = { + appId: string; + currentTab: TabEnum; + route2Tab: (currentTab: TabEnum) => void; + appDetail: AppDetailType; + setAppDetail: Dispatch>; + loadingApp: boolean; + updateAppDetail: (data: AppUpdateParams) => Promise; + onOpenInfoEdit: () => void; + onOpenTeamTagModal: () => void; + onDelApp: () => void; + onPublish: (data: PostPublishAppProps) => Promise; + appLatestVersion: + | { + nodes: StoreNodeItemType[]; + edges: StoreEdgeItemType[]; + chatConfig: AppChatConfigType; + } + | undefined; +}; + +export const AppContext = createContext({ + appId: '', + currentTab: TabEnum.appEdit, + route2Tab: function (currentTab: TabEnum): void { + throw new Error('Function not implemented.'); + }, + appDetail: defaultApp, + loadingApp: false, + updateAppDetail: function (data: AppUpdateParams): Promise { + throw new Error('Function not implemented.'); + }, + setAppDetail: function (value: SetStateAction): void { + throw new Error('Function not implemented.'); + }, + onOpenInfoEdit: function (): void { + throw new Error('Function not implemented.'); + }, + onOpenTeamTagModal: function (): void { + throw new Error('Function not implemented.'); + }, + onDelApp: function (): void { + throw new Error('Function not implemented.'); + }, + onPublish: function (data: PostPublishAppProps): Promise { + throw new Error('Function not implemented.'); + }, + appLatestVersion: undefined +}); + +const AppContextProvider = ({ children }: { children: ReactNode }) => { + const { t } = useTranslation(); + const { appT } = useI18n(); + const router = useRouter(); + const { appId, currentTab = TabEnum.appEdit } = router.query as { + appId: string; + currentTab: TabEnum; + }; + + const { + isOpen: isOpenInfoEdit, + onOpen: onOpenInfoEdit, + onClose: onCloseInfoEdit + } = useDisclosure(); + const { + isOpen: isOpenTeamTagModal, + onOpen: onOpenTeamTagModal, + onClose: onCloseTeamTagModal + } = useDisclosure(); + + const route2Tab = useCallback( + (currentTab: `${TabEnum}`) => { + router.push({ + query: { + ...router.query, + currentTab + } + }); + }, + [router] + ); + + const [appDetail, setAppDetail] = useState(defaultApp); + const { loading: loadingApp, runAsync: reLoadApp } = useRequest2( + () => { + if (appId) { + return getAppDetailById(appId); + } + return Promise.resolve(defaultApp); + }, + { + manual: false, + refreshDeps: [appId], + errorToast: t('core.app.error.Get app failed'), + onError(err: any) { + router.replace('/app/list'); + }, + onSuccess(res) { + setAppDetail(res); + } + } + ); + + const { data: appLatestVersion, run: reloadAppLatestVersion } = useRequest2( + () => getAppLatestVersion({ appId }), + { + manual: false + } + ); + + const { runAsync: updateAppDetail } = useRequest2(async (data: AppUpdateParams) => { + await putAppById(appId, data); + setAppDetail((state) => ({ + ...state, + ...data, + modules: data.nodes || state.modules + })); + }); + + const { runAsync: onPublish } = useRequest2( + async (data: PostPublishAppProps) => { + await postPublishApp(appId, data); + setAppDetail((state) => ({ + ...state, + ...data, + modules: data.nodes || state.modules + })); + reloadAppLatestVersion(); + }, + { + successToast: appT('Publish success') + } + ); + + const { openConfirm: openConfirmDel, ConfirmModal: ConfirmDelModal } = useConfirm({ + content: appT('Confirm Del App Tip'), + type: 'delete' + }); + const { runAsync: deleteApp } = useRequest2( + async () => { + if (!appDetail) return Promise.reject('Not load app'); + return delAppById(appDetail._id); + }, + { + onSuccess() { + router.replace(`/app/list`); + }, + successToast: t('common.Delete Success'), + errorToast: t('common.Delete Failed') + } + ); + const onDelApp = useCallback(() => openConfirmDel(deleteApp)(), [deleteApp, openConfirmDel]); + + const contextValue: AppContextType = { + appId, + currentTab, + route2Tab, + appDetail, + setAppDetail, + loadingApp, + updateAppDetail, + onOpenInfoEdit, + onOpenTeamTagModal, + onDelApp, + onPublish, + appLatestVersion + }; + + return ( + + {children} + {isOpenInfoEdit && } + {isOpenTeamTagModal && } + + + + ); +}; + +export default AppContextProvider; diff --git a/projects/app/src/pages/app/detail/components/useChatTest.tsx b/projects/app/src/pages/app/detail/components/useChatTest.tsx new file mode 100644 index 000000000..76e5aae95 --- /dev/null +++ b/projects/app/src/pages/app/detail/components/useChatTest.tsx @@ -0,0 +1,87 @@ +import { useUserStore } from '@/web/support/user/useUserStore'; +import React, { useCallback, useRef } from 'react'; +import ChatBox from '@/components/ChatBox'; +import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d'; +import { streamFetch } from '@/web/common/api/fetch'; +import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils'; +import { + getDefaultEntryNodeIds, + getMaxHistoryLimitFromNodes, + initWorkflowEdgeStatus, + storeNodes2RuntimeNodes +} from '@fastgpt/global/core/workflow/runtime/utils'; +import { useMemoizedFn } from 'ahooks'; +import { AppChatConfigType } from '@fastgpt/global/core/app/type'; +import { useContextSelector } from 'use-context-selector'; +import { AppContext } from './context'; +import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type'; +import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; + +export const useChatTest = ({ + nodes, + edges, + chatConfig +}: { + nodes: StoreNodeItemType[]; + edges: StoreEdgeItemType[]; + chatConfig: AppChatConfigType; +}) => { + const { userInfo } = useUserStore(); + const ChatBoxRef = useRef(null); + const { appDetail } = useContextSelector(AppContext, (v) => v); + + const startChat = useMemoizedFn( + async ({ chatList, controller, generatingMessage, variables }: StartChatFnProps) => { + /* get histories */ + let historyMaxLen = getMaxHistoryLimitFromNodes(nodes); + + const history = chatList.slice(-historyMaxLen - 2, -2); + + // 流请求,获取数据 + const { responseText, responseData } = await streamFetch({ + url: '/api/core/chat/chatTest', + data: { + history, + prompt: chatList[chatList.length - 2].value, + nodes: storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes)), + edges: initWorkflowEdgeStatus(edges), + variables, + appId: appDetail._id, + appName: `调试-${appDetail.name}` + }, + onMessage: generatingMessage, + abortCtrl: controller + }); + + return { responseText, responseData }; + } + ); + + const resetChatBox = useCallback(() => { + ChatBoxRef.current?.resetHistory([]); + ChatBoxRef.current?.resetVariables(); + }, []); + + const CustomChatBox = useMemoizedFn(() => ( + {}} + /> + )); + + return { + resetChatBox, + ChatBox: CustomChatBox + }; +}; + +export default function Dom() { + return <>; +} diff --git a/projects/app/src/pages/app/detail/index.tsx b/projects/app/src/pages/app/detail/index.tsx index a27b17d09..f808f9c38 100644 --- a/projects/app/src/pages/app/detail/index.tsx +++ b/projects/app/src/pages/app/detail/index.tsx @@ -1,195 +1,55 @@ -import React, { useMemo, useCallback } from 'react'; -import { useRouter } from 'next/router'; -import { Box, Flex, IconButton, useTheme } from '@chakra-ui/react'; +import React from 'react'; +import { Box, Flex } from '@chakra-ui/react'; import dynamic from 'next/dynamic'; -import { useSystemStore } from '@/web/common/system/useSystemStore'; - -import Tabs from '@/components/Tabs'; -import SideTabs from '@/components/SideTabs'; -import Avatar from '@/components/Avatar'; -import MyIcon from '@fastgpt/web/components/common/Icon'; -import PageContainer from '@/components/PageContainer'; import Loading from '@fastgpt/web/components/common/MyLoading'; -import SimpleEdit from './components/SimpleEdit'; import { serviceSideProps } from '@/web/common/utils/i18n'; -import Head from 'next/head'; -import { useTranslation } from 'next-i18next'; -import { useI18n } from '@/web/context/I18n'; -import { AppContext, AppContextProvider } from '@/web/core/app/context/appContext'; +import NextHead from '@/components/common/NextHead'; import { useContextSelector } from 'use-context-selector'; +import AppContextProvider, { AppContext, TabEnum } from './components/context'; +import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; -const FlowEdit = dynamic(() => import('./components/FlowEdit'), { - loading: () => +const SimpleEdit = dynamic(() => import('./components/SimpleApp'), { + loading: () => +}); +const Workflow = dynamic(() => import('./components/Workflow'), { + loading: () => +}); +const Plugin = dynamic(() => import('./components/Plugin'), { + loading: () => }); -const Publish = dynamic(() => import('./components/Publish'), {}); -const Logs = dynamic(() => import('./components/Logs'), {}); -enum TabEnum { - 'simpleEdit' = 'simpleEdit', - 'adEdit' = 'adEdit', - 'publish' = 'publish', - 'logs' = 'logs', - 'startChat' = 'startChat' -} - -const AppDetail = ({ appId, currentTab }: { appId: string; currentTab: TabEnum }) => { - const { t } = useTranslation(); - const { appT } = useI18n(); - const router = useRouter(); - const theme = useTheme(); - const { feConfigs } = useSystemStore(); - const { appDetail, loadingApp } = useContextSelector(AppContext, (e) => e); - - const setCurrentTab = useCallback( - (tab: TabEnum) => { - router.push({ - query: { - ...router.query, - currentTab: tab - } - }); - }, - [router] - ); - - const tabList = useMemo( - () => [ - { - label: t('core.app.navbar.Simple mode'), - id: TabEnum.simpleEdit, - icon: 'common/overviewLight' - }, - - { - label: t('core.app.navbar.Flow mode'), - id: TabEnum.adEdit, - icon: 'core/modules/flowLight' - }, - ...(appDetail.permission.hasManagePer - ? [ - { - label: t('core.app.navbar.Publish app'), - id: TabEnum.publish, - icon: 'support/outlink/shareLight' - }, - { label: appT('Chat logs'), id: TabEnum.logs, icon: 'core/app/logsLight' } - ] - : []), - { label: t('core.Start chat'), id: TabEnum.startChat, icon: 'core/chat/chatLight' } - ], - [appDetail.permission.hasManagePer, appT, t] - ); - - const onCloseFlowEdit = useCallback(() => setCurrentTab(TabEnum.simpleEdit), [setCurrentTab]); +const AppDetail = () => { + const { appDetail } = useContextSelector(AppContext, (e) => e); return ( <> - - {appDetail.name} - - - {!loadingApp && ( - - {/* pc tab */} - - - - - {appDetail.name} - - - { - if (e === 'startChat') { - router.push(`/chat?appId=${appId}`); - } else { - setCurrentTab(e); - } - }} - /> - router.replace('/app/list')} - > - } - bg={'white'} - boxShadow={'1px 1px 9px rgba(0,0,0,0.15)'} - size={'smSquare'} - borderRadius={'50%'} - aria-label={''} - /> - {appT('My Apps')} - - - {/* phone tab */} - - - {appDetail.name} - - { - if (e === 'startChat') { - router.push(`/chat?appId=${appId}`); - } else { - setCurrentTab(e); - } - }} - /> - - - {currentTab === TabEnum.simpleEdit && } - {currentTab === TabEnum.adEdit && appDetail && } - {currentTab === TabEnum.logs && } - {currentTab === TabEnum.publish && } - - + + + {!appDetail._id ? ( + + ) : ( + <> + {appDetail.type === AppTypeEnum.simple && } + {appDetail.type === AppTypeEnum.workflow && } + {appDetail.type === AppTypeEnum.plugin && } + )} - + ); }; -const Provider = ({ appId, currentTab }: { appId: string; currentTab: TabEnum }) => { +const Provider = () => { return ( - - + + ); }; export async function getServerSideProps(context: any) { - const currentTab = context?.query?.currentTab || TabEnum.simpleEdit; - const appId = context?.query?.appId || ''; - return { props: { - currentTab, - appId, ...(await serviceSideProps(context, ['app', 'chat', 'file', 'publish', 'workflow'])) } }; diff --git a/projects/app/src/pages/app/list/component/CreateModal.tsx b/projects/app/src/pages/app/list/components/CreateModal.tsx similarity index 81% rename from projects/app/src/pages/app/list/component/CreateModal.tsx rename to projects/app/src/pages/app/list/components/CreateModal.tsx index 644c061dc..730389b5d 100644 --- a/projects/app/src/pages/app/list/component/CreateModal.tsx +++ b/projects/app/src/pages/app/list/components/CreateModal.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, useRef } from 'react'; import { Box, Flex, @@ -17,7 +17,7 @@ import { getErrText } from '@fastgpt/global/common/error/utils'; import { useToast } from '@fastgpt/web/hooks/useToast'; import { postCreateApp } from '@/web/core/app/api'; import { useRouter } from 'next/router'; -import { appTemplates } from '@/web/core/app/templates'; +import { simpleBotTemplates, workflowTemplates, pluginTemplates } from '@/web/core/app/templates'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useRequest } from '@fastgpt/web/hooks/useRequest'; import Avatar from '@/components/Avatar'; @@ -27,6 +27,8 @@ import { useTranslation } from 'next-i18next'; import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants'; import { useContextSelector } from 'use-context-selector'; import { AppListContext } from './context'; +import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; +import { useI18n } from '@/web/context/I18n'; type FormType = { avatar: string; @@ -34,20 +36,40 @@ type FormType = { templateId: string; }; -const CreateModal = ({ onClose }: { onClose: () => void }) => { +export type CreateAppType = AppTypeEnum.simple | AppTypeEnum.workflow | AppTypeEnum.plugin; + +const CreateModal = ({ onClose, type }: { type: CreateAppType; onClose: () => void }) => { const { t } = useTranslation(); + const { appT } = useI18n(); const { toast } = useToast(); const router = useRouter(); const { parentId, loadMyApps } = useContextSelector(AppListContext, (v) => v); - - const theme = useTheme(); const { isPc } = useSystemStore(); + const typeMap = useRef({ + [AppTypeEnum.simple]: { + icon: 'core/app/simpleBot', + title: appT('type.Create simple bot'), + templates: simpleBotTemplates + }, + [AppTypeEnum.workflow]: { + icon: 'core/app/type/workflowFill', + title: appT('type.Create workflow bot'), + templates: workflowTemplates + }, + [AppTypeEnum.plugin]: { + icon: 'core/app/type/pluginFill', + title: appT('type.Create plugin bot'), + templates: pluginTemplates + } + }); + const typeData = typeMap.current[type]; + const { register, setValue, watch, handleSubmit } = useForm({ defaultValues: { avatar: '', name: '', - templateId: appTemplates[0].id + templateId: typeData.templates[0].id } }); const avatar = watch('avatar'); @@ -82,7 +104,7 @@ const CreateModal = ({ onClose }: { onClose: () => void }) => { const { mutate: onclickCreate, isLoading: creating } = useRequest({ mutationFn: async (data: FormType) => { - const template = appTemplates.find((item) => item.id === data.templateId); + const template = typeData.templates.find((item) => item.id === data.templateId); if (!template) { return Promise.reject(t('core.dataset.error.Template does not exist')); } @@ -106,8 +128,8 @@ const CreateModal = ({ onClose }: { onClose: () => void }) => { return ( void }) => { gridTemplateColumns={['repeat(1,1fr)', 'repeat(2,1fr)']} gridGap={[2, 4]} > - {appTemplates.map((item) => ( + {typeData.templates.map((item) => ( void; - onSuccess: () => void; - onDelete: () => void; }) => { const { t } = useTranslation(); const { toast } = useToast(); const isEdit = !!defaultPlugin.id; - const [refresh, setRefresh] = useState(false); + const { parentId, loadMyApps } = useContextSelector(AppListContext, (v) => v); const [schemaUrl, setSchemaUrl] = useState(''); const [customHeaders, setCustomHeaders] = useState<{ key: string; value: string }[]>(() => { - const keyValue = JSON.parse(defaultPlugin.metadata?.customHeaders || '{}'); + const keyValue = JSON.parse(defaultPlugin.pluginData?.customHeaders || '{}'); return Object.keys(keyValue).map((key) => ({ key, value: keyValue[key] })); }); const [updateTrigger, setUpdateTrigger] = useState(false); - const { register, setValue, getValues, handleSubmit, watch } = useForm({ + const { register, setValue, handleSubmit, watch } = useForm({ defaultValues: defaultPlugin }); - const apiSchemaStr = watch('metadata.apiSchemaStr'); + const avatar = watch('avatar'); + const apiSchemaStr = watch('pluginData.apiSchemaStr'); const [apiData, setApiData] = useState({ pathData: [], serverPath: '' }); const { mutate: onCreate, isLoading: isCreating } = useRequest({ - mutationFn: async (data: CreateOnePluginParams) => { - return postCreatePlugin(data); + mutationFn: async (data: EditHttpPluginProps) => { + return postCreateHttpPlugin({ + parentId, + name: data.name, + intro: data.intro, + avatar: data.avatar, + pluginData: { + apiSchemaStr: data.pluginData?.apiSchemaStr, + customHeaders: data.pluginData?.customHeaders + } + }); }, onSuccess() { - onSuccess(); + loadMyApps(); onClose(); }, successToast: t('common.Create Success'), @@ -99,29 +106,25 @@ const HttpPluginEditModal = ({ }); const { mutate: updatePlugins, isLoading: isUpdating } = useRequest({ - mutationFn: async (data: EditFormType) => { - if (!data.id) return Promise.resolve(''); - return putUpdatePlugin({ - id: data.id, + mutationFn: async (data: EditHttpPluginProps) => { + if (!data.id || !data.pluginData) return Promise.resolve(''); + + return putUpdateHttpPlugin({ + appId: data.id, name: data.name, - avatar: data.avatar, intro: data.intro, - metadata: data.metadata + avatar: data.avatar, + pluginData: data.pluginData }); }, onSuccess() { + loadMyApps(); onClose(); - onSuccess(); }, successToast: t('common.Update Success'), errorToast: t('common.Update Failed') }); - const { openConfirm, ConfirmModal } = useConfirm({ - title: t('common.Delete Tip'), - content: t('core.plugin.Delete http plugin') - }); - const { File, onOpen: onOpenSelectFile } = useSelectFile({ fileType: 'image/*', multiple: false @@ -139,7 +142,6 @@ const HttpPluginEditModal = ({ maxH: 300 }); setValue('avatar', src); - setRefresh((state) => !state); } catch (err: any) { toast({ title: getErrText(err, t('common.Select File Failed')), @@ -150,18 +152,6 @@ const HttpPluginEditModal = ({ [setValue, t, toast] ); - const { mutate: onClickDelPlugin, isLoading: isDeleting } = useRequest({ - mutationFn: async () => { - if (!defaultPlugin.id) return; - - await delOnePlugin(defaultPlugin.id); - onDelete(); - onClose(); - }, - successToast: t('common.Delete Success'), - errorToast: t('common.Delete Failed') - }); - /* load api from url */ const { mutate: onClickUrlLoadApi, isLoading: isLoadingUrlApi } = useRequest({ mutationFn: async () => { @@ -173,7 +163,7 @@ const HttpPluginEditModal = ({ } const schema = await getApiSchemaByUrl(schemaUrl); - setValue('metadata.apiSchemaStr', JSON.stringify(schema, null, 2)); + setValue('pluginData.apiSchemaStr', JSON.stringify(schema, null, 2)); }, errorToast: t('plugin.Invalid Schema') }); @@ -209,7 +199,7 @@ const HttpPluginEditModal = ({ + <> + + {t('plugin.Intro')} + +