From 45deaee762e1d8407d09dc91fb4f649015713421 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Mon, 27 May 2024 21:57:08 +0800 Subject: [PATCH] feat: workflow new nodes (#4683) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Joel Co-authored-by: Patryk Garstecki Co-authored-by: Sebastian.W Co-authored-by: 呆萌闷油瓶 <253605712@qq.com> Co-authored-by: takatost Co-authored-by: rechardwang Co-authored-by: Nite Knite Co-authored-by: Chenhe Gu Co-authored-by: Joshua <138381132+joshua20231026@users.noreply.github.com> Co-authored-by: Weaxs <459312872@qq.com> Co-authored-by: Ikko Eltociear Ashimine Co-authored-by: leejoo0 <81673835+leejoo0@users.noreply.github.com> Co-authored-by: JzoNg Co-authored-by: sino Co-authored-by: Vikey Chen Co-authored-by: wanghl Co-authored-by: Haolin Wang-汪皓临 Co-authored-by: Zixuan Cheng <61724187+Theysua@users.noreply.github.com> Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: Bowen Liang Co-authored-by: Bowen Liang Co-authored-by: fanghongtai <42790567+fanghongtai@users.noreply.github.com> Co-authored-by: wxfanghongtai Co-authored-by: Matri Co-authored-by: Benjamin --- .../datasets/NewDatasetCard.tsx | 2 +- web/app/(commonLayout)/tools/custom/page.tsx | 10 - web/app/(commonLayout)/tools/page.tsx | 11 +- .../(commonLayout)/tools/third-part/page.tsx | 10 - .../components/app/app-publisher/index.tsx | 27 +- .../agent/agent-tools/choose-tool/index.tsx | 77 --- .../config/agent/agent-tools/index.tsx | 28 +- .../agent-tools/setting-built-in-tool.tsx | 15 +- web/app/components/app/log/list.tsx | 4 +- web/app/components/app/store.ts | 16 +- .../base/chat/chat/answer/index.tsx | 9 +- .../chat/chat/answer/workflow-process.tsx | 17 +- web/app/components/base/chat/chat/hooks.ts | 39 ++ web/app/components/base/drawer-plus/index.tsx | 12 +- web/app/components/base/drawer/index.tsx | 11 +- .../base/icons/assets/public/common/d.svg | 16 + .../icons/assets/vender/line/files/folder.svg | 5 + .../assets/vender/line/others/apps-02.svg | 5 + .../assets/vender/line/others/colors.svg | 10 + .../assets/vender/line/others/exchange-02.svg | 3 + .../assets/vender/line/others/file-code.svg | 3 + .../icons/assets/vender/line/others/tools.svg | 14 + .../vender/workflow/iteration-start.svg | 5 + .../assets/vender/workflow/iteration.svg | 5 + .../vender/workflow/parameter-extractor.svg | 28 + .../base/icons/src/public/common/D.json | 125 ++++ .../base/icons/src/public/common/D.tsx | 16 + .../base/icons/src/public/common/index.ts | 1 + .../icons/src/vender/line/files/Folder.json | 39 ++ .../icons/src/vender/line/files/Folder.tsx | 16 + .../base/icons/src/vender/line/files/index.ts | 1 + .../icons/src/vender/line/others/Apps02.json | 36 ++ .../icons/src/vender/line/others/Apps02.tsx | 16 + .../icons/src/vender/line/others/Colors.json | 66 ++ .../icons/src/vender/line/others/Colors.tsx | 16 + .../src/vender/line/others/Exchange02.json | 26 + .../src/vender/line/others/Exchange02.tsx | 16 + .../src/vender/line/others/FileCode.json | 26 + .../icons/src/vender/line/others/FileCode.tsx | 16 + .../icons/src/vender/line/others/Tools.json | 119 ++++ .../icons/src/vender/line/others/Tools.tsx | 16 + .../icons/src/vender/line/others/index.ts | 5 + .../icons/src/vender/workflow/Iteration.json | 36 ++ .../icons/src/vender/workflow/Iteration.tsx | 16 + .../src/vender/workflow/IterationStart.json | 36 ++ .../src/vender/workflow/IterationStart.tsx | 16 + .../vender/workflow/ParameterExtractor.json | 266 ++++++++ .../vender/workflow/ParameterExtractor.tsx | 16 + .../base/icons/src/vender/workflow/index.ts | 3 + .../base/message-log-modal/index.tsx | 46 +- web/app/components/base/panel/index.tsx | 78 --- .../base/portal-to-follow-elem/index.tsx | 4 +- .../workflow-variable-block/component.tsx | 10 +- web/app/components/base/select/index.tsx | 5 +- web/app/components/header/HeaderWrapper.tsx | 2 +- .../model-provider-page/declarations.ts | 1 + .../model-parameter-modal/index.tsx | 2 +- .../share/text-generation/result/index.tsx | 32 + web/app/components/tools/add-tool-modal/D.png | Bin 0 -> 1139 bytes .../tools/add-tool-modal/category.tsx | 70 +++ .../components/tools/add-tool-modal/empty.png | Bin 0 -> 30290 bytes .../components/tools/add-tool-modal/empty.tsx | 15 + .../components/tools/add-tool-modal/index.tsx | 235 +++++++ .../components/tools/add-tool-modal/tools.tsx | 146 +++++ .../components/tools/add-tool-modal/type.tsx | 34 + web/app/components/tools/contribute.tsx | 31 - .../config-credentials.tsx | 3 + .../edit-custom-collection-modal/index.tsx | 26 +- .../edit-custom-collection-modal/test-api.tsx | 4 + web/app/components/tools/index.tsx | 259 -------- .../components/tools/info/no-custom-tool.tsx | 38 -- .../components/tools/info/no-search-res.tsx | 38 -- web/app/components/tools/labels/constant.ts | 6 + web/app/components/tools/labels/filter.tsx | 144 +++++ web/app/components/tools/labels/selector.tsx | 128 ++++ web/app/components/tools/labels/store.ts | 15 + .../tools/no-custom-tool-placeholder.tsx | 26 - web/app/components/tools/provider-list.tsx | 117 ++++ web/app/components/tools/provider/card.tsx | 83 +++ .../components/tools/provider/contribute.tsx | 38 ++ .../tools/provider/custom-create-card.tsx | 70 +++ web/app/components/tools/provider/detail.tsx | 343 ++++++++++ web/app/components/tools/provider/grid_bg.svg | 14 + .../components/tools/provider/tool-item.tsx | 53 ++ web/app/components/tools/search.tsx | 41 -- .../setting/build-in/config-credentials.tsx | 6 +- web/app/components/tools/tool-list/header.tsx | 77 --- web/app/components/tools/tool-list/index.tsx | 220 ------- web/app/components/tools/tool-list/item.tsx | 84 --- .../components/tools/tool-nav-list/index.tsx | 28 - .../components/tools/tool-nav-list/item.tsx | 50 -- web/app/components/tools/types.ts | 48 ++ .../tools/workflow-tool/configure-button.tsx | 225 +++++++ .../workflow-tool/confirm-modal/index.tsx | 47 ++ .../confirm-modal/style.module.css | 3 + .../components/tools/workflow-tool/index.tsx | 282 +++++++++ .../tools/workflow-tool/method-selector.tsx | 77 +++ web/app/components/workflow/block-icon.tsx | 8 + .../workflow/block-selector/all-tools.tsx | 76 +++ .../workflow/block-selector/constants.tsx | 14 +- .../workflow/block-selector/hooks.ts | 36 +- .../workflow/block-selector/index.tsx | 3 + .../workflow/block-selector/tabs.tsx | 62 +- .../workflow/block-selector/tools.tsx | 30 +- .../workflow/block-selector/types.ts | 10 +- web/app/components/workflow/constants.ts | 72 ++- web/app/components/workflow/custom-edge.tsx | 23 +- web/app/components/workflow/header/index.tsx | 37 ++ web/app/components/workflow/hooks/index.ts | 1 + .../workflow/hooks/use-checklist.ts | 18 +- .../workflow/hooks/use-edges-interactions.ts | 59 -- .../components/workflow/hooks/use-helpline.ts | 114 ++++ .../workflow/hooks/use-node-data-update.ts | 2 +- .../workflow/hooks/use-nodes-data.ts | 32 +- .../workflow/hooks/use-nodes-interactions.ts | 594 +++++++++++++----- .../workflow/hooks/use-nodes-layout.ts | 96 +++ .../workflow/hooks/use-workflow-run.ts | 244 +++++-- .../components/workflow/hooks/use-workflow.ts | 133 +++- web/app/components/workflow/index.tsx | 21 +- .../add-variable-popup-with-position.tsx | 123 ++++ .../_base/components/add-variable-popup.tsx | 36 ++ .../components/before-run-form/form-item.tsx | 41 +- .../_base/components/before-run-form/form.tsx | 10 +- .../nodes/_base/components/editor/base.tsx | 3 + .../workflow/nodes/_base/components/field.tsx | 8 +- .../components/list-no-data-placeholder.tsx | 18 + .../nodes/_base/components/next-step/item.tsx | 12 +- .../nodes/_base/components/node-handle.tsx | 25 +- .../nodes/_base/components/node-resizer.tsx | 51 ++ .../panel-operator/change-block.tsx | 27 +- .../panel-operator/panel-operator-popup.tsx | 22 +- .../readonly-input-with-select-var.tsx | 4 +- .../nodes/_base/components/variable/utils.ts | 382 ++++++++++- .../variable/var-reference-picker.tsx | 269 ++++---- .../variable/var-reference-popup.tsx | 4 +- .../_base/hooks/use-available-var-list.ts | 23 +- .../nodes/_base/hooks/use-node-info.ts | 20 + .../nodes/_base/hooks/use-one-step-run.ts | 163 ++++- .../components/workflow/nodes/_base/node.tsx | 100 ++- .../workflow/nodes/answer/panel.tsx | 4 +- .../components/workflow/nodes/constants.ts | 10 + .../components/workflow/nodes/end/node.tsx | 35 +- .../if-else/components/condition-item.tsx | 4 +- .../workflow/nodes/if-else/default.ts | 2 +- .../workflow/nodes/if-else/use-config.ts | 13 +- web/app/components/workflow/nodes/index.tsx | 12 +- .../workflow/nodes/iteration/add-block.tsx | 128 ++++ .../workflow/nodes/iteration/default.ts | 39 ++ .../workflow/nodes/iteration/insert-block.tsx | 61 ++ .../workflow/nodes/iteration/node.tsx | 49 ++ .../workflow/nodes/iteration/panel.tsx | 136 ++++ .../workflow/nodes/iteration/types.ts | 15 + .../workflow/nodes/iteration/use-config.ts | 215 +++++++ .../nodes/iteration/use-interactions.ts | 142 +++++ .../nodes/llm/components/config-prompt.tsx | 6 +- .../components/workflow/nodes/llm/panel.tsx | 4 +- .../workflow/nodes/llm/use-config.ts | 4 +- .../extract-parameter/import-from-tool.tsx | 92 +++ .../components/extract-parameter/item.tsx | 59 ++ .../components/extract-parameter/list.tsx | 85 +++ .../components/extract-parameter/update.tsx | 193 ++++++ .../components/reasoning-mode-picker.tsx | 70 +++ .../nodes/parameter-extractor/default.ts | 64 ++ .../nodes/parameter-extractor/node.tsx | 31 + .../nodes/parameter-extractor/panel.tsx | 227 +++++++ .../nodes/parameter-extractor/types.ts | 33 + .../nodes/parameter-extractor/use-config.ts | 258 ++++++++ .../components/advanced-setting.tsx | 14 +- .../nodes/question-classifier/default.ts | 2 +- .../nodes/tool/components/input-var-list.tsx | 71 ++- .../components/workflow/nodes/tool/types.ts | 3 +- .../workflow/nodes/tool/use-config.ts | 15 +- .../components/add-variable/index.tsx | 87 +++ .../components/node-group-item.tsx | 107 ++++ .../components/node-handle.tsx | 69 ++ .../components/node-variable-item.tsx | 36 ++ .../components/var-group-item.tsx | 176 ++++++ .../components/var-list/index.tsx | 12 +- .../components/var-list/use-var-list.ts | 6 +- .../nodes/variable-assigner/default.ts | 4 +- .../workflow/nodes/variable-assigner/hooks.ts | 243 +++++++ .../workflow/nodes/variable-assigner/node.tsx | 112 ++-- .../nodes/variable-assigner/panel.tsx | 163 +++-- .../workflow/nodes/variable-assigner/types.ts | 11 +- .../nodes/variable-assigner/use-config.ts | 217 +++++-- .../workflow/nodes/variable-assigner/utils.ts | 15 +- .../workflow/panel/debug-and-preview/hooks.ts | 96 ++- web/app/components/workflow/panel/index.tsx | 4 +- .../workflow/panel/inputs-panel.tsx | 2 +- .../workflow/panel/workflow-preview.tsx | 257 ++++---- web/app/components/workflow/run/index.tsx | 96 ++- .../workflow/run/iteration-result-panel.tsx | 92 +++ web/app/components/workflow/run/node.tsx | 41 +- .../components/workflow/run/tracing-panel.tsx | 5 +- web/app/components/workflow/store.ts | 38 ++ web/app/components/workflow/types.ts | 19 +- web/app/components/workflow/utils.ts | 45 +- web/i18n/en-US/app-debug.ts | 1 + web/i18n/en-US/tools.ts | 34 + web/i18n/en-US/workflow.ts | 69 +- web/i18n/zh-Hans/app-debug.ts | 1 + web/i18n/zh-Hans/tools.ts | 34 + web/i18n/zh-Hans/workflow.ts | 69 +- web/package.json | 4 +- web/service/base.ts | 26 +- web/service/share.ts | 10 +- web/service/tools.ts | 54 +- web/service/workflow.ts | 4 + web/types/workflow.ts | 55 ++ web/yarn.lock | 107 ++-- 210 files changed, 9951 insertions(+), 2223 deletions(-) delete mode 100644 web/app/(commonLayout)/tools/custom/page.tsx delete mode 100644 web/app/(commonLayout)/tools/third-part/page.tsx delete mode 100644 web/app/components/app/configuration/config/agent/agent-tools/choose-tool/index.tsx create mode 100644 web/app/components/base/icons/assets/public/common/d.svg create mode 100644 web/app/components/base/icons/assets/vender/line/files/folder.svg create mode 100644 web/app/components/base/icons/assets/vender/line/others/apps-02.svg create mode 100644 web/app/components/base/icons/assets/vender/line/others/colors.svg create mode 100644 web/app/components/base/icons/assets/vender/line/others/exchange-02.svg create mode 100644 web/app/components/base/icons/assets/vender/line/others/file-code.svg create mode 100644 web/app/components/base/icons/assets/vender/line/others/tools.svg create mode 100644 web/app/components/base/icons/assets/vender/workflow/iteration-start.svg create mode 100644 web/app/components/base/icons/assets/vender/workflow/iteration.svg create mode 100644 web/app/components/base/icons/assets/vender/workflow/parameter-extractor.svg create mode 100644 web/app/components/base/icons/src/public/common/D.json create mode 100644 web/app/components/base/icons/src/public/common/D.tsx create mode 100644 web/app/components/base/icons/src/vender/line/files/Folder.json create mode 100644 web/app/components/base/icons/src/vender/line/files/Folder.tsx create mode 100644 web/app/components/base/icons/src/vender/line/others/Apps02.json create mode 100644 web/app/components/base/icons/src/vender/line/others/Apps02.tsx create mode 100644 web/app/components/base/icons/src/vender/line/others/Colors.json create mode 100644 web/app/components/base/icons/src/vender/line/others/Colors.tsx create mode 100644 web/app/components/base/icons/src/vender/line/others/Exchange02.json create mode 100644 web/app/components/base/icons/src/vender/line/others/Exchange02.tsx create mode 100644 web/app/components/base/icons/src/vender/line/others/FileCode.json create mode 100644 web/app/components/base/icons/src/vender/line/others/FileCode.tsx create mode 100644 web/app/components/base/icons/src/vender/line/others/Tools.json create mode 100644 web/app/components/base/icons/src/vender/line/others/Tools.tsx create mode 100644 web/app/components/base/icons/src/vender/workflow/Iteration.json create mode 100644 web/app/components/base/icons/src/vender/workflow/Iteration.tsx create mode 100644 web/app/components/base/icons/src/vender/workflow/IterationStart.json create mode 100644 web/app/components/base/icons/src/vender/workflow/IterationStart.tsx create mode 100644 web/app/components/base/icons/src/vender/workflow/ParameterExtractor.json create mode 100644 web/app/components/base/icons/src/vender/workflow/ParameterExtractor.tsx delete mode 100644 web/app/components/base/panel/index.tsx create mode 100644 web/app/components/tools/add-tool-modal/D.png create mode 100644 web/app/components/tools/add-tool-modal/category.tsx create mode 100644 web/app/components/tools/add-tool-modal/empty.png create mode 100644 web/app/components/tools/add-tool-modal/empty.tsx create mode 100644 web/app/components/tools/add-tool-modal/index.tsx create mode 100644 web/app/components/tools/add-tool-modal/tools.tsx create mode 100644 web/app/components/tools/add-tool-modal/type.tsx delete mode 100644 web/app/components/tools/contribute.tsx delete mode 100644 web/app/components/tools/index.tsx delete mode 100644 web/app/components/tools/info/no-custom-tool.tsx delete mode 100644 web/app/components/tools/info/no-search-res.tsx create mode 100644 web/app/components/tools/labels/constant.ts create mode 100644 web/app/components/tools/labels/filter.tsx create mode 100644 web/app/components/tools/labels/selector.tsx create mode 100644 web/app/components/tools/labels/store.ts delete mode 100644 web/app/components/tools/no-custom-tool-placeholder.tsx create mode 100644 web/app/components/tools/provider-list.tsx create mode 100644 web/app/components/tools/provider/card.tsx create mode 100644 web/app/components/tools/provider/contribute.tsx create mode 100644 web/app/components/tools/provider/custom-create-card.tsx create mode 100644 web/app/components/tools/provider/detail.tsx create mode 100644 web/app/components/tools/provider/grid_bg.svg create mode 100644 web/app/components/tools/provider/tool-item.tsx delete mode 100644 web/app/components/tools/search.tsx delete mode 100644 web/app/components/tools/tool-list/header.tsx delete mode 100644 web/app/components/tools/tool-list/index.tsx delete mode 100644 web/app/components/tools/tool-list/item.tsx delete mode 100644 web/app/components/tools/tool-nav-list/index.tsx delete mode 100644 web/app/components/tools/tool-nav-list/item.tsx create mode 100644 web/app/components/tools/workflow-tool/configure-button.tsx create mode 100644 web/app/components/tools/workflow-tool/confirm-modal/index.tsx create mode 100644 web/app/components/tools/workflow-tool/confirm-modal/style.module.css create mode 100644 web/app/components/tools/workflow-tool/index.tsx create mode 100644 web/app/components/tools/workflow-tool/method-selector.tsx create mode 100644 web/app/components/workflow/block-selector/all-tools.tsx create mode 100644 web/app/components/workflow/hooks/use-helpline.ts create mode 100644 web/app/components/workflow/hooks/use-nodes-layout.ts create mode 100644 web/app/components/workflow/nodes/_base/components/add-variable-popup-with-position.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/add-variable-popup.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/list-no-data-placeholder.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/node-resizer.tsx create mode 100644 web/app/components/workflow/nodes/_base/hooks/use-node-info.ts create mode 100644 web/app/components/workflow/nodes/iteration/add-block.tsx create mode 100644 web/app/components/workflow/nodes/iteration/default.ts create mode 100644 web/app/components/workflow/nodes/iteration/insert-block.tsx create mode 100644 web/app/components/workflow/nodes/iteration/node.tsx create mode 100644 web/app/components/workflow/nodes/iteration/panel.tsx create mode 100644 web/app/components/workflow/nodes/iteration/types.ts create mode 100644 web/app/components/workflow/nodes/iteration/use-config.ts create mode 100644 web/app/components/workflow/nodes/iteration/use-interactions.ts create mode 100644 web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/import-from-tool.tsx create mode 100644 web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/item.tsx create mode 100644 web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/list.tsx create mode 100644 web/app/components/workflow/nodes/parameter-extractor/components/extract-parameter/update.tsx create mode 100644 web/app/components/workflow/nodes/parameter-extractor/components/reasoning-mode-picker.tsx create mode 100644 web/app/components/workflow/nodes/parameter-extractor/default.ts create mode 100644 web/app/components/workflow/nodes/parameter-extractor/node.tsx create mode 100644 web/app/components/workflow/nodes/parameter-extractor/panel.tsx create mode 100644 web/app/components/workflow/nodes/parameter-extractor/types.ts create mode 100644 web/app/components/workflow/nodes/parameter-extractor/use-config.ts create mode 100644 web/app/components/workflow/nodes/variable-assigner/components/add-variable/index.tsx create mode 100644 web/app/components/workflow/nodes/variable-assigner/components/node-group-item.tsx create mode 100644 web/app/components/workflow/nodes/variable-assigner/components/node-handle.tsx create mode 100644 web/app/components/workflow/nodes/variable-assigner/components/node-variable-item.tsx create mode 100644 web/app/components/workflow/nodes/variable-assigner/components/var-group-item.tsx create mode 100644 web/app/components/workflow/nodes/variable-assigner/hooks.ts create mode 100644 web/app/components/workflow/run/iteration-result-panel.tsx diff --git a/web/app/(commonLayout)/datasets/NewDatasetCard.tsx b/web/app/(commonLayout)/datasets/NewDatasetCard.tsx index 5ce79adadc..f3e34ff7e2 100644 --- a/web/app/(commonLayout)/datasets/NewDatasetCard.tsx +++ b/web/app/(commonLayout)/datasets/NewDatasetCard.tsx @@ -9,7 +9,7 @@ const CreateAppCard = forwardRef((_, ref) => { return ( -
+
diff --git a/web/app/(commonLayout)/tools/custom/page.tsx b/web/app/(commonLayout)/tools/custom/page.tsx deleted file mode 100644 index c666bf347c..0000000000 --- a/web/app/(commonLayout)/tools/custom/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' - -const Custom = () => { - return ( -
- Custom -
- ) -} -export default Custom diff --git a/web/app/(commonLayout)/tools/page.tsx b/web/app/(commonLayout)/tools/page.tsx index 36a76d60d0..066550b3a2 100644 --- a/web/app/(commonLayout)/tools/page.tsx +++ b/web/app/(commonLayout)/tools/page.tsx @@ -2,8 +2,7 @@ import type { FC } from 'react' import { useTranslation } from 'react-i18next' import React, { useEffect } from 'react' -import Tools from '@/app/components/tools' -import { LOC } from '@/app/components/tools/types' +import ToolProviderList from '@/app/components/tools/provider-list' const Layout: FC = () => { const { t } = useTranslation() @@ -12,12 +11,6 @@ const Layout: FC = () => { document.title = `${t('tools.title')} - Dify` }, []) - return ( -
- -
- ) + return } export default React.memo(Layout) diff --git a/web/app/(commonLayout)/tools/third-part/page.tsx b/web/app/(commonLayout)/tools/third-part/page.tsx deleted file mode 100644 index d2ae810609..0000000000 --- a/web/app/(commonLayout)/tools/third-part/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' - -const ThirdPart = () => { - return ( -
- Third part -
- ) -} -export default ThirdPart diff --git a/web/app/components/app/app-publisher/index.tsx b/web/app/components/app/app-publisher/index.tsx index fa7b9ed344..7e4c584257 100644 --- a/web/app/components/app/app-publisher/index.tsx +++ b/web/app/components/app/app-publisher/index.tsx @@ -23,6 +23,8 @@ import { PlayCircle } from '@/app/components/base/icons/src/vender/line/mediaAnd import { CodeBrowser } from '@/app/components/base/icons/src/vender/line/development' import { LeftIndent02 } from '@/app/components/base/icons/src/vender/line/editor' import { FileText } from '@/app/components/base/icons/src/vender/line/files' +import WorkflowToolConfigureButton from '@/app/components/tools/workflow-tool/configure-button' +import type { InputVar } from '@/app/components/workflow/types' export type AppPublisherProps = { disabled?: boolean @@ -37,6 +39,9 @@ export type AppPublisherProps = { onRestore?: () => Promise | any onToggle?: (state: boolean) => void crossAxisOffset?: number + toolPublished?: boolean + inputs?: InputVar[] + onRefreshData?: () => void } const AppPublisher = ({ @@ -50,6 +55,9 @@ const AppPublisher = ({ onRestore, onToggle, crossAxisOffset = 0, + toolPublished, + inputs, + onRefreshData, }: AppPublisherProps) => { const { t } = useTranslation() const [published, setPublished] = useState(false) @@ -122,7 +130,7 @@ const AppPublisher = ({ -
+
{publishedAt ? t('workflow.common.latestPublished') : t('workflow.common.currentDraftUnpublished')} @@ -202,6 +210,23 @@ const AppPublisher = ({ )} }>{t('workflow.common.accessAPIReference')} + {appDetail?.mode === 'workflow' && ( + + )}
diff --git a/web/app/components/app/configuration/config/agent/agent-tools/choose-tool/index.tsx b/web/app/components/app/configuration/config/agent/agent-tools/choose-tool/index.tsx deleted file mode 100644 index d47406e95b..0000000000 --- a/web/app/components/app/configuration/config/agent/agent-tools/choose-tool/index.tsx +++ /dev/null @@ -1,77 +0,0 @@ -'use client' -import type { FC } from 'react' -import React from 'react' -import { useContext } from 'use-context-selector' -import { useTranslation } from 'react-i18next' -import produce from 'immer' -import Tools from '@/app/components/tools' -import { LOC } from '@/app/components/tools/types' -import Drawer from '@/app/components/base/drawer-plus' -import ConfigContext from '@/context/debug-configuration' -import type { ModelConfig } from '@/models/debug' -import I18n from '@/context/i18n' - -type Props = { - show: boolean - onHide: () => void - selectedProviderId?: string -} - -const ChooseTool: FC = ({ - show, - onHide, - selectedProviderId, -}) => { - const { t } = useTranslation() - const { locale } = useContext(I18n) - const { - modelConfig, - setModelConfig, - } = useContext(ConfigContext) - if (!show) - return null - - return ( - { - const parameters: Record = {} - if (tool.parameters) { - tool.parameters.forEach((item) => { - parameters[item.name] = '' - }) - } - - const nexModelConfig = produce(modelConfig, (draft: ModelConfig) => { - draft.agentConfig.tools.push({ - provider_id: collection.id || collection.name, - provider_type: collection.type, - provider_name: collection.name, - tool_name: tool.name, - tool_label: tool.label[locale] || tool.label[locale.replaceAll('-', '_')], - tool_parameters: parameters, - enabled: true, - }) - }) - setModelConfig(nexModelConfig) - }} - addedTools={(modelConfig?.agentConfig?.tools as any) || []} - /> - } - isShowMask={true} - clickOutsideNotOpen={false} - /> - ) -} -export default React.memo(ChooseTool) diff --git a/web/app/components/app/configuration/config/agent/agent-tools/index.tsx b/web/app/components/app/configuration/config/agent/agent-tools/index.tsx index b92ff94983..c59145aa9d 100644 --- a/web/app/components/app/configuration/config/agent/agent-tools/index.tsx +++ b/web/app/components/app/configuration/config/agent/agent-tools/index.tsx @@ -6,7 +6,6 @@ import cn from 'classnames' import { useContext } from 'use-context-selector' import produce from 'immer' import { useFormattingChangedDispatcher } from '../../../debug/hooks' -import ChooseTool from './choose-tool' import SettingBuiltInTool from './setting-built-in-tool' import Panel from '@/app/components/app/configuration/base/feature-panel' import Tooltip from '@/app/components/base/tooltip' @@ -22,6 +21,7 @@ import { MAX_TOOLS_NUM } from '@/config' import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' import TooltipPlus from '@/app/components/base/tooltip-plus' import { DefaultToolIcon } from '@/app/components/base/icons/src/public/other' +import AddToolModal from '@/app/components/tools/add-tool-modal' type AgentToolWithMoreInfo = AgentTool & { icon: any; collection?: Collection } | null const AgentTools: FC = () => { @@ -31,7 +31,6 @@ const AgentTools: FC = () => { const formattingChangedDispatcher = useFormattingChangedDispatcher() const [currentTool, setCurrentTool] = useState(null) - const [selectedProviderId, setSelectedProviderId] = useState(undefined) const [isShowSettingTool, setIsShowSettingTool] = useState(false) const tools = (modelConfig?.agentConfig?.tools as AgentTool[] || []).map((item) => { const collection = collectionList.find(collection => collection.id === item.provider_id && collection.type === item.provider_type) @@ -78,10 +77,7 @@ const AgentTools: FC = () => { {tools.length < MAX_TOOLS_NUM && ( <>
- { - setSelectedProviderId(undefined) - setIsShowChooseTool(true) - }} /> + setIsShowChooseTool(true)} /> )}
@@ -116,10 +112,14 @@ const AgentTools: FC = () => { /> ))}
- {item.tool_label || item.tool_name} + {item.provider_type === CollectionType.builtIn ? item.provider_name : item.tool_label} + + {item.tool_name} +
@@ -130,10 +130,8 @@ const AgentTools: FC = () => { popupContent={t(`tools.${item.isDeleted ? 'toolRemoved' : 'notAuthorized'}`)} >
{ - if (item.notAuthor) { - setSelectedProviderId(item.provider_id) + if (item.notAuthor) setIsShowChooseTool(true) - } }}>
@@ -153,7 +151,6 @@ const AgentTools: FC = () => { ) : (
- {/* {item.provider_type === CollectionType.builtIn && ( */} @@ -164,7 +161,6 @@ const AgentTools: FC = () => {
- {/* )} */}
{ const newModelConfig = produce(modelConfig, (draft) => { @@ -197,11 +193,7 @@ const AgentTools: FC = () => {
{isShowChooseTool && ( - setIsShowChooseTool(false)} - selectedProviderId={selectedProviderId} - /> + setIsShowChooseTool(false)} /> )} { isShowSettingTool && ( diff --git a/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx b/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx index 9eb2657fcf..b35217732f 100644 --- a/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx +++ b/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx @@ -8,7 +8,8 @@ import Drawer from '@/app/components/base/drawer-plus' import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form' import { addDefaultValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' import type { Collection, Tool } from '@/app/components/tools/types' -import { fetchBuiltInToolList, fetchCustomToolList, fetchModelToolList } from '@/service/tools' +import { CollectionType } from '@/app/components/tools/types' +import { fetchBuiltInToolList, fetchCustomToolList, fetchModelToolList, fetchWorkflowToolList } from '@/service/tools' import I18n from '@/context/i18n' import Button from '@/app/components/base/button' import Loading from '@/app/components/base/loading' @@ -64,6 +65,8 @@ const SettingBuiltInTool: FC = ({ resolve(await fetchModelToolList(collection.name)) else if (isBuiltIn) resolve(await fetchBuiltInToolList(collection.name)) + else if (collection.type === CollectionType.workflow) + resolve(await fetchWorkflowToolList(collection.id)) else resolve(await fetchCustomToolList(collection.name)) }()) @@ -78,7 +81,7 @@ const SettingBuiltInTool: FC = ({ catch (e) { } setIsLoading(false) })() - }, [collection?.name]) + }, [collection?.name, collection?.id, collection?.type]) useEffect(() => { setCurrType((!readonly && hasSetting) ? 'setting' : 'info') @@ -150,7 +153,7 @@ const SettingBuiltInTool: FC = ({ onHide={onHide} title={(
- {collection.icon === 'string' + {typeof collection.icon === 'string' ? (
= ({ )}
)} - panelClassName='mt-[65px] !w-[480px]' - maxWidthClassName='!max-w-[480px]' + panelClassName='mt-[65px] !w-[405px]' + maxWidthClassName='!max-w-[405px]' height='calc(100vh - 65px)' headerClassName='!border-b-black/5' body={ @@ -212,7 +215,7 @@ const SettingBuiltInTool: FC = ({
)}
} - isShowMask={true} + isShowMask={false} clickOutsideNotOpen={false} /> ) diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index 3c420d342f..c2ce0b5633 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -164,7 +164,7 @@ function DetailPanel ({ + const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal, showAgentLogModal, setShowAgentLogModal, showMessageLogModal, setShowMessageLogModal, currentLogModalActiveTab } = useAppStore(useShallow(state => ({ currentLogItem: state.currentLogItem, setCurrentLogItem: state.setCurrentLogItem, showPromptLogModal: state.showPromptLogModal, @@ -173,6 +173,7 @@ function DetailPanel([]) @@ -444,6 +445,7 @@ function DetailPanel )}
diff --git a/web/app/components/app/store.ts b/web/app/components/app/store.ts index aaa42d7110..eb7ad62f77 100644 --- a/web/app/components/app/store.ts +++ b/web/app/components/app/store.ts @@ -6,6 +6,7 @@ type State = { appDetail?: App appSidebarExpand: string currentLogItem?: IChatItem + currentLogModalActiveTab: string showPromptLogModal: boolean showAgentLogModal: boolean showMessageLogModal: boolean @@ -15,6 +16,7 @@ type Action = { setAppDetail: (appDetail?: App) => void setAppSiderbarExpand: (state: string) => void setCurrentLogItem: (item?: IChatItem) => void + setCurrentLogModalActiveTab: (tab: string) => void setShowPromptLogModal: (showPromptLogModal: boolean) => void setShowAgentLogModal: (showAgentLogModal: boolean) => void setShowMessageLogModal: (showMessageLogModal: boolean) => void @@ -26,11 +28,23 @@ export const useStore = create(set => ({ appSidebarExpand: '', setAppSiderbarExpand: appSidebarExpand => set(() => ({ appSidebarExpand })), currentLogItem: undefined, + currentLogModalActiveTab: 'DETAIL', setCurrentLogItem: currentLogItem => set(() => ({ currentLogItem })), + setCurrentLogModalActiveTab: currentLogModalActiveTab => set(() => ({ currentLogModalActiveTab })), showPromptLogModal: false, setShowPromptLogModal: showPromptLogModal => set(() => ({ showPromptLogModal })), showAgentLogModal: false, setShowAgentLogModal: showAgentLogModal => set(() => ({ showAgentLogModal })), showMessageLogModal: false, - setShowMessageLogModal: showMessageLogModal => set(() => ({ showMessageLogModal })), + setShowMessageLogModal: showMessageLogModal => set(() => { + if (showMessageLogModal) { + return { showMessageLogModal } + } + else { + return { + showMessageLogModal, + currentLogModalActiveTab: 'DETAIL', + } + } + }), })) diff --git a/web/app/components/base/chat/chat/answer/index.tsx b/web/app/components/base/chat/chat/answer/index.tsx index f2739f6374..4e88283663 100644 --- a/web/app/components/base/chat/chat/answer/index.tsx +++ b/web/app/components/base/chat/chat/answer/index.tsx @@ -97,7 +97,7 @@ const Answer: FC = ({ ) }
-
+
= ({ } { workflowProcess && ( - + ) } { diff --git a/web/app/components/base/chat/chat/answer/workflow-process.tsx b/web/app/components/base/chat/chat/answer/workflow-process.tsx index 4eef0b938f..d47db40437 100644 --- a/web/app/components/base/chat/chat/answer/workflow-process.tsx +++ b/web/app/components/base/chat/chat/answer/workflow-process.tsx @@ -1,20 +1,23 @@ import { + useCallback, useEffect, useMemo, useState, } from 'react' import cn from 'classnames' import { useTranslation } from 'react-i18next' -import type { WorkflowProcess } from '../../types' +import type { ChatItem, WorkflowProcess } from '../../types' import { CheckCircle } from '@/app/components/base/icons/src/vender/solid/general' import { AlertCircle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' import { Loading02 } from '@/app/components/base/icons/src/vender/line/general' import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows' import { WorkflowRunningStatus } from '@/app/components/workflow/types' import NodePanel from '@/app/components/workflow/run/node' +import { useStore as useAppStore } from '@/app/components/app/store' type WorkflowProcessProps = { data: WorkflowProcess + item?: ChatItem grayBg?: boolean expand?: boolean hideInfo?: boolean @@ -22,6 +25,7 @@ type WorkflowProcessProps = { } const WorkflowProcessItem = ({ data, + item, grayBg, expand = false, hideInfo = false, @@ -48,6 +52,16 @@ const WorkflowProcessItem = ({ setCollapse(!expand) }, [expand]) + const setCurrentLogItem = useAppStore(s => s.setCurrentLogItem) + const setShowMessageLogModal = useAppStore(s => s.setShowMessageLogModal) + const setCurrentLogModalActiveTab = useAppStore(s => s.setCurrentLogModalActiveTab) + + const showIterationDetail = useCallback(() => { + setCurrentLogItem(item) + setCurrentLogModalActiveTab('TRACING') + setShowMessageLogModal(true) + }, [item, setCurrentLogItem, setCurrentLogModalActiveTab, setShowMessageLogModal]) + return (
)) diff --git a/web/app/components/base/chat/chat/hooks.ts b/web/app/components/base/chat/chat/hooks.ts index 382c01e167..be1269858e 100644 --- a/web/app/components/base/chat/chat/hooks.ts +++ b/web/app/components/base/chat/chat/hooks.ts @@ -237,6 +237,8 @@ export const useChat = ( isAnswer: true, } + let isInIteration = false + handleResponding(true) hasStopResponded.current = false @@ -467,7 +469,41 @@ export const useChat = ( } })) }, + onIterationStart: ({ data }) => { + responseItem.workflowProcess!.tracing!.push({ + ...data, + status: WorkflowRunningStatus.Running, + } as any) + handleUpdateChatList(produce(chatListRef.current, (draft) => { + const currentIndex = draft.findIndex(item => item.id === responseItem.id) + draft[currentIndex] = { + ...draft[currentIndex], + ...responseItem, + } + })) + isInIteration = true + }, + onIterationFinish: ({ data }) => { + const tracing = responseItem.workflowProcess!.tracing! + tracing[tracing.length - 1] = { + ...tracing[tracing.length - 1], + ...data, + status: WorkflowRunningStatus.Succeeded, + } as any + + handleUpdateChatList(produce(chatListRef.current, (draft) => { + const currentIndex = draft.findIndex(item => item.id === responseItem.id) + draft[currentIndex] = { + ...draft[currentIndex], + ...responseItem, + } + })) + isInIteration = false + }, onNodeStarted: ({ data }) => { + if (isInIteration) + return + responseItem.workflowProcess!.tracing!.push({ ...data, status: WorkflowRunningStatus.Running, @@ -481,6 +517,9 @@ export const useChat = ( })) }, onNodeFinished: ({ data }) => { + if (isInIteration) + return + const currentIndex = responseItem.workflowProcess!.tracing!.findIndex(item => item.node_id === data.node_id) responseItem.workflowProcess!.tracing[currentIndex] = data as any handleUpdateChatList(produce(chatListRef.current, (draft) => { diff --git a/web/app/components/base/drawer-plus/index.tsx b/web/app/components/base/drawer-plus/index.tsx index 3415b067aa..3eabe1973e 100644 --- a/web/app/components/base/drawer-plus/index.tsx +++ b/web/app/components/base/drawer-plus/index.tsx @@ -20,6 +20,7 @@ type Props = { foot?: JSX.Element isShowMask?: boolean clickOutsideNotOpen?: boolean + positionCenter?: boolean } const DrawerPlus: FC = ({ @@ -36,6 +37,7 @@ const DrawerPlus: FC = ({ foot, isShowMask, clickOutsideNotOpen = true, + positionCenter, }) => { const ref = useRef(null) const media = useBreakpoints() @@ -46,7 +48,15 @@ const DrawerPlus: FC = ({ return ( // clickOutsideNotOpen to fix confirm modal click cause drawer close - +
!clickOutsideNotOpen && onClose()} className="fixed z-30 inset-0 overflow-y-auto" > -
+
{/* mask */} -
+
<> {title && + + + + + + + + + + + + + + + diff --git a/web/app/components/base/icons/assets/vender/line/files/folder.svg b/web/app/components/base/icons/assets/vender/line/files/folder.svg new file mode 100644 index 0000000000..248708bd3d --- /dev/null +++ b/web/app/components/base/icons/assets/vender/line/files/folder.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/web/app/components/base/icons/assets/vender/line/others/apps-02.svg b/web/app/components/base/icons/assets/vender/line/others/apps-02.svg new file mode 100644 index 0000000000..8e1fec9ecc --- /dev/null +++ b/web/app/components/base/icons/assets/vender/line/others/apps-02.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/web/app/components/base/icons/assets/vender/line/others/colors.svg b/web/app/components/base/icons/assets/vender/line/others/colors.svg new file mode 100644 index 0000000000..3173ef4bae --- /dev/null +++ b/web/app/components/base/icons/assets/vender/line/others/colors.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/web/app/components/base/icons/assets/vender/line/others/exchange-02.svg b/web/app/components/base/icons/assets/vender/line/others/exchange-02.svg new file mode 100644 index 0000000000..45d2770277 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/line/others/exchange-02.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/app/components/base/icons/assets/vender/line/others/file-code.svg b/web/app/components/base/icons/assets/vender/line/others/file-code.svg new file mode 100644 index 0000000000..eb77033a0a --- /dev/null +++ b/web/app/components/base/icons/assets/vender/line/others/file-code.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/app/components/base/icons/assets/vender/line/others/tools.svg b/web/app/components/base/icons/assets/vender/line/others/tools.svg new file mode 100644 index 0000000000..a10f5391b1 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/line/others/tools.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/web/app/components/base/icons/assets/vender/workflow/iteration-start.svg b/web/app/components/base/icons/assets/vender/workflow/iteration-start.svg new file mode 100644 index 0000000000..a1cc763596 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/workflow/iteration-start.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/web/app/components/base/icons/assets/vender/workflow/iteration.svg b/web/app/components/base/icons/assets/vender/workflow/iteration.svg new file mode 100644 index 0000000000..2a74faca69 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/workflow/iteration.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/web/app/components/base/icons/assets/vender/workflow/parameter-extractor.svg b/web/app/components/base/icons/assets/vender/workflow/parameter-extractor.svg new file mode 100644 index 0000000000..dc9418b40f --- /dev/null +++ b/web/app/components/base/icons/assets/vender/workflow/parameter-extractor.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/app/components/base/icons/src/public/common/D.json b/web/app/components/base/icons/src/public/common/D.json new file mode 100644 index 0000000000..2090b8909d --- /dev/null +++ b/web/app/components/base/icons/src/public/common/D.json @@ -0,0 +1,125 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "16", + "height": "16", + "viewBox": "0 0 16 16", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M2 1H7.94339C11.8094 1 14.9434 4.13401 14.9434 8C14.9434 11.866 11.8094 15 7.9434 15H2V1Z", + "fill": "white" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M2 1H7.94339C11.8094 1 14.9434 4.13401 14.9434 8C14.9434 11.866 11.8094 15 7.9434 15H2V1Z", + "fill": "url(#paint0_angular_19344_240446)" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M7.94336 8H8.20751V15H7.94336V8Z", + "fill": "url(#paint1_linear_19344_240446)" + }, + "children": [] + }, + { + "type": "element", + "name": "defs", + "attributes": {}, + "children": [ + { + "type": "element", + "name": "radialGradient", + "attributes": { + "id": "paint0_angular_19344_240446", + "cx": "0", + "cy": "0", + "r": "1", + "gradientUnits": "userSpaceOnUse", + "gradientTransform": "translate(7.9434 8) rotate(90) scale(8.75 8.75)" + }, + "children": [ + { + "type": "element", + "name": "stop", + "attributes": { + "stop-color": "#001FC2" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "0.711334", + "stop-color": "#0667F8", + "stop-opacity": "0.2" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "1", + "stop-color": "#155EEF", + "stop-opacity": "0" + }, + "children": [] + } + ] + }, + { + "type": "element", + "name": "linearGradient", + "attributes": { + "id": "paint1_linear_19344_240446", + "x1": "8.06244", + "y1": "8.43754", + "x2": "7.93744", + "y2": "9.20317", + "gradientUnits": "userSpaceOnUse" + }, + "children": [ + { + "type": "element", + "name": "stop", + "attributes": { + "stop-color": "white", + "stop-opacity": "0" + }, + "children": [] + }, + { + "type": "element", + "name": "stop", + "attributes": { + "offset": "1", + "stop-color": "white" + }, + "children": [] + } + ] + } + ] + } + ] + }, + "name": "D" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/public/common/D.tsx b/web/app/components/base/icons/src/public/common/D.tsx new file mode 100644 index 0000000000..6d8bbf7653 --- /dev/null +++ b/web/app/components/base/icons/src/public/common/D.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './D.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'D' + +export default Icon diff --git a/web/app/components/base/icons/src/public/common/index.ts b/web/app/components/base/icons/src/public/common/index.ts index b65032834b..b20668175c 100644 --- a/web/app/components/base/icons/src/public/common/index.ts +++ b/web/app/components/base/icons/src/public/common/index.ts @@ -1,3 +1,4 @@ +export { default as D } from './D' export { default as DiagonalDividingLine } from './DiagonalDividingLine' export { default as Dify } from './Dify' export { default as Github } from './Github' diff --git a/web/app/components/base/icons/src/vender/line/files/Folder.json b/web/app/components/base/icons/src/vender/line/files/Folder.json new file mode 100644 index 0000000000..6bbc4380ae --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/files/Folder.json @@ -0,0 +1,39 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "14", + "height": "14", + "viewBox": "0 0 14 14", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "folder" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "id": "Icon", + "d": "M12.8327 11.0833C12.8327 11.3928 12.7098 11.6895 12.491 11.9083C12.2722 12.1271 11.9754 12.25 11.666 12.25H2.33268C2.02326 12.25 1.72652 12.1271 1.50772 11.9083C1.28893 11.6895 1.16602 11.3928 1.16602 11.0833V2.91667C1.16602 2.60725 1.28893 2.3105 1.50772 2.09171C1.72652 1.87292 2.02326 1.75 2.33268 1.75H5.24935L6.41602 3.5H11.666C11.9754 3.5 12.2722 3.62292 12.491 3.84171C12.7098 4.0605 12.8327 4.35725 12.8327 4.66667V11.0833Z", + "stroke": "currentColor", + "stroke-width": "1.5", + "stroke-linecap": "round", + "stroke-linejoin": "round" + }, + "children": [] + } + ] + } + ] + }, + "name": "Folder" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/line/files/Folder.tsx b/web/app/components/base/icons/src/vender/line/files/Folder.tsx new file mode 100644 index 0000000000..1914bf6768 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/files/Folder.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './Folder.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'Folder' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/line/files/index.ts b/web/app/components/base/icons/src/vender/line/files/index.ts index aa057d699a..05832a9ca5 100644 --- a/web/app/components/base/icons/src/vender/line/files/index.ts +++ b/web/app/components/base/icons/src/vender/line/files/index.ts @@ -7,3 +7,4 @@ export { default as FileDownload02 } from './FileDownload02' export { default as FilePlus01 } from './FilePlus01' export { default as FilePlus02 } from './FilePlus02' export { default as FileText } from './FileText' +export { default as Folder } from './Folder' diff --git a/web/app/components/base/icons/src/vender/line/others/Apps02.json b/web/app/components/base/icons/src/vender/line/others/Apps02.json new file mode 100644 index 0000000000..2ff128f24c --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/others/Apps02.json @@ -0,0 +1,36 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "16", + "height": "16", + "viewBox": "0 0 16 16", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "apps-2-line" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector", + "d": "M4.66602 7.6665C3.00916 7.6665 1.66602 6.32336 1.66602 4.6665C1.66602 3.00965 3.00916 1.6665 4.66602 1.6665C6.32287 1.6665 7.66602 3.00965 7.66602 4.6665C7.66602 6.32336 6.32287 7.6665 4.66602 7.6665ZM4.66602 14.3332C3.00916 14.3332 1.66602 12.99 1.66602 11.3332C1.66602 9.6763 3.00916 8.33317 4.66602 8.33317C6.32287 8.33317 7.66602 9.6763 7.66602 11.3332C7.66602 12.99 6.32287 14.3332 4.66602 14.3332ZM11.3327 7.6665C9.67582 7.6665 8.33268 6.32336 8.33268 4.6665C8.33268 3.00965 9.67582 1.6665 11.3327 1.6665C12.9895 1.6665 14.3327 3.00965 14.3327 4.6665C14.3327 6.32336 12.9895 7.6665 11.3327 7.6665ZM11.3327 14.3332C9.67582 14.3332 8.33268 12.99 8.33268 11.3332C8.33268 9.6763 9.67582 8.33317 11.3327 8.33317C12.9895 8.33317 14.3327 9.6763 14.3327 11.3332C14.3327 12.99 12.9895 14.3332 11.3327 14.3332ZM4.66602 6.33317C5.58649 6.33317 6.33268 5.58698 6.33268 4.6665C6.33268 3.74603 5.58649 2.99984 4.66602 2.99984C3.74554 2.99984 2.99935 3.74603 2.99935 4.6665C2.99935 5.58698 3.74554 6.33317 4.66602 6.33317ZM4.66602 12.9998C5.58649 12.9998 6.33268 12.2536 6.33268 11.3332C6.33268 10.4127 5.58649 9.6665 4.66602 9.6665C3.74554 9.6665 2.99935 10.4127 2.99935 11.3332C2.99935 12.2536 3.74554 12.9998 4.66602 12.9998ZM11.3327 6.33317C12.2531 6.33317 12.9993 5.58698 12.9993 4.6665C12.9993 3.74603 12.2531 2.99984 11.3327 2.99984C10.4122 2.99984 9.66602 3.74603 9.66602 4.6665C9.66602 5.58698 10.4122 6.33317 11.3327 6.33317ZM11.3327 12.9998C12.2531 12.9998 12.9993 12.2536 12.9993 11.3332C12.9993 10.4127 12.2531 9.6665 11.3327 9.6665C10.4122 9.6665 9.66602 10.4127 9.66602 11.3332C9.66602 12.2536 10.4122 12.9998 11.3327 12.9998Z", + "fill": "currentColor" + }, + "children": [] + } + ] + } + ] + }, + "name": "Apps02" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/line/others/Apps02.tsx b/web/app/components/base/icons/src/vender/line/others/Apps02.tsx new file mode 100644 index 0000000000..42109d81da --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/others/Apps02.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './Apps02.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'Apps02' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/line/others/Colors.json b/web/app/components/base/icons/src/vender/line/others/Colors.json new file mode 100644 index 0000000000..b1832c2fe8 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/others/Colors.json @@ -0,0 +1,66 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "14", + "height": "14", + "viewBox": "0 0 14 14", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "colors", + "clip-path": "url(#clip0_18499_53582)" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "id": "Icon", + "d": "M7.00032 11.9422C7.61954 12.4964 8.43724 12.8334 9.33366 12.8334C11.2667 12.8334 12.8337 11.2664 12.8337 9.33342C12.8337 7.71938 11.7411 6.36051 10.2552 5.95602M3.74543 5.95601C2.25954 6.3605 1.16699 7.71937 1.16699 9.33341C1.16699 11.2664 2.734 12.8334 4.66699 12.8334C6.59999 12.8334 8.16699 11.2664 8.16699 9.33341C8.16699 8.87813 8.08006 8.44314 7.92189 8.04415M10.5003 4.66675C10.5003 6.59974 8.93332 8.16675 7.00033 8.16675C5.06733 8.16675 3.50033 6.59974 3.50033 4.66675C3.50033 2.73375 5.06733 1.16675 7.00033 1.16675C8.93332 1.16675 10.5003 2.73375 10.5003 4.66675Z", + "stroke": "currentColor", + "stroke-width": "1.25", + "stroke-linecap": "round", + "stroke-linejoin": "round" + }, + "children": [] + } + ] + }, + { + "type": "element", + "name": "defs", + "attributes": {}, + "children": [ + { + "type": "element", + "name": "clipPath", + "attributes": { + "id": "clip0_18499_53582" + }, + "children": [ + { + "type": "element", + "name": "rect", + "attributes": { + "width": "14", + "height": "14", + "fill": "white" + }, + "children": [] + } + ] + } + ] + } + ] + }, + "name": "Colors" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/line/others/Colors.tsx b/web/app/components/base/icons/src/vender/line/others/Colors.tsx new file mode 100644 index 0000000000..224ca116bd --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/others/Colors.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './Colors.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'Colors' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/line/others/Exchange02.json b/web/app/components/base/icons/src/vender/line/others/Exchange02.json new file mode 100644 index 0000000000..808a9ff644 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/others/Exchange02.json @@ -0,0 +1,26 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "16", + "height": "16", + "viewBox": "0 0 16 16", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M4.66602 14.3334C3.00916 14.3334 1.66602 12.9903 1.66602 11.3334C1.66602 9.67655 3.00916 8.33342 4.66602 8.33342C6.32287 8.33342 7.66602 9.67655 7.66602 11.3334C7.66602 12.9903 6.32287 14.3334 4.66602 14.3334ZM11.3327 7.66675C9.67582 7.66675 8.33268 6.3236 8.33268 4.66675C8.33268 3.00989 9.67582 1.66675 11.3327 1.66675C12.9895 1.66675 14.3327 3.00989 14.3327 4.66675C14.3327 6.3236 12.9895 7.66675 11.3327 7.66675ZM4.66602 13.0001C5.58649 13.0001 6.33268 12.2539 6.33268 11.3334C6.33268 10.4129 5.58649 9.66675 4.66602 9.66675C3.74554 9.66675 2.99935 10.4129 2.99935 11.3334C2.99935 12.2539 3.74554 13.0001 4.66602 13.0001ZM11.3327 6.33342C12.2531 6.33342 12.9993 5.58722 12.9993 4.66675C12.9993 3.74627 12.2531 3.00008 11.3327 3.00008C10.4122 3.00008 9.66602 3.74627 9.66602 4.66675C9.66602 5.58722 10.4122 6.33342 11.3327 6.33342ZM1.99935 5.33341C1.99935 3.49247 3.49174 2.00008 5.33268 2.00008H7.33268V3.33341H5.33268C4.22812 3.33341 3.33268 4.22885 3.33268 5.33341V7.33342H1.99935V5.33341ZM13.9993 8.66675H12.666V10.6667C12.666 11.7713 11.7706 12.6667 10.666 12.6667H8.66602V14.0001H10.666C12.5069 14.0001 13.9993 12.5077 13.9993 10.6667V8.66675Z", + "fill": "currentColor" + }, + "children": [] + } + ] + }, + "name": "Exchange02" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/line/others/Exchange02.tsx b/web/app/components/base/icons/src/vender/line/others/Exchange02.tsx new file mode 100644 index 0000000000..36fa63e487 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/others/Exchange02.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './Exchange02.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'Exchange02' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/line/others/FileCode.json b/web/app/components/base/icons/src/vender/line/others/FileCode.json new file mode 100644 index 0000000000..41050a559b --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/others/FileCode.json @@ -0,0 +1,26 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "16", + "height": "16", + "viewBox": "0 0 16 16", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M10 2.66659H3.33333V13.3333H12.6667V5.33325H10V2.66659ZM2 1.99445C2 1.62929 2.29833 1.33325 2.66567 1.33325H10.6667L13.9998 4.66658L14 13.9949C14 14.3659 13.7034 14.6666 13.3377 14.6666H2.66227C2.29651 14.6666 2 14.3631 2 14.0054V1.99445ZM11.7713 7.99992L9.4142 10.3569L8.4714 9.41412L9.8856 7.99992L8.4714 6.58571L9.4142 5.6429L11.7713 7.99992ZM4.22877 7.99992L6.58579 5.6429L7.5286 6.58571L6.11438 7.99992L7.5286 9.41412L6.58579 10.3569L4.22877 7.99992Z", + "fill": "currentColor" + }, + "children": [] + } + ] + }, + "name": "FileCode" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/line/others/FileCode.tsx b/web/app/components/base/icons/src/vender/line/others/FileCode.tsx new file mode 100644 index 0000000000..c050f368bb --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/others/FileCode.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './FileCode.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'FileCode' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/line/others/Tools.json b/web/app/components/base/icons/src/vender/line/others/Tools.json new file mode 100644 index 0000000000..0ab6857b09 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/others/Tools.json @@ -0,0 +1,119 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "16", + "height": "17", + "viewBox": "0 0 16 17", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "Tools", + "clip-path": "url(#clip0_5381_39479)" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "id": "vector", + "d": "M13.4375 14.4375V6.8125H2.5625V14.4375C2.5625 14.9898 3.01022 15.4375 3.5625 15.4375H12.4375C12.9898 15.4375 13.4375 14.9898 13.4375 14.4375Z", + "stroke": "currentColor", + "stroke-width": "1.25", + "stroke-linecap": "round", + "stroke-linejoin": "round" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "vector_2", + "d": "M13.6254 2.875L11.1738 6.47327", + "stroke": "currentColor", + "stroke-width": "1.25", + "stroke-linecap": "round", + "stroke-linejoin": "round" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "vector_3", + "d": "M6.3125 9.8125H9.6875", + "stroke": "currentColor", + "stroke-width": "1.25", + "stroke-linecap": "round", + "stroke-linejoin": "round" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "vector_4", + "d": "M8.63355 1.88044L8.75 1.64754L8.86645 1.88044C8.97531 2.09816 9.15184 2.27469 9.36956 2.38355L9.60246 2.5L9.36956 2.61645C9.15184 2.72531 8.97531 2.90184 8.86645 3.11956L8.75 3.35246L8.63355 3.11956C8.52469 2.90184 8.34816 2.72531 8.13044 2.61645L7.89754 2.5L8.13044 2.38355C8.34816 2.27469 8.52469 2.09816 8.63355 1.88044Z", + "stroke": "currentColor", + "stroke-width": "1.25", + "stroke-linecap": "square", + "stroke-linejoin": "round" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "vector_5", + "d": "M4.625 3.14754L4.61865 3.16025C4.51946 3.35862 4.35862 3.51946 4.16025 3.61865L4.14754 3.625L4.16025 3.63135C4.35862 3.73054 4.51946 3.89138 4.61865 4.08975L4.625 4.10246L4.63135 4.08975C4.73054 3.89138 4.89138 3.73054 5.08975 3.63135L5.10246 3.625L5.08975 3.61865C4.89138 3.51946 4.73054 3.35862 4.63135 3.16025L4.625 3.14754ZM4.625 3.14754L4.63135 3.16025L4.625 3.14754Z", + "stroke": "currentColor", + "stroke-width": "1.25", + "stroke-linecap": "square", + "stroke-linejoin": "round" + }, + "children": [] + } + ] + }, + { + "type": "element", + "name": "defs", + "attributes": {}, + "children": [ + { + "type": "element", + "name": "clipPath", + "attributes": { + "id": "clip0_5381_39479" + }, + "children": [ + { + "type": "element", + "name": "rect", + "attributes": { + "width": "16", + "height": "16", + "fill": "white", + "transform": "translate(0 0.5)" + }, + "children": [] + } + ] + } + ] + } + ] + }, + "name": "Tools" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/line/others/Tools.tsx b/web/app/components/base/icons/src/vender/line/others/Tools.tsx new file mode 100644 index 0000000000..c46b8bc2b0 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/others/Tools.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './Tools.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'Tools' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/line/others/index.ts b/web/app/components/base/icons/src/vender/line/others/index.ts index 48620ef9e8..648792f22b 100644 --- a/web/app/components/base/icons/src/vender/line/others/index.ts +++ b/web/app/components/base/icons/src/vender/line/others/index.ts @@ -1 +1,6 @@ +export { default as Apps02 } from './Apps02' +export { default as Colors } from './Colors' export { default as DragHandle } from './DragHandle' +export { default as Exchange02 } from './Exchange02' +export { default as FileCode } from './FileCode' +export { default as Tools } from './Tools' diff --git a/web/app/components/base/icons/src/vender/workflow/Iteration.json b/web/app/components/base/icons/src/vender/workflow/Iteration.json new file mode 100644 index 0000000000..ee5748d1f1 --- /dev/null +++ b/web/app/components/base/icons/src/vender/workflow/Iteration.json @@ -0,0 +1,36 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "14", + "height": "14", + "viewBox": "0 0 14 14", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "icons/iteration" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector", + "d": "M6.82849 0.754349C6.6007 0.526545 6.23133 0.526545 6.00354 0.754349C5.77573 0.982158 5.77573 1.3515 6.00354 1.57931L6.82849 0.754349ZM8.16602 2.91683L8.57849 3.32931C8.80628 3.1015 8.80628 2.73216 8.57849 2.50435L8.16602 2.91683ZM6.00354 4.25435C5.77573 4.48216 5.77573 4.8515 6.00354 5.07931C6.23133 5.30711 6.6007 5.30711 6.82849 5.07931L6.00354 4.25435ZM7.99516 9.74597C8.22295 9.51818 8.22295 9.14881 7.99516 8.92102C7.76737 8.69323 7.398 8.69323 7.17021 8.92102L7.99516 9.74597ZM5.83268 11.0835L5.4202 10.671C5.1924 10.8988 5.1924 11.2682 5.4202 11.496L5.83268 11.0835ZM7.17021 13.246C7.398 13.4738 7.76737 13.4738 7.99516 13.246C8.22295 13.0182 8.22295 12.6488 7.99516 12.421L7.17021 13.246ZM11.4993 3.73414C11.2738 3.50404 10.9045 3.5003 10.6744 3.72578C10.4443 3.95127 10.4405 4.32059 10.6661 4.55069L11.4993 3.73414ZM7.58268 3.50016C7.90486 3.50016 8.16602 3.23899 8.16602 2.91683C8.16602 2.59467 7.90486 2.3335 7.58268 2.3335L7.58268 3.50016ZM2.49938 10.2662C2.72486 10.4963 3.09419 10.5 3.32429 10.2745C3.55439 10.0491 3.55814 9.6797 3.33266 9.44964L2.49938 10.2662ZM6.00354 1.57931L7.75354 3.32931L8.57849 2.50435L6.82849 0.754349L6.00354 1.57931ZM7.75354 2.50435L6.00354 4.25435L6.82849 5.07931L8.57849 3.32931L7.75354 2.50435ZM7.17021 8.92102L5.4202 10.671L6.24516 11.496L7.99516 9.74597L7.17021 8.92102ZM5.4202 11.496L7.17021 13.246L7.99516 12.421L6.24516 10.671L5.4202 11.496ZM8.16602 10.5002L6.41602 10.5002V11.6668L8.16602 11.6668V10.5002ZM11.666 7.00016C11.666 8.93316 10.099 10.5002 8.16602 10.5002V11.6668C10.7434 11.6668 12.8327 9.57751 12.8327 7.00016H11.666ZM12.8327 7.00016C12.8327 5.72882 12.3235 4.57524 11.4993 3.73414L10.6661 4.55069C11.2852 5.18256 11.666 6.0463 11.666 7.00016H12.8327ZM5.83268 3.50016H7.58268L7.58268 2.3335H5.83268L5.83268 3.50016ZM2.33268 7.00016C2.33268 5.06717 3.89968 3.50016 5.83268 3.50016L5.83268 2.3335C3.25535 2.3335 1.16602 4.42283 1.16602 7.00016H2.33268ZM1.16602 7.00016C1.16602 8.27148 1.67517 9.42508 2.49938 10.2662L3.33266 9.44964C2.71348 8.81777 2.33268 7.95403 2.33268 7.00016H1.16602Z", + "fill": "currentColor" + }, + "children": [] + } + ] + } + ] + }, + "name": "Iteration" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/workflow/Iteration.tsx b/web/app/components/base/icons/src/vender/workflow/Iteration.tsx new file mode 100644 index 0000000000..57b6a882d6 --- /dev/null +++ b/web/app/components/base/icons/src/vender/workflow/Iteration.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './Iteration.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'Iteration' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/workflow/IterationStart.json b/web/app/components/base/icons/src/vender/workflow/IterationStart.json new file mode 100644 index 0000000000..2941cdb65d --- /dev/null +++ b/web/app/components/base/icons/src/vender/workflow/IterationStart.json @@ -0,0 +1,36 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "12", + "height": "12", + "viewBox": "0 0 12 12", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "icons/block-start" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector", + "d": "M6.8498 1.72732C6.3379 1.3754 5.6621 1.3754 5.1502 1.72732L2.1502 3.78982C1.74317 4.06965 1.5 4.53193 1.5 5.02588V8.99983C1.5 9.82828 2.17158 10.4998 3 10.4998H4.25C4.52614 10.4998 4.75 10.276 4.75 9.99983V8.24983C4.75 7.55948 5.30965 6.99983 6 6.99983C6.69035 6.99983 7.25 7.55948 7.25 8.24983V9.99983C7.25 10.276 7.47385 10.4998 7.75 10.4998H9C9.82845 10.4998 10.5 9.82828 10.5 8.99983V5.02588C10.5 4.53193 10.2568 4.06965 9.8498 3.78982L6.8498 1.72732Z", + "fill": "currentColor" + }, + "children": [] + } + ] + } + ] + }, + "name": "IterationStart" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/workflow/IterationStart.tsx b/web/app/components/base/icons/src/vender/workflow/IterationStart.tsx new file mode 100644 index 0000000000..6a9155a1f1 --- /dev/null +++ b/web/app/components/base/icons/src/vender/workflow/IterationStart.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './IterationStart.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'IterationStart' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/workflow/ParameterExtractor.json b/web/app/components/base/icons/src/vender/workflow/ParameterExtractor.json new file mode 100644 index 0000000000..7d4fa6424a --- /dev/null +++ b/web/app/components/base/icons/src/vender/workflow/ParameterExtractor.json @@ -0,0 +1,266 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "14", + "height": "14", + "viewBox": "0 0 14 14", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "icons/parma-extractor" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector", + "d": "M7.58398 10.3543C7.58398 10.0322 7.84514 9.771 8.16732 9.771C8.48949 9.771 8.75065 10.0322 8.75065 10.3543C8.75065 10.6765 8.48949 10.9377 8.16732 10.9377C7.84514 10.9377 7.58398 10.6765 7.58398 10.3543Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_2", + "d": "M9.625 10.3543C9.625 10.0322 9.88616 9.771 10.2083 9.771C10.5305 9.771 10.7917 10.0322 10.7917 10.3543C10.7917 10.6765 10.5305 10.9377 10.2083 10.9377C9.88616 10.9377 9.625 10.6765 9.625 10.3543Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_3", + "d": "M7.58398 3.64583C7.58398 3.32366 7.84514 3.0625 8.16732 3.0625C8.48949 3.0625 8.75065 3.32366 8.75065 3.64583C8.75065 3.968 8.48949 4.22917 8.16732 4.22917C7.84514 4.22917 7.58398 3.968 7.58398 3.64583Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_4", + "d": "M7.72852 12.104C7.72852 11.8624 7.9244 11.6665 8.16602 11.6665C8.40763 11.6665 8.60352 11.8624 8.60352 12.104C8.60352 12.3456 8.40763 12.5415 8.16602 12.5415C7.9244 12.5415 7.72852 12.3456 7.72852 12.104Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_5", + "d": "M11.375 8.1665C11.375 7.92489 11.5709 7.729 11.8125 7.729C12.0541 7.729 12.25 7.92489 12.25 8.1665C12.25 8.40812 12.0541 8.604 11.8125 8.604C11.5709 8.604 11.375 8.40812 11.375 8.1665Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_6", + "d": "M11.375 5.8335C11.375 5.59187 11.5709 5.396 11.8125 5.396C12.0541 5.396 12.25 5.59187 12.25 5.8335C12.25 6.07511 12.0541 6.271 11.8125 6.271C11.5709 6.271 11.375 6.07511 11.375 5.8335Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_7", + "d": "M7.72852 1.896C7.72852 1.65437 7.9244 1.4585 8.16602 1.4585C8.40763 1.4585 8.60352 1.65437 8.60352 1.896C8.60352 2.13762 8.40763 2.3335 8.16602 2.3335C7.9244 2.3335 7.72852 2.13762 7.72852 1.896Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_8", + "d": "M7.29102 8.1665C7.29102 7.68327 7.68278 7.2915 8.16602 7.2915C8.64925 7.2915 9.04102 7.68327 9.04102 8.1665C9.04102 8.64974 8.64925 9.0415 8.16602 9.0415C7.68278 9.0415 7.29102 8.64974 7.29102 8.1665Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_9", + "d": "M7.29102 5.8335C7.29102 5.35025 7.68278 4.9585 8.16602 4.9585C8.64925 4.9585 9.04102 5.35025 9.04102 5.8335C9.04102 6.31673 8.64925 6.7085 8.16602 6.7085C7.68278 6.7085 7.29102 6.31673 7.29102 5.8335Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_10", + "d": "M9.625 8.16683C9.625 7.84465 9.88616 7.5835 10.2083 7.5835C10.5305 7.5835 10.7917 7.84465 10.7917 8.16683C10.7917 8.489 10.5305 8.75016 10.2083 8.75016C9.88616 8.75016 9.625 8.489 9.625 8.16683Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_11", + "d": "M9.625 5.83333C9.625 5.51116 9.88616 5.25 10.2083 5.25C10.5305 5.25 10.7917 5.51116 10.7917 5.83333C10.7917 6.15551 10.5305 6.41667 10.2083 6.41667C9.88616 6.41667 9.625 6.15551 9.625 5.83333Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_12", + "d": "M9.625 3.64583C9.625 3.32366 9.88616 3.0625 10.2083 3.0625C10.5305 3.0625 10.7917 3.32366 10.7917 3.64583C10.7917 3.968 10.5305 4.22917 10.2083 4.22917C9.88616 4.22917 9.625 3.968 9.625 3.64583Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_13", + "d": "M6.41667 3.64583C6.41667 3.968 6.15551 4.22917 5.83333 4.22917C5.51117 4.22917 5.25 3.968 5.25 3.64583C5.25 3.32367 5.51117 3.0625 5.83333 3.0625C6.15551 3.0625 6.41667 3.32367 6.41667 3.64583Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_14", + "d": "M4.37565 3.64583C4.37565 3.968 4.11448 4.22917 3.79232 4.22917C3.47015 4.22917 3.20898 3.968 3.20898 3.64583C3.20898 3.32367 3.47015 3.0625 3.79232 3.0625C4.11448 3.0625 4.37565 3.32367 4.37565 3.64583Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_15", + "d": "M6.41667 10.3543C6.41667 10.6765 6.15551 10.9377 5.83333 10.9377C5.51117 10.9377 5.25 10.6765 5.25 10.3543C5.25 10.0322 5.51117 9.771 5.83333 9.771C6.15551 9.771 6.41667 10.0322 6.41667 10.3543Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_16", + "d": "M6.27148 1.896C6.27148 2.13762 6.0756 2.3335 5.83398 2.3335C5.59236 2.3335 5.39648 2.13762 5.39648 1.896C5.39648 1.65437 5.59236 1.4585 5.83398 1.4585C6.0756 1.4585 6.27148 1.65437 6.27148 1.896Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_17", + "d": "M2.625 5.8335C2.625 6.07511 2.42912 6.271 2.1875 6.271C1.94588 6.271 1.75 6.07511 1.75 5.8335C1.75 5.59187 1.94588 5.396 2.1875 5.396C2.42912 5.396 2.625 5.59187 2.625 5.8335Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_18", + "d": "M2.625 8.1665C2.625 8.40812 2.42912 8.604 2.1875 8.604C1.94588 8.604 1.75 8.40812 1.75 8.1665C1.75 7.92489 1.94588 7.729 2.1875 7.729C2.42912 7.729 2.625 7.92489 2.625 8.1665Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_19", + "d": "M6.27148 12.104C6.27148 12.3456 6.0756 12.5415 5.83398 12.5415C5.59236 12.5415 5.39648 12.3456 5.39648 12.104C5.39648 11.8624 5.59236 11.6665 5.83398 11.6665C6.0756 11.6665 6.27148 11.8624 6.27148 12.104Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_20", + "d": "M6.70898 5.8335C6.70898 6.31673 6.31722 6.7085 5.83398 6.7085C5.35073 6.7085 4.95898 6.31673 4.95898 5.8335C4.95898 5.35025 5.35073 4.9585 5.83398 4.9585C6.31722 4.9585 6.70898 5.35025 6.70898 5.8335Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_21", + "d": "M6.70898 8.1665C6.70898 8.64974 6.31722 9.0415 5.83398 9.0415C5.35073 9.0415 4.95898 8.64974 4.95898 8.1665C4.95898 7.68327 5.35073 7.2915 5.83398 7.2915C6.31722 7.2915 6.70898 7.68327 6.70898 8.1665Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_22", + "d": "M4.37565 5.83333C4.37565 6.15551 4.11448 6.41667 3.79232 6.41667C3.47015 6.41667 3.20898 6.15551 3.20898 5.83333C3.20898 5.51117 3.47015 5.25 3.79232 5.25C4.11448 5.25 4.37565 5.51117 4.37565 5.83333Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_23", + "d": "M4.37565 8.16683C4.37565 8.489 4.11448 8.75016 3.79232 8.75016C3.47015 8.75016 3.20898 8.489 3.20898 8.16683C3.20898 7.84465 3.47015 7.5835 3.79232 7.5835C4.11448 7.5835 4.37565 7.84465 4.37565 8.16683Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "id": "Vector_24", + "d": "M4.37565 10.3543C4.37565 10.6765 4.11448 10.9377 3.79232 10.9377C3.47015 10.9377 3.20898 10.6765 3.20898 10.3543C3.20898 10.0322 3.47015 9.771 3.79232 9.771C4.11448 9.771 4.37565 10.0322 4.37565 10.3543Z", + "fill": "currentColor" + }, + "children": [] + } + ] + } + ] + }, + "name": "ParameterExtractor" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/workflow/ParameterExtractor.tsx b/web/app/components/base/icons/src/vender/workflow/ParameterExtractor.tsx new file mode 100644 index 0000000000..f080f5aaef --- /dev/null +++ b/web/app/components/base/icons/src/vender/workflow/ParameterExtractor.tsx @@ -0,0 +1,16 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './ParameterExtractor.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase' + +const Icon = React.forwardRef, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'ParameterExtractor' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/workflow/index.ts b/web/app/components/base/icons/src/vender/workflow/index.ts index bb79b2c045..94e20ae6a9 100644 --- a/web/app/components/base/icons/src/vender/workflow/index.ts +++ b/web/app/components/base/icons/src/vender/workflow/index.ts @@ -4,9 +4,12 @@ export { default as End } from './End' export { default as Home } from './Home' export { default as Http } from './Http' export { default as IfElse } from './IfElse' +export { default as IterationStart } from './IterationStart' +export { default as Iteration } from './Iteration' export { default as Jinja } from './Jinja' export { default as KnowledgeRetrieval } from './KnowledgeRetrieval' export { default as Llm } from './Llm' +export { default as ParameterExtractor } from './ParameterExtractor' export { default as QuestionClassifier } from './QuestionClassifier' export { default as TemplatingTransform } from './TemplatingTransform' export { default as VariableX } from './VariableX' diff --git a/web/app/components/base/message-log-modal/index.tsx b/web/app/components/base/message-log-modal/index.tsx index 4c389f7e10..3b3d626737 100644 --- a/web/app/components/base/message-log-modal/index.tsx +++ b/web/app/components/base/message-log-modal/index.tsx @@ -1,20 +1,24 @@ import type { FC } from 'react' import { useTranslation } from 'react-i18next' import cn from 'classnames' -import { useEffect, useRef, useState } from 'react' -import { useClickAway } from 'ahooks' +import { useCallback, useEffect, useRef, useState } from 'react' +import { useBoolean, useClickAway } from 'ahooks' +import IterationResultPanel from '../../workflow/run/iteration-result-panel' import { XClose } from '@/app/components/base/icons/src/vender/line/general' import type { IChatItem } from '@/app/components/app/chat/type' import Run from '@/app/components/workflow/run' +import type { NodeTracing } from '@/types/workflow' type MessageLogModalProps = { currentLogItem?: IChatItem + defaultTab?: string width: number fixedWidth?: boolean onCancel: () => void } const MessageLogModal: FC = ({ currentLogItem, + defaultTab = 'DETAIL', width, fixedWidth, onCancel, @@ -32,6 +36,17 @@ const MessageLogModal: FC = ({ setMounted(true) }, []) + const [iterationRunResult, setIterationRunResult] = useState([]) + const [isShowIterationDetail, { + setTrue: doShowIterationDetail, + setFalse: doHideIterationDetail, + }] = useBoolean(false) + + const handleShowIterationDetail = useCallback((detail: NodeTracing[][]) => { + setIterationRunResult(detail) + doShowIterationDetail() + }, [doShowIterationDetail]) + if (!currentLogItem || !currentLogItem.workflow_run_id) return null @@ -53,11 +68,28 @@ const MessageLogModal: FC = ({ }} ref={ref} > -

{t('appLog.runDetail.title')}

- - - - + {isShowIterationDetail + ? ( + + ) + : ( + <> +

{t('appLog.runDetail.title')}

+ + + + + + )} +
) } diff --git a/web/app/components/base/panel/index.tsx b/web/app/components/base/panel/index.tsx deleted file mode 100644 index 11de390fd7..0000000000 --- a/web/app/components/base/panel/index.tsx +++ /dev/null @@ -1,78 +0,0 @@ -'use client' -import type { FC } from 'react' -import React, { useEffect } from 'react' -import cn from 'classnames' -import { useBoolean } from 'ahooks' -import { ChevronRightIcon } from '@heroicons/react/24/outline' - -export type IPanelProps = { - className?: string - headerIcon: React.ReactNode - title: React.ReactNode - headerRight?: React.ReactNode - bodyClassName?: string - children: React.ReactNode - keepUnFold?: boolean - foldDisabled?: boolean - onFoldChange?: (fold: boolean) => void - controlUnFold?: number - controlFold?: number -} - -const Panel: FC = ({ - className, - headerIcon, - title, - headerRight, - bodyClassName, - children, - keepUnFold, - foldDisabled = false, - onFoldChange, - controlUnFold, - controlFold, -}) => { - const [fold, { setTrue: setFold, setFalse: setUnFold, toggle: toggleFold }] = useBoolean(!keepUnFold) - useEffect(() => { - onFoldChange?.(fold) - }, [fold]) - - useEffect(() => { - if (controlUnFold) - setUnFold() - }, [controlUnFold]) - - useEffect(() => { - if (controlFold) - setFold() - }, [controlFold]) - - // overflow-hidden - return ( -
- {/* Header */} -
(!foldDisabled && !keepUnFold) && toggleFold()} - className={cn(!fold && 'border-b border-gray-100', 'flex justify-between items-center h-12 bg-gray-50 pl-4 pr-2')}> -
- {headerIcon} -
{title}
-
- {(fold && headerRight) ? headerRight : ''} - {!headerRight && !keepUnFold && ( - - - )} -
- - {/* Main Content */} - - {!fold && !foldDisabled && ( -
- {children} -
- )} -
- ) -} -export default React.memo(Panel) diff --git a/web/app/components/base/portal-to-follow-elem/index.tsx b/web/app/components/base/portal-to-follow-elem/index.tsx index 46f4e1a40f..24d30eb526 100644 --- a/web/app/components/base/portal-to-follow-elem/index.tsx +++ b/web/app/components/base/portal-to-follow-elem/index.tsx @@ -150,8 +150,10 @@ React.HTMLProps if (!context.open) return null + const body = document.body + return ( - +
{ + const isSystem = isSystemVar(variables) + const varName = variablesLength >= 3 ? (variables).slice(-2).join('.') : variables[variablesLength - 1] + return `${isSystem ? 'sys.' : ''}${varName}` + } + )() const [localWorkflowNodesMap, setLocalWorkflowNodesMap] = useState(workflowNodesMap) const node = localWorkflowNodesMap![variables[0]] @@ -86,7 +92,7 @@ const WorkflowVariableBlockComponent = ({
-
{lastVariable}
+
{varName}
{ !node && ( diff --git a/web/app/components/base/select/index.tsx b/web/app/components/base/select/index.tsx index 1b40b27b0a..503aeab635 100644 --- a/web/app/components/base/select/index.tsx +++ b/web/app/components/base/select/index.tsx @@ -37,6 +37,7 @@ export type ISelectProps = { bgClassName?: string placeholder?: string overlayClassName?: string + optionClassName?: string } const Select: FC = ({ className, @@ -47,6 +48,7 @@ const Select: FC = ({ allowSearch = true, bgClassName = 'bg-gray-100', overlayClassName, + optionClassName, }) => { const [query, setQuery] = useState('') const [open, setOpen] = useState(false) @@ -97,7 +99,7 @@ const Select: FC = ({ if (!disabled) setOpen(!open) } - } className={`flex items-center h-9 w-full rounded-lg border-0 ${bgClassName} py-1.5 pl-3 pr-10 shadow-sm sm:text-sm sm:leading-6 focus-visible:outline-none focus-visible:bg-gray-200 group-hover:bg-gray-200`}> + } className={classNames(optionClassName, `flex items-center h-9 w-full rounded-lg border-0 ${bgClassName} py-1.5 pl-3 pr-10 shadow-sm sm:text-sm sm:leading-6 focus-visible:outline-none focus-visible:bg-gray-200 group-hover:bg-gray-200`)}>
{selectedItem?.name}
} = ({ value={item} className={({ active }: { active: boolean }) => classNames( + optionClassName, 'relative cursor-default select-none py-2 pl-3 pr-9 rounded-lg hover:bg-gray-100 text-gray-700', active ? 'bg-gray-100' : '', ) diff --git a/web/app/components/header/HeaderWrapper.tsx b/web/app/components/header/HeaderWrapper.tsx index e0f6cfc111..ba51216dd7 100644 --- a/web/app/components/header/HeaderWrapper.tsx +++ b/web/app/components/header/HeaderWrapper.tsx @@ -11,7 +11,7 @@ const HeaderWrapper = ({ children, }: HeaderWrapperProps) => { const pathname = usePathname() - const isBordered = ['/apps', '/datasets', '/datasets/create'].includes(pathname) + const isBordered = ['/apps', '/datasets', '/datasets/create', '/tools'].includes(pathname) return (
= ({ key={`${modelId}-${parameter.name}`} className='mb-4' parameterRule={parameter} - value={completionParams[parameter.name]} + value={completionParams?.[parameter.name]} onChange={v => handleParamChange(parameter.name, v)} onSwitch={(checked, assignValue) => handleSwitch(parameter.name, checked, assignValue)} isInWorkflow={isInWorkflow} diff --git a/web/app/components/share/text-generation/result/index.tsx b/web/app/components/share/text-generation/result/index.tsx index cf6c31c6c3..9272c20548 100644 --- a/web/app/components/share/text-generation/result/index.tsx +++ b/web/app/components/share/text-generation/result/index.tsx @@ -192,6 +192,8 @@ const Result: FC = ({ })() if (isWorkflow) { + let isInIteration = false + sendWorkflowMessage( data, { @@ -205,7 +207,34 @@ const Result: FC = ({ }) setRespondingFalse() }, + onIterationStart: ({ data }) => { + setWorkflowProccessData(produce(getWorkflowProccessData()!, (draft) => { + draft.expand = true + draft.tracing!.push({ + ...data, + status: NodeRunningStatus.Running, + expand: true, + } as any) + })) + isInIteration = true + }, + onIterationNext: () => { + }, + onIterationFinish: ({ data }) => { + setWorkflowProccessData(produce(getWorkflowProccessData()!, (draft) => { + draft.expand = true + // const iteration = draft.tracing![draft.tracing!.length - 1] + draft.tracing![draft.tracing!.length - 1] = { + ...data, + expand: !!data.error, + } as any + })) + isInIteration = false + }, onNodeStarted: ({ data }) => { + if (isInIteration) + return + setWorkflowProccessData(produce(getWorkflowProccessData()!, (draft) => { draft.expand = true draft.tracing!.push({ @@ -216,6 +245,9 @@ const Result: FC = ({ })) }, onNodeFinished: ({ data }) => { + if (isInIteration) + return + setWorkflowProccessData(produce(getWorkflowProccessData()!, (draft) => { const currentIndex = draft.tracing!.findIndex(trace => trace.node_id === data.node_id) if (currentIndex > -1 && draft.tracing) { diff --git a/web/app/components/tools/add-tool-modal/D.png b/web/app/components/tools/add-tool-modal/D.png new file mode 100644 index 0000000000000000000000000000000000000000..70b829c821fe5e49cf22ca1c0b55e48d2e46ed9b GIT binary patch literal 1139 zcmV-(1dRKMP)ghe-o=);zf`mveCBhOfuzmxGh>&;$kjN2_KtyO0AQB`W0SU>jKuBbFLHx>gZzVcTD<-A4#`h5)OI^;1GzfBW;zKVROAnPKv<=+ zU;lWuyZ2f~g^W?rH49^C=vW0o?cmWl$HP;qfUnlWk#ni#kqIxy34 zR)_Rx{l^r>8IVeu|KGwnV}r7DlQe;8g(kD}01Q$dOh6+$RDlU(bigu&llstk*urUR zgERp;v(+zu?pG5dcRG$PZpXUjiM$w;iRVQ73m~&o)tT3_+X^Uo8@>>rEW0N`XGijJ z?MqtoP_qVB`BRSPp>Znrnb>iO=EV3~YHsW|8 zvM`->dU#PF%u606?DRpF(8#W-a8IOap($0vQi}AFrk|Y?+ez1t768W#a>s!hn+~M) znQSk?ds%W}39kMEda@DrNpgjnFT@O|CzU}EPLC6uupDJ$BD^Ic80D{4H4t08m-l;8 z$5Ffj2(9}OZqNql^B09#0lfe263$PP=BMAeIgSPhfbd$+8Y6w$5S<3AmNwV;?T+A| zzn;S9ADzSVTd=YRX#HiMqYf%;oiL#}(80uxQN-THkwI<=o;}^eF;~morR8GBeK$sr zDGD|X0TiVRe5@3HTX!A+GT#b#UlHt?$<2!q{(H8E#nO7_PPWezL2GFB0o}L~?K$D! z|5ipeJfpO$R-0@<^0yP9n*uv#MMtQ4L$#k0 z3UphP)yOv0Nq;#2M#28TI1vG5peB4_I<6vyF4y|`AHZ}z? z3MfDfaC7lCZY^}(F%fKv1BoWzOzhq!cQr>MHbAB#fQb!%`HfaxI4NZUqR{|^ortVg z-)0nSKW1tqi#i}(uMgij&kq$y9T9XuxI^@Z-*hG!t2A|{5_8Ng*7RlTqY@`- z4?<=WQOloH=8R44CJ0!5&_UUJf`lKwb&lHrN>Od??$90QaTSB^NS~f=Qp4i0XHfc52zolbh2Z5zJZeRcN) zFX0tW1FG#3;`ch&Q3VH4+j1_p9Dv_B7ni3?+`t>2{{f?Ff}eEgCd&W-002ovPDHLk FV1i2L`F;QZ literal 0 HcmV?d00001 diff --git a/web/app/components/tools/add-tool-modal/category.tsx b/web/app/components/tools/add-tool-modal/category.tsx new file mode 100644 index 0000000000..e40a735f0f --- /dev/null +++ b/web/app/components/tools/add-tool-modal/category.tsx @@ -0,0 +1,70 @@ +'use client' +import { useRef } from 'react' +import cn from 'classnames' +import { useTranslation } from 'react-i18next' +import { useContext } from 'use-context-selector' +import { useMount } from 'ahooks' +import { Apps02 } from '@/app/components/base/icons/src/vender/line/others' +import I18n from '@/context/i18n' +import { getLanguage } from '@/i18n/language' +import { useStore as useLabelStore } from '@/app/components/tools/labels/store' +import { fetchLabelList } from '@/service/tools' + +type Props = { + value: string + onSelect: (type: string) => void +} + +const Icon = ({ svgString, active }: { svgString: string; active: boolean }) => { + const svgRef = useRef(null) + const SVGParsor = (svg: string) => { + if (!svg) + return null + const parser = new DOMParser() + const doc = parser.parseFromString(svg, 'image/svg+xml') + console.log(doc.documentElement) + return doc.documentElement + } + useMount(() => { + const svgElement = SVGParsor(svgString) + if (svgRef.current && svgElement) + svgRef.current.appendChild(svgElement) + }) + return +} + +const Category = ({ + value, + onSelect, +}: Props) => { + const { t } = useTranslation() + const { locale } = useContext(I18n) + const language = getLanguage(locale) + const labelList = useLabelStore(s => s.labelList) + const setLabelList = useLabelStore(s => s.setLabelList) + + useMount(() => { + fetchLabelList().then((res) => { + setLabelList(res) + }) + }) + + return ( +
+
{t('tools.addToolModal.category').toLocaleUpperCase()}
+
onSelect('')}> + + {t('tools.type.all')} +
+ {labelList.map(label => ( +
onSelect(label.name)}> +
+ +
+ {label.label[language]} +
+ ))} +
+ ) +} +export default Category diff --git a/web/app/components/tools/add-tool-modal/empty.png b/web/app/components/tools/add-tool-modal/empty.png new file mode 100644 index 0000000000000000000000000000000000000000..da4007e45acab1ae84864f5e7187cab966d6286d GIT binary patch literal 30290 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
)^_xjGZJnY^#>{Na zc8k{vA)lzLKV)@Ilf5)n!i=OMm-3A#-u>DYa~{9K?Aahd-jv^vs9N&S^dH_cNebpooQvxCrWJ^x_7-;r$kCkmbd`+YTzZxihTxt-Cux>46^1`V@oqnDE!HeLN6k9)uj-Gay`R zo0LZOme={zC=BXDYp7|QH=5>z5iB#e!28k39S!Re-s7Vm*K=PXwarf!G@r?qnQuzF z^s>PYB2jF6gT~T`psklJJSV9R5@&Xup77sNkjifM-=J+POU7;f)>614;o;AFe*HwV zkK1Cmm`jdva`bEcrzaBGL8yNJ`nrdc^63B0_X}z|gbgCFEBe8=z8geDPVWa3Quj+b zkqL0Z*YHe+XS>bR4?QxtIPWV+qL1y3CXzxA>R1bmN$r`a>DpB5^8K*H^ugEWNNCx! zE#F7D>C5W)I%WFATT?FXdR}LOQD*5X*5cYhb#B<;8?EBtwXw16yj`#%NFy#n-Yb7) zPzqA$q8D?-BmmuHIh|>~b;OqPE}pp|JN))VQE&OwbRn%j`rv1DRBHt5lY@Lvs8;FU zy1i>09ZN3QOFpGN130yvLhotM%x%ftAIU*PQcH9A%5Gs>;6T&wU2%8aiC3oh=a4RK zJ?zrkMEMPp(S?SHL5%VTIrYyx4*R0Mj^4z`y;<;BvU)iYs)rf`aVjPiX|Iige*8}T zPo_^emt?eNcJgVzF-Fx-=?zwZes?QjbHIAs^vGy8S9gf(6Zz9kii)$27g(#EP3l0< zysjK`Yo%AOIyx<-Hf^(YzT4N1-e>WR0;xMqeR3m+z&Jq}*2Y2Jk2n4hZ23{@BTBq1 z1`S_(oI9Y~`7`%k7+KBjb#2LQbRgZTXTwAMpmkFvHo%+iDA_fldA-u72jzna|L(BH zn39jsRiHV89&Y~K4C|^E9dNsPsE1($3K-k&1v-T^bX{hPf+#D!Dg&sO&mMxf*VPEO}Z?EO~3 zFvFYr|9jYD#`(mX68~lHFjRhU^C=NRL-|;Q2GLqKG8$1Xg~&!qHddbB$B@`n>s7U| z=mlvf9gY3W{lax~Ej)SrJxOPKT&kECF6pRf{V*Lj9CylSe;eNYiLpOwJ4a~8$s43f4$>y35+>`KHR^n_2DGqSTt}+n6!GL^q8n&LrXr< z-sRqg20@jIdykKtCr`dFE7xuFDOtd7}R6tB2ogDuldfuApWMp3+oJ3=kjKp<*M|M zwJgI+3tz09+FC3(@1}?545baeIoVPU;Msd*T7{#zcoLSIJ9iXh={Lw@4o6GxW--XvIHv}~_J|F%(ar|i*b?pV)SHo0 zfp&GX@&379EWRV+^vwJ;qwjj`O+H(QMYOxI6`xMC~~ zc4YmNp?sRm!DPoP)=H{6ZRyFsY`WIQP_7ZDw)+#St4jQ1?Jy#balIXQJXgBgsPI=G z&!#iw-cOD>M${X9y@3_IjGgK>Av2%PtyJORj;~9oqG%t g7XR;CU+KsUSxb`z3Z3sC{QHHGf$8 { + const { t } = useTranslation() + + return ( +
+
+
{t('tools.addToolModal.emptyTitle')}
+
{t('tools.addToolModal.emptyTip')}
+
+ ) +} + +export default Empty diff --git a/web/app/components/tools/add-tool-modal/index.tsx b/web/app/components/tools/add-tool-modal/index.tsx new file mode 100644 index 0000000000..53e25fb6d8 --- /dev/null +++ b/web/app/components/tools/add-tool-modal/index.tsx @@ -0,0 +1,235 @@ +'use client' +import type { FC } from 'react' +import React, { useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useContext } from 'use-context-selector' +import produce from 'immer' +import cn from 'classnames' +import { useMount } from 'ahooks' +import type { Collection, CustomCollectionBackend, Tool } from '../types' +import Type from './type' +import Category from './category' +import Tools from './tools' +import I18n from '@/context/i18n' +import { getLanguage } from '@/i18n/language' +import Drawer from '@/app/components/base/drawer' +import Button from '@/app/components/base/button' +import Loading from '@/app/components/base/loading' +import SearchInput from '@/app/components/base/search-input' +import { Plus, XClose } from '@/app/components/base/icons/src/vender/line/general' +import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal' +import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials' +import { + createCustomCollection, + fetchAllBuiltInTools, + fetchAllCustomTools, + fetchAllWorkflowTools, + removeBuiltInToolCredential, + updateBuiltInToolCredential, +} from '@/service/tools' +import type { ToolWithProvider } from '@/app/components/workflow/types' +import Toast from '@/app/components/base/toast' +import ConfigContext from '@/context/debug-configuration' +import type { ModelConfig } from '@/models/debug' + +type Props = { + onHide: () => void +} +// Add and Edit +const AddToolModal: FC = ({ + onHide, +}) => { + const { t } = useTranslation() + const { locale } = useContext(I18n) + const language = getLanguage(locale) + const [currentType, setCurrentType] = useState('builtin') + const [currentCategory, setCurrentCategory] = useState('') + const [keywords, setKeywords] = useState('') + const handleKeywordsChange = (value: string) => { + setKeywords(value) + } + const [toolList, setToolList] = useState([]) + const [listLoading, setListLoading] = useState(true) + const getAllTools = async () => { + setListLoading(true) + const buildInTools = await fetchAllBuiltInTools() + const customTools = await fetchAllCustomTools() + const workflowTools = await fetchAllWorkflowTools() + const mergedToolList = [ + ...buildInTools, + ...customTools, + ...workflowTools.filter((toolWithProvider) => { + return !toolWithProvider.tools.some((tool) => { + return !!tool.parameters.find(item => item.name === '__image') + }) + }), + ] + setToolList(mergedToolList) + setListLoading(false) + } + const filteredList = useMemo(() => { + return toolList.filter((toolWithProvider) => { + if (currentType === 'all') + return true + else + return toolWithProvider.type === currentType + }).filter((toolWithProvider) => { + if (!currentCategory) + return true + else + return toolWithProvider.labels.includes(currentCategory) + }).filter((toolWithProvider) => { + return toolWithProvider.tools.some((tool) => { + return tool.label[language].toLowerCase().includes(keywords.toLowerCase()) + }) + }) + }, [currentType, currentCategory, toolList, keywords, language]) + + const { + modelConfig, + setModelConfig, + } = useContext(ConfigContext) + + const [isShowEditCollectionToolModal, setIsShowEditCustomCollectionModal] = useState(false) + const doCreateCustomToolCollection = async (data: CustomCollectionBackend) => { + await createCustomCollection(data) + Toast.notify({ + type: 'success', + message: t('common.api.actionSuccess'), + }) + setIsShowEditCustomCollectionModal(false) + getAllTools() + } + const [showSettingAuth, setShowSettingAuth] = useState(false) + const [collection, setCollection] = useState() + const toolSelectHandle = (collection: Collection, tool: Tool) => { + const parameters: Record = {} + if (tool.parameters) { + tool.parameters.forEach((item) => { + parameters[item.name] = '' + }) + } + + const nexModelConfig = produce(modelConfig, (draft: ModelConfig) => { + draft.agentConfig.tools.push({ + provider_id: collection.id || collection.name, + provider_type: collection.type, + provider_name: collection.name, + tool_name: tool.name, + tool_label: tool.label[locale] || tool.label[locale.replaceAll('-', '_')], + tool_parameters: parameters, + enabled: true, + }) + }) + setModelConfig(nexModelConfig) + } + const authSelectHandle = (provider: Collection) => { + setCollection(provider) + setShowSettingAuth(true) + } + const updateBuiltinAuth = async (value: Record) => { + if (!collection) + return + await updateBuiltInToolCredential(collection.name, value) + Toast.notify({ + type: 'success', + message: t('common.api.actionSuccess'), + }) + await getAllTools() + setShowSettingAuth(false) + } + const removeBuiltinAuth = async () => { + if (!collection) + return + await removeBuiltInToolCredential(collection.name) + Toast.notify({ + type: 'success', + message: t('common.api.actionSuccess'), + }) + await getAllTools() + setShowSettingAuth(false) + } + + useMount(() => { + getAllTools() + }) + + return ( + <> + +
+
+
+
{t('tools.addTool')}
+
+ +
+
+
+ + +
+
+
+
+
+ +
+
+
+ +
+
+ {listLoading && ( +
+ +
+ )} + {!listLoading && ( + + )} +
+
+
+ {isShowEditCollectionToolModal && ( + setIsShowEditCustomCollectionModal(false)} + onAdd={doCreateCustomToolCollection} + /> + )} + {showSettingAuth && collection && ( + setShowSettingAuth(false)} + onSaved={updateBuiltinAuth} + onRemove={removeBuiltinAuth} + /> + )} + + + ) +} +export default React.memo(AddToolModal) diff --git a/web/app/components/tools/add-tool-modal/tools.tsx b/web/app/components/tools/add-tool-modal/tools.tsx new file mode 100644 index 0000000000..9ba5ba81a9 --- /dev/null +++ b/web/app/components/tools/add-tool-modal/tools.tsx @@ -0,0 +1,146 @@ +import { + memo, + useCallback, +} from 'react' +import cn from 'classnames' +import { useTranslation } from 'react-i18next' +import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows' +import { Check, Plus } from '@/app/components/base/icons/src/vender/line/general' +import { Tag01 } from '@/app/components/base/icons/src/vender/line/financeAndECommerce' +import type { ToolWithProvider } from '@/app/components/workflow/types' +import { BlockEnum } from '@/app/components/workflow/types' +import BlockIcon from '@/app/components/workflow/block-icon' +import Tooltip from '@/app/components/base/tooltip' +import Button from '@/app/components/base/button' +import { useGetLanguage } from '@/context/i18n' +import { useStore as useLabelStore } from '@/app/components/tools/labels/store' +import Empty from '@/app/components/tools/add-tool-modal/empty' +import type { Tool } from '@/app/components/tools/types' +import { CollectionType } from '@/app/components/tools/types' +import type { AgentTool } from '@/types/app' +import { MAX_TOOLS_NUM } from '@/config' + +type ToolsProps = { + showWorkflowEmpty: boolean + tools: ToolWithProvider[] + addedTools: AgentTool[] + onSelect: (provider: ToolWithProvider, tool: Tool) => void + onAuthSetup: (provider: ToolWithProvider) => void +} +const Blocks = ({ + showWorkflowEmpty, + tools, + addedTools, + onSelect, + onAuthSetup, +}: ToolsProps) => { + const { t } = useTranslation() + const language = useGetLanguage() + const labelList = useLabelStore(s => s.labelList) + const addable = addedTools.length < MAX_TOOLS_NUM + + const renderGroup = useCallback((toolWithProvider: ToolWithProvider) => { + const list = toolWithProvider.tools + const needAuth = toolWithProvider.allow_delete && !toolWithProvider.is_team_authorization && toolWithProvider.type === CollectionType.builtIn + + return ( +
+
+ {toolWithProvider.label[language]} + {t('tools.addToolModal.manageInTools')} +
+ {list.map((tool) => { + const labelContent = (() => { + if (!tool.labels) + return '' + return tool.labels.map((name) => { + const label = labelList.find(item => item.name === name) + return label?.label[language] + }).filter(Boolean).join(', ') + })() + const added = !!addedTools?.find(v => v.provider_id === toolWithProvider.id && v.provider_type === toolWithProvider.type && v.tool_name === tool.name) + return ( + + +
{tool.label[language]}
+
{tool.description[language]}
+ {tool.labels?.length > 0 && ( +
+
+ +
{labelContent}
+
+
+ )} +
+ )} + noArrow + > +
+ +
{tool.label[language]}
+ {!needAuth && added && ( +
+ + {t('tools.addToolModal.added').toLocaleUpperCase()} +
+ )} + {!needAuth && !added && addable && ( + + )} + {needAuth && ( + + )} +
+ + ) + })} +
+ ) + }, [addable, language, t, labelList, addedTools, onAuthSetup, onSelect]) + + return ( +
+ {!tools.length && !showWorkflowEmpty && ( +
{t('workflow.tabs.noResult')}
+ )} + {!tools.length && showWorkflowEmpty && ( +
+ +
+ )} + {!!tools.length && tools.map(renderGroup)} +
+ ) +} + +export default memo(Blocks) diff --git a/web/app/components/tools/add-tool-modal/type.tsx b/web/app/components/tools/add-tool-modal/type.tsx new file mode 100644 index 0000000000..74e5e73096 --- /dev/null +++ b/web/app/components/tools/add-tool-modal/type.tsx @@ -0,0 +1,34 @@ +'use client' +import cn from 'classnames' +import { useTranslation } from 'react-i18next' +import { Exchange02, FileCode } from '@/app/components/base/icons/src/vender/line/others' + +type Props = { + value: string + onSelect: (type: string) => void +} + +const Types = ({ + value, + onSelect, +}: Props) => { + const { t } = useTranslation() + + return ( +
+
onSelect('builtin')}> +
+ {t('tools.type.builtIn')} +
+
onSelect('api')}> + + {t('tools.type.custom')} +
+
onSelect('workflow')}> + + {t('tools.type.workflow')} +
+
+ ) +} +export default Types diff --git a/web/app/components/tools/contribute.tsx b/web/app/components/tools/contribute.tsx deleted file mode 100644 index aeec959614..0000000000 --- a/web/app/components/tools/contribute.tsx +++ /dev/null @@ -1,31 +0,0 @@ -'use client' -import type { FC } from 'react' -import React from 'react' -import { useTranslation } from 'react-i18next' -import { Heart02 } from '../base/icons/src/vender/solid/education' -import { BookOpen01 } from '../base/icons/src/vender/line/education' - -const Contribute: FC = () => { - const { t } = useTranslation() - - return ( -
- ) -} -export default React.memo(Contribute) diff --git a/web/app/components/tools/edit-custom-collection-modal/config-credentials.tsx b/web/app/components/tools/edit-custom-collection-modal/config-credentials.tsx index 9da0ff7dcc..cdff542431 100644 --- a/web/app/components/tools/edit-custom-collection-modal/config-credentials.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/config-credentials.tsx @@ -12,6 +12,7 @@ import Radio from '@/app/components/base/radio/ui' import { AuthHeaderPrefix, AuthType } from '@/app/components/tools/types' type Props = { + positionCenter?: boolean credential: Credential onChange: (credential: Credential) => void onHide: () => void @@ -38,6 +39,7 @@ const SelectItem: FC = ({ text, value, isChecked, onClick }) => { } const ConfigCredential: FC = ({ + positionCenter, credential, onChange, onHide, @@ -48,6 +50,7 @@ const ConfigCredential: FC = ({ return ( void onAdd?: (payload: CustomCollectionBackend) => void @@ -27,6 +29,7 @@ type Props = { } // Add and Edit const EditCustomCollectionModal: FC = ({ + positionLeft, payload, onHide, onAdd, @@ -114,6 +117,11 @@ const EditCustomCollectionModal: FC = ({ const [currTool, setCurrTool] = useState(null) const [isShowTestApi, setIsShowTestApi] = useState(false) + const [labels, setLabels] = useState(payload?.labels || []) + const handleLabelSelect = (value: string[]) => { + setLabels(value) + } + const handleSave = () => { // const postData = clone(customCollection) const postData = produce(customCollection, (draft) => { @@ -124,6 +132,8 @@ const EditCustomCollectionModal: FC = ({ delete draft.credentials.api_key_header_prefix delete draft.credentials.api_key_value } + + draft.labels = labels }) if (isAdd) { @@ -154,10 +164,11 @@ const EditCustomCollectionModal: FC = ({ <> = ({
+ {/* Labels */} +
+
{t('tools.createTool.toolInput.label')}
+ +
+ + {/* Privacy Policy */}
{t('tools.createTool.privacyPolicy')}
= ({ ) }
- +
@@ -308,6 +326,7 @@ const EditCustomCollectionModal: FC = ({ />} {credentialsModalShow && ( setCredentialsModalShow(false)} @@ -315,6 +334,7 @@ const EditCustomCollectionModal: FC = ({ } {isShowTestApi && ( setIsShowTestApi(false)} diff --git a/web/app/components/tools/edit-custom-collection-modal/test-api.tsx b/web/app/components/tools/edit-custom-collection-modal/test-api.tsx index 791ac5edbf..793589800a 100644 --- a/web/app/components/tools/edit-custom-collection-modal/test-api.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/test-api.tsx @@ -13,6 +13,7 @@ import { testAPIAvailable } from '@/service/tools' import { getLanguage } from '@/i18n/language' type Props = { + positionCenter?: boolean customCollection: CustomCollectionBackend tool: CustomParamSchema onHide: () => void @@ -21,6 +22,7 @@ type Props = { const keyClassNames = 'py-2 leading-5 text-sm font-medium text-gray-900' const TestApi: FC = ({ + positionCenter, customCollection, tool, onHide, @@ -57,6 +59,7 @@ const TestApi: FC = ({ <> = ({ /> {credentialsModalShow && ( setCredentialsModalShow(false)} diff --git a/web/app/components/tools/index.tsx b/web/app/components/tools/index.tsx deleted file mode 100644 index b467dcbad5..0000000000 --- a/web/app/components/tools/index.tsx +++ /dev/null @@ -1,259 +0,0 @@ -'use client' -import type { FC } from 'react' -import React, { useEffect, useState } from 'react' -import { useTranslation } from 'react-i18next' -import cn from 'classnames' -import Button from '../base/button' -import { Plus } from '../base/icons/src/vender/line/general' -import Toast from '../base/toast' -import type { Collection, CustomCollectionBackend, Tool } from './types' -import { CollectionType, LOC } from './types' -import ToolNavList from './tool-nav-list' -import Search from './search' -import Contribute from './contribute' -import ToolList from './tool-list' -import EditCustomToolModal from './edit-custom-collection-modal' -import NoCustomTool from './info/no-custom-tool' -import NoSearchRes from './info/no-search-res' -import NoCustomToolPlaceholder from './no-custom-tool-placeholder' -import { useTabSearchParams } from '@/hooks/use-tab-searchparams' -import TabSlider from '@/app/components/base/tab-slider' -import { createCustomCollection, fetchCollectionList as doFetchCollectionList, fetchBuiltInToolList, fetchCustomToolList, fetchModelToolList } from '@/service/tools' -import type { AgentTool } from '@/types/app' - -type Props = { - loc: LOC - addedTools?: AgentTool[] - onAddTool?: (collection: Collection, payload: Tool) => void - selectedProviderId?: string -} - -const Tools: FC = ({ - loc, - addedTools, - onAddTool, - selectedProviderId, -}) => { - const { t } = useTranslation() - const isInToolsPage = loc === LOC.tools - const isInDebugPage = !isInToolsPage - - const [collectionList, setCollectionList] = useState([]) - const [currCollectionIndex, setCurrCollectionIndex] = useState(null) - - const [isDetailLoading, setIsDetailLoading] = useState(false) - - const fetchCollectionList = async () => { - const list = await doFetchCollectionList() - setCollectionList(list) - if (list.length > 0 && currCollectionIndex === null) { - let index = 0 - if (selectedProviderId) - index = list.findIndex(item => item.id === selectedProviderId) - - setCurrCollectionIndex(index || 0) - } - } - useEffect(() => { - fetchCollectionList() - }, []) - - const collectionTypeOptions = (() => { - const res = [ - { value: CollectionType.builtIn, text: t('tools.type.builtIn') }, - { value: CollectionType.custom, text: t('tools.type.custom') }, - ] - if (!isInToolsPage) - res.unshift({ value: CollectionType.all, text: t('tools.type.all') }) - return res - })() - - const [query, setQuery] = useState('') - const [toolPageCollectionType, setToolPageCollectionType] = useTabSearchParams({ - defaultTab: collectionTypeOptions[0].value, - }) - const [appPageCollectionType, setAppPageCollectionType] = useState(collectionTypeOptions[0].value) - const { collectionType, setCollectionType } = (() => { - if (isInToolsPage) { - return { - collectionType: toolPageCollectionType, - setCollectionType: setToolPageCollectionType, - } - } - return { - collectionType: appPageCollectionType, - setCollectionType: setAppPageCollectionType, - } - })() - - const showCollectionList = (() => { - let typeFilteredList: Collection[] = [] - if (collectionType === CollectionType.all) - typeFilteredList = collectionList.filter(item => item.type !== CollectionType.model) - else if (collectionType === CollectionType.builtIn) - typeFilteredList = collectionList.filter(item => item.type === CollectionType.builtIn) - else if (collectionType === CollectionType.custom) - typeFilteredList = collectionList.filter(item => item.type === CollectionType.custom) - if (query) - return typeFilteredList.filter(item => item.name.includes(query)) - - return typeFilteredList - })() - - const hasNoCustomCollection = !collectionList.find(item => item.type === CollectionType.custom) - - useEffect(() => { - setCurrCollectionIndex(0) - }, [collectionType]) - - const currCollection = (() => { - if (currCollectionIndex === null) - return null - return showCollectionList[currCollectionIndex] - })() - - const [currTools, setCurrentTools] = useState([]) - useEffect(() => { - if (!currCollection) - return - - (async () => { - setIsDetailLoading(true) - try { - if (currCollection.type === CollectionType.builtIn) { - const list = await fetchBuiltInToolList(currCollection.name) - setCurrentTools(list) - } - else if (currCollection.type === CollectionType.model) { - const list = await fetchModelToolList(currCollection.name) - setCurrentTools(list) - } - else { - const list = await fetchCustomToolList(currCollection.name) - setCurrentTools(list) - } - } - catch (e) { } - setIsDetailLoading(false) - })() - }, [currCollection?.name, currCollection?.type]) - - const [isShowEditCollectionToolModal, setIsShowEditCollectionToolModal] = useState(false) - const handleCreateToolCollection = () => { - setIsShowEditCollectionToolModal(true) - } - - const doCreateCustomToolCollection = async (data: CustomCollectionBackend) => { - await createCustomCollection(data) - Toast.notify({ - type: 'success', - message: t('common.api.actionSuccess'), - }) - await fetchCollectionList() - setIsShowEditCollectionToolModal(false) - } - - return ( - <> -
- {/* sidebar */} -
- {isInToolsPage && ( - - )} - - {isInDebugPage && ( -
- - -
- )} - - setCollectionType(v as CollectionType)} - options={collectionTypeOptions} - /> - {isInToolsPage && ( - - )} - - {(collectionType === CollectionType.custom && hasNoCustomCollection) - ? ( -
- -
- ) - : ( - (showCollectionList.length > 0 || !query) - ? - : ( -
- { setQuery('') }} - /> -
- ) - )} - - {loc === LOC.tools && ( - - )} -
- - {/* tools */} -
-
- {!(collectionType === CollectionType.custom && hasNoCustomCollection) && showCollectionList.length > 0 && ( - { - setCurrCollectionIndex(0) - fetchCollectionList() - }} - isLoading={isDetailLoading} - /> - )} - - {collectionType === CollectionType.custom && hasNoCustomCollection && ( - - )} -
-
-
- {isShowEditCollectionToolModal && ( - setIsShowEditCollectionToolModal(false)} - onAdd={doCreateCustomToolCollection} - /> - )} - - ) -} -export default React.memo(Tools) diff --git a/web/app/components/tools/info/no-custom-tool.tsx b/web/app/components/tools/info/no-custom-tool.tsx deleted file mode 100644 index 91c09253c1..0000000000 --- a/web/app/components/tools/info/no-custom-tool.tsx +++ /dev/null @@ -1,38 +0,0 @@ -'use client' -import type { FC } from 'react' -import React from 'react' -import { useTranslation } from 'react-i18next' -import { Icon3Dots } from '../../base/icons/src/public/other' -import { Tools } from '@/app/components/base/icons/src/public/header-nav/tools' -type Props = { - onCreateTool: () => void -} - -const NoCustomTool: FC = ({ - onCreateTool, -}) => { - const { t } = useTranslation() - - return ( -
-
- -
-
-
- {t('tools.noCustomTool.title')} -
-
- {t('tools.noCustomTool.content')} -
-
- {t('tools.noCustomTool.createTool')} -
-
-
- ) -} -export default React.memo(NoCustomTool) diff --git a/web/app/components/tools/info/no-search-res.tsx b/web/app/components/tools/info/no-search-res.tsx deleted file mode 100644 index f4752ab184..0000000000 --- a/web/app/components/tools/info/no-search-res.tsx +++ /dev/null @@ -1,38 +0,0 @@ -'use client' -import type { FC } from 'react' -import React from 'react' -import { useTranslation } from 'react-i18next' -import { SearchMd } from '../../base/icons/src/vender/solid/general' - -type Props = { - onReset: () => void -} - -const NoSearchRes: FC = ({ - onReset, -}) => { - const { t } = useTranslation() - - return ( -
-
- -
-
-
- {t('tools.noSearchRes.title')} -
-
- {t('tools.noSearchRes.content')} -
-
- {t('tools.noSearchRes.reset')} -
-
-
- ) -} -export default React.memo(NoSearchRes) diff --git a/web/app/components/tools/labels/constant.ts b/web/app/components/tools/labels/constant.ts new file mode 100644 index 0000000000..3f073859d9 --- /dev/null +++ b/web/app/components/tools/labels/constant.ts @@ -0,0 +1,6 @@ +import type { TypeWithI18N } from '@/app/components/header/account-setting/model-provider-page/declarations' +export type Label = { + name: string + icon: string + label: TypeWithI18N +} diff --git a/web/app/components/tools/labels/filter.tsx b/web/app/components/tools/labels/filter.tsx new file mode 100644 index 0000000000..23c2b46524 --- /dev/null +++ b/web/app/components/tools/labels/filter.tsx @@ -0,0 +1,144 @@ +import type { FC } from 'react' +import { useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useContext } from 'use-context-selector' +import { useDebounceFn, useMount } from 'ahooks' +import cn from 'classnames' +import { useStore as useLabelStore } from './store' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import SearchInput from '@/app/components/base/search-input' +import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows' +import { Tag01, Tag03 } from '@/app/components/base/icons/src/vender/line/financeAndECommerce' +import { Check } from '@/app/components/base/icons/src/vender/line/general' +import { XCircle } from '@/app/components/base/icons/src/vender/solid/general' +import type { Label } from '@/app/components/tools/labels/constant' +import { fetchLabelList } from '@/service/tools' +import I18n from '@/context/i18n' +import { getLanguage } from '@/i18n/language' + +type LabelFilterProps = { + value: string[] + onChange: (v: string[]) => void +} +const LabelFilter: FC = ({ + value, + onChange, +}) => { + const { t } = useTranslation() + const { locale } = useContext(I18n) + const language = getLanguage(locale) + const [open, setOpen] = useState(false) + + const labelList = useLabelStore(s => s.labelList) + const setLabelList = useLabelStore(s => s.setLabelList) + + const [keywords, setKeywords] = useState('') + const [searchKeywords, setSearchKeywords] = useState('') + const { run: handleSearch } = useDebounceFn(() => { + setSearchKeywords(keywords) + }, { wait: 500 }) + const handleKeywordsChange = (value: string) => { + setKeywords(value) + handleSearch() + } + + const filteredLabelList = useMemo(() => { + return labelList.filter(label => label.name.includes(searchKeywords)) + }, [labelList, searchKeywords]) + + const currentLabel = useMemo(() => { + return labelList.find(label => label.name === value[0]) + }, [value, labelList]) + + const selectLabel = (label: Label) => { + if (value.includes(label.name)) + onChange(value.filter(v => v !== label.name)) + else + onChange([...value, label.name]) + } + + useMount(() => { + fetchLabelList().then((res) => { + setLabelList(res) + }) + }) + + return ( + +
+ setOpen(v => !v)} + className='block' + > +
+
+ +
+
+ {!value.length && t('common.tag.placeholder')} + {!!value.length && currentLabel?.label[language]} +
+ {value.length > 1 && ( +
{`+${value.length - 1}`}
+ )} + {!value.length && ( +
+ +
+ )} + {!!value.length && ( +
{ + e.stopPropagation() + onChange([]) + }}> + +
+ )} +
+
+ +
+
+ +
+
+ {filteredLabelList.map(label => ( +
selectLabel(label)} + > +
{label.label[language]}
+ {value.includes(label.name) && } +
+ ))} + {!filteredLabelList.length && ( +
+ +
{t('common.tag.noTag')}
+
+ )} +
+
+
+
+
+ + ) +} + +export default LabelFilter diff --git a/web/app/components/tools/labels/selector.tsx b/web/app/components/tools/labels/selector.tsx new file mode 100644 index 0000000000..958981ba17 --- /dev/null +++ b/web/app/components/tools/labels/selector.tsx @@ -0,0 +1,128 @@ +import type { FC } from 'react' +import { useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useContext } from 'use-context-selector' +import { useDebounceFn, useMount } from 'ahooks' +import cn from 'classnames' +import { useStore as useLabelStore } from './store' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import SearchInput from '@/app/components/base/search-input' +import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows' +import { Tag03 } from '@/app/components/base/icons/src/vender/line/financeAndECommerce' +import Checkbox from '@/app/components/base/checkbox' +import type { Label } from '@/app/components/tools/labels/constant' +import { fetchLabelList } from '@/service/tools' +import I18n from '@/context/i18n' +import { getLanguage } from '@/i18n/language' + +type LabelSelectorProps = { + value: string[] + onChange: (v: string[]) => void +} +const LabelSelector: FC = ({ + value, + onChange, +}) => { + const { t } = useTranslation() + const { locale } = useContext(I18n) + const language = getLanguage(locale) + const [open, setOpen] = useState(false) + + const labelList = useLabelStore(s => s.labelList) + const setLabelList = useLabelStore(s => s.setLabelList) + + const [keywords, setKeywords] = useState('') + const [searchKeywords, setSearchKeywords] = useState('') + const { run: handleSearch } = useDebounceFn(() => { + setSearchKeywords(keywords) + }, { wait: 500 }) + const handleKeywordsChange = (value: string) => { + setKeywords(value) + handleSearch() + } + + const filteredLabelList = useMemo(() => { + return labelList.filter(label => label.name.includes(searchKeywords)) + }, [labelList, searchKeywords]) + + const selectedLabels = useMemo(() => { + return value.map(v => labelList.find(l => l.name === v)?.label[language]).join(', ') + }, [value, labelList, language]) + + const selectLabel = (label: Label) => { + if (value.includes(label.name)) + onChange(value.filter(v => v !== label.name)) + else + onChange([...value, label.name]) + } + + useMount(() => { + fetchLabelList().then((res) => { + setLabelList(res) + }) + }) + + return ( + +
+ setOpen(v => !v)} + className='block' + > +
+
0 ? selectedLabels : ''} className={cn('grow text-[13px] leading-[18px] text-gray-700 truncate', !value.length && '!text-gray-400')}> + {!value.length && t('tools.createTool.toolInput.labelPlaceholder')} + {!!value.length && selectedLabels} +
+
+ +
+
+
+ +
+
+ +
+
+ {filteredLabelList.map(label => ( +
selectLabel(label)} + > + {}} + /> +
{label.label[language]}
+
+ ))} + {!filteredLabelList.length && ( +
+ +
{t('common.tag.noTag')}
+
+ )} +
+
+
+
+
+ ) +} + +export default LabelSelector diff --git a/web/app/components/tools/labels/store.ts b/web/app/components/tools/labels/store.ts new file mode 100644 index 0000000000..c19991dfd4 --- /dev/null +++ b/web/app/components/tools/labels/store.ts @@ -0,0 +1,15 @@ +import { create } from 'zustand' +import type { Label } from './constant' + +type State = { + labelList: Label[] +} + +type Action = { + setLabelList: (labelList?: Label[]) => void +} + +export const useStore = create(set => ({ + labelList: [], + setLabelList: labelList => set(() => ({ labelList })), +})) diff --git a/web/app/components/tools/no-custom-tool-placeholder.tsx b/web/app/components/tools/no-custom-tool-placeholder.tsx deleted file mode 100644 index 7fda72140c..0000000000 --- a/web/app/components/tools/no-custom-tool-placeholder.tsx +++ /dev/null @@ -1,26 +0,0 @@ -'use client' -import type { FC } from 'react' -import React from 'react' -import { useTranslation } from 'react-i18next' -import { BookOpen01 } from '../base/icons/src/vender/line/education' -import { Icon3Dots } from '../base/icons/src/public/other' - -const NoCustomToolPlaceHolder: FC = () => { - const { t } = useTranslation() - - return ( -
-
-
- -
-
- {t('tools.noCustomTool.title')} - -
-
{t('tools.noCustomTool.content')}
-
-
- ) -} -export default React.memo(NoCustomToolPlaceHolder) diff --git a/web/app/components/tools/provider-list.tsx b/web/app/components/tools/provider-list.tsx new file mode 100644 index 0000000000..a888edadf2 --- /dev/null +++ b/web/app/components/tools/provider-list.tsx @@ -0,0 +1,117 @@ +'use client' +import { useEffect, useMemo, useState } from 'react' +import cn from 'classnames' +import { useTranslation } from 'react-i18next' +import type { Collection } from './types' +import { useTabSearchParams } from '@/hooks/use-tab-searchparams' +import TabSliderNew from '@/app/components/base/tab-slider-new' +import LabelFilter from '@/app/components/tools/labels/filter' +import SearchInput from '@/app/components/base/search-input' +import { DotsGrid, XClose } from '@/app/components/base/icons/src/vender/line/general' +import { Colors } from '@/app/components/base/icons/src/vender/line/others' +import { Route } from '@/app/components/base/icons/src/vender/line/mapsAndTravel' +import CustomCreateCard from '@/app/components/tools/provider/custom-create-card' +import ContributeCard from '@/app/components/tools/provider/contribute' +import ProviderCard from '@/app/components/tools/provider/card' +import ProviderDetail from '@/app/components/tools/provider/detail' +import Empty from '@/app/components/tools/add-tool-modal/empty' +import { fetchCollectionList } from '@/service/tools' + +const ProviderList = () => { + const { t } = useTranslation() + + const [activeTab, setActiveTab] = useTabSearchParams({ + defaultTab: 'builtin', + }) + const options = [ + { value: 'builtin', text: t('tools.type.builtIn'), icon: }, + { value: 'api', text: t('tools.type.custom'), icon: }, + { value: 'workflow', text: t('tools.type.workflow'), icon: }, + ] + const [tagFilterValue, setTagFilterValue] = useState([]) + const handleTagsChange = (value: string[]) => { + setTagFilterValue(value) + } + const [keywords, setKeywords] = useState('') + const handleKeywordsChange = (value: string) => { + setKeywords(value) + } + + const [collectionList, setCollectionList] = useState([]) + const filteredCollectionList = useMemo(() => { + return collectionList.filter((collection) => { + if (collection.type !== activeTab) + return false + if (tagFilterValue.length > 0 && (!collection.labels || collection.labels.every(label => !tagFilterValue.includes(label)))) + return false + if (keywords) + return collection.name.toLowerCase().includes(keywords.toLowerCase()) + return true + }) + }, [activeTab, tagFilterValue, keywords, collectionList]) + const getProviderList = async () => { + const list = await fetchCollectionList() + setCollectionList([...list]) + } + useEffect(() => { + getProviderList() + }, []) + + const [currentProvider, setCurrentProvider] = useState() + useEffect(() => { + if (currentProvider && collectionList.length > 0) { + const newCurrentProvider = collectionList.find(collection => collection.id === currentProvider.id) + setCurrentProvider(newCurrentProvider) + } + }, [collectionList, currentProvider]) + + return ( +
+
+
+ { + setActiveTab(state) + if (state !== activeTab) + setCurrentProvider(undefined) + }} + options={options} + /> +
+ + +
+
+
+ {activeTab === 'builtin' && } + {activeTab === 'api' && } + {filteredCollectionList.map(collection => ( + setCurrentProvider(collection)} + key={collection.id} + collection={collection} + /> + ))} + {!filteredCollectionList.length &&
} +
+
+
+ {currentProvider && } +
+
setCurrentProvider(undefined)}>
+
+ ) +} +ProviderList.displayName = 'ToolProviderList' +export default ProviderList diff --git a/web/app/components/tools/provider/card.tsx b/web/app/components/tools/provider/card.tsx new file mode 100644 index 0000000000..13009cf654 --- /dev/null +++ b/web/app/components/tools/provider/card.tsx @@ -0,0 +1,83 @@ +'use client' +import { useMemo } from 'react' +import cn from 'classnames' +import { useContext } from 'use-context-selector' +import { useTranslation } from 'react-i18next' +import type { Collection } from '../types' +import AppIcon from '@/app/components/base/app-icon' +import { Tag01 } from '@/app/components/base/icons/src/vender/line/financeAndECommerce' +import I18n from '@/context/i18n' +import { getLanguage } from '@/i18n/language' +import { useStore as useLabelStore } from '@/app/components/tools/labels/store' + +type Props = { + active: boolean + collection: Collection + onSelect: () => void +} + +const ProviderCard = ({ + active, + collection, + onSelect, +}: Props) => { + const { t } = useTranslation() + const { locale } = useContext(I18n) + const language = getLanguage(locale) + const labelList = useLabelStore(s => s.labelList) + + const labelContent = useMemo(() => { + if (!collection.labels) + return '' + return collection.labels.map((name) => { + const label = labelList.find(item => item.name === name) + return label?.label[language] + }).filter(Boolean).join(', ') + }, [collection.labels, labelList, language]) + + return ( +
+
+
+ {typeof collection.icon === 'string' && ( +
+ )} + {typeof collection.icon !== 'string' && ( + + )} +
+
+
+
{collection.label[language]}
+
+
+
{t('tools.author')} {collection.author}
+
+
+
+
0 && 'group-hover:line-clamp-2 group-hover:max-h-[36px]', + )} + title={collection.description[language]} + > + {collection.description[language]} +
+ {collection.labels?.length > 0 && ( +
+
+ +
{labelContent}
+
+
+ )} +
+ ) +} +export default ProviderCard diff --git a/web/app/components/tools/provider/contribute.tsx b/web/app/components/tools/provider/contribute.tsx new file mode 100644 index 0000000000..9c3c2559b0 --- /dev/null +++ b/web/app/components/tools/provider/contribute.tsx @@ -0,0 +1,38 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { useTranslation } from 'react-i18next' +import { ToolsActive } from '@/app/components/base/icons/src/public/header-nav/tools' +import { Heart02 } from '@/app/components/base/icons/src/vender/solid/education' +import { BookOpen01 } from '@/app/components/base/icons/src/vender/line/education' +import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows' + +const Contribute: FC = () => { + const { t } = useTranslation() + + return ( + +
+
+
+
+
+
+
+
{t('tools.contribute.line1')}
+
{t('tools.contribute.line2')}
+
+
+ +
{t('tools.contribute.viewGuide')}
+ +
+
+ ) +} +export default React.memo(Contribute) diff --git a/web/app/components/tools/provider/custom-create-card.tsx b/web/app/components/tools/provider/custom-create-card.tsx new file mode 100644 index 0000000000..f85cd21b1d --- /dev/null +++ b/web/app/components/tools/provider/custom-create-card.tsx @@ -0,0 +1,70 @@ +'use client' +import { useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useContext } from 'use-context-selector' +import type { CustomCollectionBackend } from '../types' +import I18n from '@/context/i18n' +import { getLanguage } from '@/i18n/language' +import { Plus } from '@/app/components/base/icons/src/vender/line/general' +import { BookOpen01 } from '@/app/components/base/icons/src/vender/line/education' +import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows' +import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal' +import { createCustomCollection } from '@/service/tools' +import Toast from '@/app/components/base/toast' + +type Props = { + onRefreshData: () => void +} + +const Contribute = ({ onRefreshData }: Props) => { + const { t } = useTranslation() + const { locale } = useContext(I18n) + const language = getLanguage(locale) + + const linkUrl = useMemo(() => { + if (language.startsWith('zh_')) + return 'https://docs.dify.ai/v/zh-hans/guides/gong-ju/quick-tool-integration' + return 'https://docs.dify.ai/tutorials/quick-tool-integration' + }, [language]) + + const [isShowEditCollectionToolModal, setIsShowEditCustomCollectionModal] = useState(false) + const doCreateCustomToolCollection = async (data: CustomCollectionBackend) => { + await createCustomCollection(data) + Toast.notify({ + type: 'success', + message: t('common.api.actionSuccess'), + }) + setIsShowEditCustomCollectionModal(false) + onRefreshData() + } + + return ( + <> +
+
setIsShowEditCustomCollectionModal(true)}> +
+
+ +
+
{t('tools.createCustomTool')}
+
+
+ +
+ {isShowEditCollectionToolModal && ( + setIsShowEditCustomCollectionModal(false)} + onAdd={doCreateCustomToolCollection} + /> + )} + + ) +} +export default Contribute diff --git a/web/app/components/tools/provider/detail.tsx b/web/app/components/tools/provider/detail.tsx new file mode 100644 index 0000000000..7eb72d328e --- /dev/null +++ b/web/app/components/tools/provider/detail.tsx @@ -0,0 +1,343 @@ +'use client' +import React, { useCallback, useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useContext } from 'use-context-selector' +import cn from 'classnames' +import { AuthHeaderPrefix, AuthType, CollectionType } from '../types' +import type { Collection, CustomCollectionBackend, Tool, WorkflowToolProviderRequest, WorkflowToolProviderResponse } from '../types' +import ToolItem from './tool-item' +import I18n from '@/context/i18n' +import { getLanguage } from '@/i18n/language' +import AppIcon from '@/app/components/base/app-icon' +import Button from '@/app/components/base/button' +import Indicator from '@/app/components/header/indicator' +import { LinkExternal02, Settings01 } from '@/app/components/base/icons/src/vender/line/general' +import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials' +import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal' +import WorkflowToolModal from '@/app/components/tools/workflow-tool' +import Toast from '@/app/components/base/toast' +import { + deleteWorkflowTool, + fetchBuiltInToolList, + fetchCustomCollection, + fetchCustomToolList, + fetchModelToolList, + fetchWorkflowToolDetail, + removeBuiltInToolCredential, + removeCustomCollection, + saveWorkflowToolProvider, + updateBuiltInToolCredential, + updateCustomCollection, +} from '@/service/tools' +import { useModalContext } from '@/context/modal-context' +import { useProviderContext } from '@/context/provider-context' +import { ConfigurateMethodEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import Loading from '@/app/components/base/loading' + +type Props = { + collection: Collection + onRefreshData: () => void +} + +const ProviderDetail = ({ + collection, + onRefreshData, +}: Props) => { + const { t } = useTranslation() + const { locale } = useContext(I18n) + const language = getLanguage(locale) + + const needAuth = collection.allow_delete || collection.type === CollectionType.model + const isAuthed = collection.is_team_authorization + const isBuiltIn = collection.type === CollectionType.builtIn + const isModel = collection.type === CollectionType.model + + const [isDetailLoading, setIsDetailLoading] = useState(false) + + // built in provider + const [showSettingAuth, setShowSettingAuth] = useState(false) + const { setShowModelModal } = useModalContext() + const { modelProviders: providers } = useProviderContext() + const showSettingAuthModal = () => { + if (isModel) { + const provider = providers.find(item => item.provider === collection?.id) + if (provider) { + setShowModelModal({ + payload: { + currentProvider: provider, + currentConfigurateMethod: ConfigurateMethodEnum.predefinedModel, + currentCustomConfigrationModelFixedFields: undefined, + }, + onSaveCallback: () => { + onRefreshData() + }, + }) + } + } + else { + setShowSettingAuth(true) + } + } + // custom provider + const [customCollection, setCustomCollection] = useState(null) + const [isShowEditCollectionToolModal, setIsShowEditCustomCollectionModal] = useState(false) + const doUpdateCustomToolCollection = async (data: CustomCollectionBackend) => { + await updateCustomCollection(data) + onRefreshData() + Toast.notify({ + type: 'success', + message: t('common.api.actionSuccess'), + }) + setIsShowEditCustomCollectionModal(false) + } + const doRemoveCustomToolCollection = async () => { + await removeCustomCollection(collection?.name as string) + onRefreshData() + Toast.notify({ + type: 'success', + message: t('common.api.actionSuccess'), + }) + setIsShowEditCustomCollectionModal(false) + } + const getCustomProvider = useCallback(async () => { + setIsDetailLoading(true) + const res = await fetchCustomCollection(collection.name) + if (res.credentials.auth_type === AuthType.apiKey && !res.credentials.api_key_header_prefix) { + if (res.credentials.api_key_value) + res.credentials.api_key_header_prefix = AuthHeaderPrefix.custom + } + setCustomCollection({ + ...res, + labels: collection.labels, + provider: collection.name, + }) + setIsDetailLoading(false) + }, [collection.name]) + // workflow provider + const [isShowEditWorkflowToolModal, setIsShowEditWorkflowToolModal] = useState(false) + const getWorkflowToolProvider = useCallback(async () => { + setIsDetailLoading(true) + const res = await fetchWorkflowToolDetail(collection.id) + const payload = { + ...res, + parameters: res.tool?.parameters.map((item) => { + return { + name: item.name, + description: item.llm_description, + form: item.form, + required: item.required, + type: item.type, + } + }) || [], + labels: res.tool?.labels || [], + } + setCustomCollection(payload) + setIsDetailLoading(false) + }, [collection.id]) + const removeWorkflowToolProvider = async () => { + await deleteWorkflowTool(collection.id) + onRefreshData() + Toast.notify({ + type: 'success', + message: t('common.api.actionSuccess'), + }) + setIsShowEditWorkflowToolModal(false) + } + const updateWorkflowToolProvider = async (data: WorkflowToolProviderRequest & Partial<{ + workflow_app_id: string + workflow_tool_id: string + }>) => { + await saveWorkflowToolProvider(data) + onRefreshData() + getWorkflowToolProvider() + Toast.notify({ + type: 'success', + message: t('common.api.actionSuccess'), + }) + setIsShowEditWorkflowToolModal(false) + } + + // ToolList + const [toolList, setToolList] = useState([]) + const getProviderToolList = useCallback(async () => { + setIsDetailLoading(true) + try { + if (collection.type === CollectionType.builtIn) { + const list = await fetchBuiltInToolList(collection.name) + setToolList(list) + } + else if (collection.type === CollectionType.model) { + const list = await fetchModelToolList(collection.name) + setToolList(list) + } + else if (collection.type === CollectionType.workflow) { + setToolList([]) + } + else { + const list = await fetchCustomToolList(collection.name) + setToolList(list) + } + } + catch (e) { } + setIsDetailLoading(false) + }, [collection.name, collection.type]) + + useEffect(() => { + if (collection.type === CollectionType.custom) + getCustomProvider() + if (collection.type === CollectionType.workflow) + getWorkflowToolProvider() + getProviderToolList() + }, [collection.name, collection.type, getCustomProvider, getProviderToolList, getWorkflowToolProvider]) + + return ( +
+
+
+ {typeof collection.icon === 'string' && ( +
+ )} + {typeof collection.icon !== 'string' && ( + + )} +
+
+
+
{collection.label[language]}
+
+
+
+
{collection.description[language]}
+
+ {(collection.type === CollectionType.builtIn) && needAuth && ( + + )} + {collection.type === CollectionType.custom && !isDetailLoading && ( + + )} + {collection.type === CollectionType.workflow && !isDetailLoading && customCollection && ( + <> + + + + )} +
+ {/* Tools */} +
+ {isDetailLoading &&
} + {!isDetailLoading && ( +
+ {collection.type === CollectionType.workflow && {t('tools.createTool.toolInput.title').toLocaleUpperCase()}} + {collection.type !== CollectionType.workflow && {t('tools.includeToolNum', { num: toolList.length }).toLocaleUpperCase()}} + {needAuth && (isBuiltIn || isModel) && !isAuthed && ( + <> + · + {t('tools.auth.setup').toLocaleUpperCase()} + + )} +
+ )} + {!isDetailLoading && ( +
+ {collection.type !== CollectionType.workflow && toolList.map(tool => ( + + ))} + {collection.type === CollectionType.workflow && (customCollection as WorkflowToolProviderResponse)?.tool?.parameters.map(item => ( +
+
+ {item.name} + {item.type} + {item.required ? t('tools.createTool.toolInput.required') : ''} +
+
{item.llm_description}
+
+ ))} +
+ )} +
+ {showSettingAuth && ( + setShowSettingAuth(false)} + onSaved={async (value) => { + await updateBuiltInToolCredential(collection.name, value) + Toast.notify({ + type: 'success', + message: t('common.api.actionSuccess'), + }) + await onRefreshData() + setShowSettingAuth(false) + }} + onRemove={async () => { + await removeBuiltInToolCredential(collection.name) + Toast.notify({ + type: 'success', + message: t('common.api.actionSuccess'), + }) + await onRefreshData() + setShowSettingAuth(false) + }} + /> + )} + {isShowEditCollectionToolModal && ( + setIsShowEditCustomCollectionModal(false)} + onEdit={doUpdateCustomToolCollection} + onRemove={doRemoveCustomToolCollection} + /> + )} + {isShowEditWorkflowToolModal && ( + setIsShowEditWorkflowToolModal(false)} + onRemove={removeWorkflowToolProvider} + onSave={updateWorkflowToolProvider} + /> + )} +
+ ) +} +export default ProviderDetail diff --git a/web/app/components/tools/provider/grid_bg.svg b/web/app/components/tools/provider/grid_bg.svg new file mode 100644 index 0000000000..8b0f531959 --- /dev/null +++ b/web/app/components/tools/provider/grid_bg.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/web/app/components/tools/provider/tool-item.tsx b/web/app/components/tools/provider/tool-item.tsx new file mode 100644 index 0000000000..f373186a0a --- /dev/null +++ b/web/app/components/tools/provider/tool-item.tsx @@ -0,0 +1,53 @@ +'use client' +import React, { useState } from 'react' +import cn from 'classnames' +import { useContext } from 'use-context-selector' +import type { Collection, Tool } from '../types' +import I18n from '@/context/i18n' +import { getLanguage } from '@/i18n/language' +import SettingBuiltInTool from '@/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool' + +type Props = { + disabled?: boolean + collection: Collection + tool: Tool + isBuiltIn: boolean + isModel: boolean +} + +const ToolItem = ({ + disabled, + collection, + tool, + isBuiltIn, + isModel, +}: Props) => { + const { locale } = useContext(I18n) + const language = getLanguage(locale) + const [showDetail, setShowDetail] = useState(false) + + return ( + <> +
!disabled && setShowDetail(true)} + > +
{tool.label[language]}
+
{tool.description[language]}
+
+ {showDetail && ( + { + setShowDetail(false) + }} + isBuiltIn={isBuiltIn} + isModel={isModel} + /> + )} + + ) +} +export default ToolItem diff --git a/web/app/components/tools/search.tsx b/web/app/components/tools/search.tsx deleted file mode 100644 index 344d84069e..0000000000 --- a/web/app/components/tools/search.tsx +++ /dev/null @@ -1,41 +0,0 @@ -'use client' -import type { FC } from 'react' -import React from 'react' -import cn from 'classnames' -import { - MagnifyingGlassIcon, -} from '@heroicons/react/24/solid' -import { useTranslation } from 'react-i18next' - -type Props = { - className?: string - value: string - onChange: (v: string) => void -} - -const Search: FC = ({ - className, - value, - onChange, -}) => { - const { t } = useTranslation() - - return ( -
-
-
- { - onChange(e.target.value) - }} - /> -
- ) -} -export default React.memo(Search) diff --git a/web/app/components/tools/setting/build-in/config-credentials.tsx b/web/app/components/tools/setting/build-in/config-credentials.tsx index b663f96afe..fcfca8f239 100644 --- a/web/app/components/tools/setting/build-in/config-credentials.tsx +++ b/web/app/components/tools/setting/build-in/config-credentials.tsx @@ -48,8 +48,8 @@ const ConfigCredential: FC = ({ onHide={onCancel} title={t('tools.auth.setupModalTitle') as string} titleDescription={t('tools.auth.setupModalTitleDescription') as string} - panelClassName='mt-2 !w-[480px]' - maxWidthClassName='!max-w-[480px]' + panelClassName='mt-2 !w-[405px]' + maxWidthClassName='!max-w-[405px]' height='calc(100vh - 16px)' contentClassName='!bg-gray-100' headerClassName='!border-b-black/5' @@ -88,7 +88,7 @@ const ConfigCredential: FC = ({ ) } < div className='flex space-x-2'> - +
diff --git a/web/app/components/tools/tool-list/header.tsx b/web/app/components/tools/tool-list/header.tsx deleted file mode 100644 index bf564f320f..0000000000 --- a/web/app/components/tools/tool-list/header.tsx +++ /dev/null @@ -1,77 +0,0 @@ -'use client' -import type { FC } from 'react' -import React from 'react' -import { useContext } from 'use-context-selector' -import cn from 'classnames' -import { useTranslation } from 'react-i18next' -import type { Collection } from '../types' -import { CollectionType, LOC } from '../types' -import { Settings01 } from '../../base/icons/src/vender/line/general' -import I18n from '@/context/i18n' -import { getLanguage } from '@/i18n/language' -type Props = { - icon: JSX.Element - collection: Collection - loc: LOC - onShowAuth: () => void - onShowEditCustomCollection: () => void -} - -const Header: FC = ({ - icon, - collection, - loc, - onShowAuth, - onShowEditCustomCollection, -}) => { - const { locale } = useContext(I18n) - const language = getLanguage(locale) - const { t } = useTranslation() - const isInToolsPage = loc === LOC.tools - const isInDebugPage = !isInToolsPage - - const needAuth = collection?.allow_delete || collection?.type === CollectionType.model - const isAuthed = collection.is_team_authorization - return ( -
-
- {icon} -
-
-
{collection.label[language]}
-
·
-
{t('tools.author')} {collection.author}
-
- {collection.description && ( -
- {collection.description[language]} -
- )} -
-
- {(collection.type === CollectionType.builtIn || collection.type === CollectionType.model) && needAuth && ( -
{ - if (collection.type === CollectionType.builtIn || collection.type === CollectionType.model) - onShowAuth() - }} - > -
-
{t(`tools.auth.${isAuthed ? 'authorized' : 'unauthorized'}`)}
-
- )} - - {collection.type === CollectionType.custom && ( -
onShowEditCustomCollection()} - > - -
{t('tools.createTool.editAction')}
-
- )} -
- ) -} -export default React.memo(Header) diff --git a/web/app/components/tools/tool-list/index.tsx b/web/app/components/tools/tool-list/index.tsx deleted file mode 100644 index 9228a028a5..0000000000 --- a/web/app/components/tools/tool-list/index.tsx +++ /dev/null @@ -1,220 +0,0 @@ -'use client' -import type { FC } from 'react' -import React, { useEffect, useState } from 'react' -import { useTranslation } from 'react-i18next' -import cn from 'classnames' -import { AuthHeaderPrefix, AuthType, CollectionType, LOC } from '../types' -import type { Collection, CustomCollectionBackend, Tool } from '../types' -import Loading from '../../base/loading' -import { ArrowNarrowRight } from '../../base/icons/src/vender/line/arrows' -import Toast from '../../base/toast' -import { ConfigurateMethodEnum } from '../../header/account-setting/model-provider-page/declarations' -import Header from './header' -import Item from './item' -import AppIcon from '@/app/components/base/app-icon' -import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials' -import { fetchCustomCollection, removeBuiltInToolCredential, removeCustomCollection, updateBuiltInToolCredential, updateCustomCollection } from '@/service/tools' -import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal' -import type { AgentTool } from '@/types/app' -import { MAX_TOOLS_NUM } from '@/config' -import { useModalContext } from '@/context/modal-context' -import { useProviderContext } from '@/context/provider-context' - -type Props = { - collection: Collection | null - list: Tool[] - // onToolListChange: () => void // custom tools change - loc: LOC - addedTools?: AgentTool[] - onAddTool?: (collection: Collection, payload: Tool) => void - onRefreshData: () => void - onCollectionRemoved: () => void - isLoading: boolean -} - -const ToolList: FC = ({ - collection, - list, - loc, - addedTools, - onAddTool, - onRefreshData, - onCollectionRemoved, - isLoading, -}) => { - const { t } = useTranslation() - const isInToolsPage = loc === LOC.tools - const isBuiltIn = collection?.type === CollectionType.builtIn - const isModel = collection?.type === CollectionType.model - const needAuth = collection?.allow_delete - - const { setShowModelModal } = useModalContext() - const [showSettingAuth, setShowSettingAuth] = useState(false) - const { modelProviders: providers } = useProviderContext() - const showSettingAuthModal = () => { - if (isModel) { - const provider = providers.find(item => item.provider === collection?.id) - if (provider) { - setShowModelModal({ - payload: { - currentProvider: provider, - currentConfigurateMethod: ConfigurateMethodEnum.predefinedModel, - currentCustomConfigrationModelFixedFields: undefined, - }, - onSaveCallback: () => { - onRefreshData() - }, - }) - } - } - else { - setShowSettingAuth(true) - } - } - - const [customCollection, setCustomCollection] = useState(null) - useEffect(() => { - if (!collection) - return - (async () => { - if (collection.type === CollectionType.custom) { - const res = await fetchCustomCollection(collection.name) - if (res.credentials.auth_type === AuthType.apiKey && !res.credentials.api_key_header_prefix) { - if (res.credentials.api_key_value) - res.credentials.api_key_header_prefix = AuthHeaderPrefix.custom - } - setCustomCollection({ - ...res, - provider: collection.name, - }) - } - })() - }, [collection]) - const [isShowEditCollectionToolModal, setIsShowEditCustomCollectionModal] = useState(false) - - const doUpdateCustomToolCollection = async (data: CustomCollectionBackend) => { - await updateCustomCollection(data) - onRefreshData() - Toast.notify({ - type: 'success', - message: t('common.api.actionSuccess'), - }) - setIsShowEditCustomCollectionModal(false) - } - - const doRemoveCustomToolCollection = async () => { - await removeCustomCollection(collection?.name as string) - onCollectionRemoved() - Toast.notify({ - type: 'success', - message: t('common.api.actionSuccess'), - }) - setIsShowEditCustomCollectionModal(false) - } - - if (!collection || isLoading) - return - - const icon = <>{typeof collection.icon === 'string' - ? ( -
-
-
- ) - : ( - - )} - - - return ( -
-
showSettingAuthModal()} - onShowEditCustomCollection={() => setIsShowEditCustomCollectionModal(true)} - /> -
-
-
{t('tools.includeToolNum', { - num: list.length, - })}
- {needAuth && (isBuiltIn || isModel) && !collection.is_team_authorization && ( - <> -
·
-
showSettingAuthModal()} - > -
{t('tools.auth.setup')}
- -
- - )} -
-
-
- {/* list */} -
- {list.map(item => ( - = MAX_TOOLS_NUM} - added={!!addedTools?.find(v => v.provider_id === collection.id && v.provider_type === collection.type && v.tool_name === item.name)} - onAdd={!isInToolsPage ? tool => onAddTool?.(collection as Collection, tool) : undefined} - /> - ))} -
-
- {showSettingAuth && ( - setShowSettingAuth(false)} - onSaved={async (value) => { - await updateBuiltInToolCredential(collection.name, value) - Toast.notify({ - type: 'success', - message: t('common.api.actionSuccess'), - }) - await onRefreshData() - setShowSettingAuth(false) - }} - onRemove={async () => { - await removeBuiltInToolCredential(collection.name) - Toast.notify({ - type: 'success', - message: t('common.api.actionSuccess'), - }) - await onRefreshData() - setShowSettingAuth(false) - }} - /> - )} - - {isShowEditCollectionToolModal && ( - setIsShowEditCustomCollectionModal(false)} - onEdit={doUpdateCustomToolCollection} - onRemove={doRemoveCustomToolCollection} - /> - )} -
- ) -} -export default React.memo(ToolList) diff --git a/web/app/components/tools/tool-list/item.tsx b/web/app/components/tools/tool-list/item.tsx deleted file mode 100644 index e6e07cd5c7..0000000000 --- a/web/app/components/tools/tool-list/item.tsx +++ /dev/null @@ -1,84 +0,0 @@ -'use client' -import type { FC } from 'react' -import React, { useState } from 'react' -import { useContext } from 'use-context-selector' -import cn from 'classnames' -import { useTranslation } from 'react-i18next' -import type { Collection, Tool } from '../types' -import Button from '../../base/button' -import { CollectionType } from '../types' -import TooltipPlus from '../../base/tooltip-plus' -import I18n from '@/context/i18n' -import SettingBuiltInTool from '@/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool' -import { getLanguage } from '@/i18n/language' -type Props = { - collection: Collection - icon: JSX.Element - payload: Tool - isInToolsPage: boolean - isToolNumMax: boolean - added?: boolean - onAdd?: (payload: Tool) => void -} - -const Item: FC = ({ - collection, - icon, - payload, - isInToolsPage, - isToolNumMax, - added, - onAdd, -}) => { - const { t } = useTranslation() - const { locale } = useContext(I18n) - const language = getLanguage(locale) - - const isBuiltIn = collection.type === CollectionType.builtIn - const isModel = collection.type === CollectionType.model - const canShowDetail = isInToolsPage - const [showDetail, setShowDetail] = useState(false) - const addBtn = - - return ( - <> -
canShowDetail && setShowDetail(true)} - > -
- {icon} -
-
{payload.label[language]}
-
- {payload.description[language]} -
-
-
-
- {!isToolNumMax && onAdd && ( - !collection.is_team_authorization - ? - {addBtn} - - : addBtn - )} -
-
- {showDetail && ( - { - setShowDetail(false) - }} - isBuiltIn={isBuiltIn} - isModel={isModel} - /> - )} - - - ) -} -export default React.memo(Item) diff --git a/web/app/components/tools/tool-nav-list/index.tsx b/web/app/components/tools/tool-nav-list/index.tsx deleted file mode 100644 index 3a8fd4088b..0000000000 --- a/web/app/components/tools/tool-nav-list/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -'use client' -import type { FC } from 'react' -import React from 'react' -import cn from 'classnames' -import Item from './item' -import type { Collection } from '@/app/components/tools/types' -type Props = { - className?: string - currentIndex: number - list: Collection[] - onChosen: (index: number) => void -} - -const ToolNavList: FC = ({ - className, - currentIndex, - list, - onChosen, -}) => { - return ( -
- {list.map((item, index) => ( - onChosen(index)}> - ))} -
- ) -} -export default React.memo(ToolNavList) diff --git a/web/app/components/tools/tool-nav-list/item.tsx b/web/app/components/tools/tool-nav-list/item.tsx deleted file mode 100644 index 2d3606d13e..0000000000 --- a/web/app/components/tools/tool-nav-list/item.tsx +++ /dev/null @@ -1,50 +0,0 @@ -'use client' -import type { FC } from 'react' -import React from 'react' -import { useContext } from 'use-context-selector' -import cn from 'classnames' -import AppIcon from '../../base/app-icon' -import type { Collection } from '@/app/components/tools/types' -import I18n from '@/context/i18n' -import { getLanguage } from '@/i18n/language' - -type Props = { - isCurrent: boolean - payload: Collection - onClick: () => void -} - -const Item: FC = ({ - isCurrent, - payload, - onClick, -}) => { - const { locale } = useContext(I18n) - const language = getLanguage(locale) - return ( -
!isCurrent && onClick()} - > - {typeof payload.icon === 'string' - ? ( -
- ) - : ( - - )} -
{payload.label[language]}
- -
- ) -} -export default React.memo(Item) diff --git a/web/app/components/tools/types.ts b/web/app/components/tools/types.ts index b84afd795b..f2784e9dfe 100644 --- a/web/app/components/tools/types.ts +++ b/web/app/components/tools/types.ts @@ -27,6 +27,7 @@ export enum CollectionType { builtIn = 'builtin', custom = 'api', model = 'model', + workflow = 'workflow', } export type Emoji = { @@ -45,6 +46,7 @@ export type Collection = { team_credentials: Record is_team_authorization: boolean allow_delete: boolean + labels: string[] } export type ToolParameter = { @@ -52,19 +54,25 @@ export type ToolParameter = { label: TypeWithI18N human_description: TypeWithI18N type: string + form: string + llm_description: string required: boolean default: string options?: { label: TypeWithI18N value: string }[] + min?: number + max?: number } export type Tool = { name: string + author: string label: TypeWithI18N description: any parameters: ToolParameter[] + labels: string[] } export type ToolCredential = { @@ -91,13 +99,17 @@ export type CustomCollectionBackend = { privacy_policy: string custom_disclaimer: string tools?: ParamItem[] + id: string + labels: string[] } export type ParamItem = { name: string label: TypeWithI18N human_description: TypeWithI18N + llm_description: string type: string + form: string required: boolean default: string min?: number @@ -115,3 +127,39 @@ export type CustomParamSchema = { method: string parameters: ParamItem[] } + +export type WorkflowToolProviderParameter = { + name: string + form: string + description: string + required?: boolean + type?: string +} + +export type WorkflowToolProviderRequest = { + name: string + icon: Emoji + description: string + parameters: WorkflowToolProviderParameter[] + labels: string[] + privacy_policy: string +} + +export type WorkflowToolProviderResponse = { + workflow_app_id: string + workflow_tool_id: string + label: string + name: string + icon: Emoji + description: string + synced: boolean + tool: { + author: string + name: string + label: TypeWithI18N + description: TypeWithI18N + labels: string[] + parameters: ParamItem[] + } + privacy_policy: string +} diff --git a/web/app/components/tools/workflow-tool/configure-button.tsx b/web/app/components/tools/workflow-tool/configure-button.tsx new file mode 100644 index 0000000000..38e3b869df --- /dev/null +++ b/web/app/components/tools/workflow-tool/configure-button.tsx @@ -0,0 +1,225 @@ +'use client' +import React, { useCallback, useEffect, useMemo, useState } from 'react' +import cn from 'classnames' +import { useTranslation } from 'react-i18next' +import { useRouter } from 'next/navigation' +import Button from '@/app/components/base/button' +import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows' +import { Tools } from '@/app/components/base/icons/src/vender/line/others' +import Indicator from '@/app/components/header/indicator' +import WorkflowToolModal from '@/app/components/tools/workflow-tool' +import Loading from '@/app/components/base/loading' +import Toast from '@/app/components/base/toast' +import { createWorkflowToolProvider, fetchWorkflowToolDetailByAppID, saveWorkflowToolProvider } from '@/service/tools' +import type { Emoji, WorkflowToolProviderParameter, WorkflowToolProviderRequest, WorkflowToolProviderResponse } from '@/app/components/tools/types' +import type { InputVar } from '@/app/components/workflow/types' + +type Props = { + disabled: boolean + published: boolean + detailNeedUpdate: boolean + workflowAppId: string + icon: Emoji + name: string + description: string + inputs?: InputVar[] + handlePublish: () => void + onRefreshData?: () => void +} + +const WorkflowToolConfigureButton = ({ + disabled, + published, + detailNeedUpdate, + workflowAppId, + icon, + name, + description, + inputs, + handlePublish, + onRefreshData, +}: Props) => { + const { t } = useTranslation() + const router = useRouter() + const [showModal, setShowModal] = useState(false) + const [isLoading, setIsLoading] = useState(false) + const [detail, setDetail] = useState() + + const outdated = useMemo(() => { + if (!detail) + return false + if (detail.tool.parameters.length !== inputs?.length) { + return true + } + else { + for (const item of inputs || []) { + const param = detail.tool.parameters.find(toolParam => toolParam.name === item.variable) + if (!param) { + return true + } + else if (param.required !== item.required) { + return true + } + else { + if (item.type === 'paragraph' && param.type !== 'string') + return true + if (param.type !== item.type && !(param.type === 'string' && item.type === 'paragraph')) + return true + } + } + } + return false + }, [detail, inputs]) + + const payload = useMemo(() => { + let parameters: WorkflowToolProviderParameter[] = [] + if (!published) { + parameters = (inputs || []).map((item) => { + return { + name: item.variable, + description: '', + form: 'llm', + required: item.required, + type: item.type, + } + }) + } + else if (detail && detail.tool) { + parameters = (inputs || []).map((item) => { + return { + name: item.variable, + required: item.required, + type: item.type === 'paragraph' ? 'string' : item.type, + description: detail.tool.parameters.find(param => param.name === item.variable)?.llm_description || '', + form: detail.tool.parameters.find(param => param.name === item.variable)?.form || 'llm', + } + }) + } + return { + icon: detail?.icon || icon, + label: detail?.label || name, + name: detail?.name || '', + description: detail?.description || description, + parameters, + labels: detail?.tool?.labels || [], + privacy_policy: detail?.privacy_policy || '', + ...(published + ? { + workflow_tool_id: detail?.workflow_tool_id, + } + : { + workflow_app_id: workflowAppId, + }), + } + }, [detail, published, workflowAppId, icon, name, description, inputs]) + + const getDetail = useCallback(async (workflowAppId: string) => { + setIsLoading(true) + const res = await fetchWorkflowToolDetailByAppID(workflowAppId) + setDetail(res) + setIsLoading(false) + }, []) + + useEffect(() => { + if (published) + getDetail(workflowAppId) + }, [getDetail, published, workflowAppId]) + + useEffect(() => { + if (detailNeedUpdate) + getDetail(workflowAppId) + }, [detailNeedUpdate, getDetail, workflowAppId]) + + const createHandle = async (data: WorkflowToolProviderRequest & { workflow_app_id: string }) => { + try { + await createWorkflowToolProvider(data) + onRefreshData?.() + getDetail(workflowAppId) + Toast.notify({ + type: 'success', + message: t('common.api.actionSuccess'), + }) + setShowModal(false) + } + catch (e) { + Toast.notify({ type: 'error', message: (e as Error).message }) + } + } + + const updateWorkflowToolProvider = async (data: WorkflowToolProviderRequest & Partial<{ + workflow_app_id: string + workflow_tool_id: string + }>) => { + try { + await handlePublish() + await saveWorkflowToolProvider(data) + onRefreshData?.() + getDetail(workflowAppId) + Toast.notify({ + type: 'success', + message: t('common.api.actionSuccess'), + }) + setShowModal(false) + } + catch (e) { + Toast.notify({ type: 'error', message: (e as Error).message }) + } + } + + return ( + <> +
+ {(!published || !isLoading) && ( +
+
!published && setShowModal(true)} + > + +
{t('workflow.common.workflowAsTool')}
+ {!published && ( + {t('workflow.common.configureRequired').toLocaleUpperCase()} + )} +
+ {published && ( +
+
+ + +
+ {outdated &&
{t('workflow.common.workflowAsToolTip')}
} +
+ )} +
+ )} + {published && isLoading &&
} +
+ {showModal && ( + setShowModal(false)} + onCreate={createHandle} + onSave={updateWorkflowToolProvider} + /> + )} + + ) +} +export default WorkflowToolConfigureButton diff --git a/web/app/components/tools/workflow-tool/confirm-modal/index.tsx b/web/app/components/tools/workflow-tool/confirm-modal/index.tsx new file mode 100644 index 0000000000..04338547e9 --- /dev/null +++ b/web/app/components/tools/workflow-tool/confirm-modal/index.tsx @@ -0,0 +1,47 @@ +'use client' + +import { useTranslation } from 'react-i18next' +import cn from 'classnames' +import s from './style.module.css' +import Button from '@/app/components/base/button' +import Modal from '@/app/components/base/modal' +import { XClose } from '@/app/components/base/icons/src/vender/line/general' +import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' + +type ConfirmModalProps = { + show: boolean + onConfirm?: () => void + onClose: () => void +} + +const ConfirmModal = ({ show, onConfirm, onClose }: ConfirmModalProps) => { + const { t } = useTranslation() + + return ( + {}} + > +
+ +
+
+ +
+
{t('tools.createTool.confirmTitle')}
+
+ {t('tools.createTool.confirmTip')} +
+
+
+ + +
+
+
+ ) +} + +export default ConfirmModal diff --git a/web/app/components/tools/workflow-tool/confirm-modal/style.module.css b/web/app/components/tools/workflow-tool/confirm-modal/style.module.css new file mode 100644 index 0000000000..14367ec575 --- /dev/null +++ b/web/app/components/tools/workflow-tool/confirm-modal/style.module.css @@ -0,0 +1,3 @@ +.bg { + background: linear-gradient(180deg, rgba(247, 144, 9, 0.05) 0%, rgba(247, 144, 9, 0.00) 24.41%), #F9FAFB; +} diff --git a/web/app/components/tools/workflow-tool/index.tsx b/web/app/components/tools/workflow-tool/index.tsx new file mode 100644 index 0000000000..6fbcf33769 --- /dev/null +++ b/web/app/components/tools/workflow-tool/index.tsx @@ -0,0 +1,282 @@ +'use client' +import type { FC } from 'react' +import React, { useState } from 'react' +import { useTranslation } from 'react-i18next' +import cn from 'classnames' +import produce from 'immer' +import type { Emoji, WorkflowToolProviderParameter, WorkflowToolProviderRequest } from '../types' +import Drawer from '@/app/components/base/drawer-plus' +import Button from '@/app/components/base/button' +import Toast from '@/app/components/base/toast' +import EmojiPicker from '@/app/components/base/emoji-picker' +import AppIcon from '@/app/components/base/app-icon' +import MethodSelector from '@/app/components/tools/workflow-tool/method-selector' +import LabelSelector from '@/app/components/tools/labels/selector' +import ConfirmModal from '@/app/components/tools/workflow-tool/confirm-modal' +import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general' +import Tooltip from '@/app/components/base/tooltip' + +type Props = { + isAdd?: boolean + payload: any + onHide: () => void + onRemove?: () => void + onCreate?: (payload: WorkflowToolProviderRequest & { workflow_app_id: string }) => void + onSave?: (payload: WorkflowToolProviderRequest & Partial<{ + workflow_app_id: string + workflow_tool_id: string + }>) => void +} +// Add and Edit +const WorkflowToolAsModal: FC = ({ + isAdd, + payload, + onHide, + onRemove, + onSave, + onCreate, +}) => { + const { t } = useTranslation() + + const [showEmojiPicker, setShowEmojiPicker] = useState(false) + const [emoji, setEmoji] = useState(payload.icon) + const [label, setLabel] = useState(payload.label) + const [name, setName] = useState(payload.name) + const [description, setDescription] = useState(payload.description) + const [parameters, setParameters] = useState(payload.parameters) + const handleParameterChange = (key: string, value: string, index: number) => { + const newData = produce(parameters, (draft: WorkflowToolProviderParameter[]) => { + if (key === 'description') + draft[index].description = value + else + draft[index].form = value + }) + setParameters(newData) + } + const [labels, setLabels] = useState(payload.labels) + const handleLabelSelect = (value: string[]) => { + setLabels(value) + } + const [privacyPolicy, setPrivacyPolicy] = useState(payload.privacy_policy) + const [showModal, setShowModal] = useState(false) + + const isNameValid = (name: string) => { + return /^[a-zA-Z0-9_]+$/.test(name) + } + + const onConfirm = () => { + if (!label) { + return Toast.notify({ + type: 'error', + message: 'Please enter the tool name', + }) + } + if (!name) { + return Toast.notify({ + type: 'error', + message: 'Please enter the name for tool call', + }) + } + else if (!isNameValid(name)) { + return Toast.notify({ + type: 'error', + message: 'Name for tool call can only contain numbers, letters, and underscores', + }) + } + const requestParams = { + name, + description, + icon: emoji, + label, + parameters: parameters.map(item => ({ + name: item.name, + description: item.description, + form: item.form, + })), + labels, + privacy_policy: privacyPolicy, + } + if (!isAdd) { + onSave?.({ + ...requestParams, + workflow_tool_id: payload.workflow_tool_id, + }) + } + else { + onCreate?.({ + ...requestParams, + workflow_app_id: payload.workflow_app_id, + }) + } + } + + return ( + <> + +
+ {/* name & icon */} +
+
{t('tools.createTool.name')}
+
+ { setShowEmojiPicker(true) }} className='cursor-pointer' icon={emoji.content} background={emoji.background} /> + setLabel(e.target.value)} + /> +
+
+ {/* name for tool call */} +
+
+ {t('tools.createTool.nameForToolCall')} + + {t('tools.createTool.nameForToolCallPlaceHolder')} +
+ } + selector='workflow-tool-modal-tooltip' + > + + +
+ setName(e.target.value)} + /> + {!isNameValid(name) && ( +
{t('tools.createTool.nameForToolCallTip')}
+ )} +
+ {/* description */} +
+
{t('tools.createTool.description')}
+