From 4123c0a9609295b1d6697c8263c182d38b1b97be Mon Sep 17 00:00:00 2001 From: jZonG Date: Wed, 7 May 2025 19:53:45 +0800 Subject: [PATCH 01/58] mcp list --- web/app/components/tools/mcp/index.tsx | 20 +++++++++++++++ web/app/components/tools/provider-list.tsx | 29 +++++++++++++--------- 2 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 web/app/components/tools/mcp/index.tsx diff --git a/web/app/components/tools/mcp/index.tsx b/web/app/components/tools/mcp/index.tsx new file mode 100644 index 0000000000..930be386a1 --- /dev/null +++ b/web/app/components/tools/mcp/index.tsx @@ -0,0 +1,20 @@ +'use client' +import React from 'react' + +type Props = { + searchText: string +} + +const MCPList = ({ + searchText, +}: Props) => { + return ( + <> +
+ MCP + {searchText} +
+ + ) +} +export default MCPList diff --git a/web/app/components/tools/provider-list.tsx b/web/app/components/tools/provider-list.tsx index d1144d7e69..55c3c2212d 100644 --- a/web/app/components/tools/provider-list.tsx +++ b/web/app/components/tools/provider-list.tsx @@ -15,6 +15,7 @@ import WorkflowToolEmpty from '@/app/components/tools/add-tool-modal/empty' import Card from '@/app/components/plugins/card' import CardMoreInfo from '@/app/components/plugins/card/card-more-info' import PluginDetailPanel from '@/app/components/plugins/plugin-detail-panel' +import MCPList from './mcp' import { useSelector as useAppContextSelector } from '@/context/app-context' import { useAllToolProviders } from '@/service/use-tools' import { useInstalledPluginList, useInvalidateInstalledPluginList } from '@/service/use-plugins' @@ -31,6 +32,7 @@ const ProviderList = () => { { value: 'builtin', text: t('tools.type.builtIn') }, { value: 'api', text: t('tools.type.custom') }, { value: 'workflow', text: t('tools.type.workflow') }, + { value: 'mcp', text: 'MCP' }, ] const [tagFilterValue, setTagFilterValue] = useState([]) const handleTagsChange = (value: string[]) => { @@ -82,7 +84,9 @@ const ProviderList = () => { options={options} />
- + {activeTab !== 'mcp' && ( + + )} { {!filteredCollectionList.length && activeTab === 'builtin' && ( )} - { - enable_marketplace && activeTab === 'builtin' && ( - { - containerRef.current?.scrollTo({ top: containerRef.current.scrollHeight, behavior: 'smooth' }) - }} - searchPluginText={keywords} - filterPluginTags={tagFilterValue} - /> - ) - } + {enable_marketplace && activeTab === 'builtin' && ( + { + containerRef.current?.scrollTo({ top: containerRef.current.scrollHeight, behavior: 'smooth' }) + }} + searchPluginText={keywords} + filterPluginTags={tagFilterValue} + /> + )} + {activeTab === 'mcp' && ( + + )}
{currentProvider && !currentProvider.plugin_id && ( From d4b7a361821949c2dc6e2952bb8a5125b137d605 Mon Sep 17 00:00:00 2001 From: jZonG Date: Wed, 7 May 2025 20:28:34 +0800 Subject: [PATCH 02/58] MCP new card --- web/app/components/tools/mcp/create-card.tsx | 57 +++++++++++++++++++ web/app/components/tools/mcp/index.tsx | 14 ++++- web/app/components/tools/provider-list.tsx | 2 +- .../tools/provider/custom-create-card.tsx | 24 ++++---- web/i18n/en-US/tools.ts | 6 ++ web/i18n/zh-Hans/tools.ts | 6 ++ 6 files changed, 94 insertions(+), 15 deletions(-) create mode 100644 web/app/components/tools/mcp/create-card.tsx diff --git a/web/app/components/tools/mcp/create-card.tsx b/web/app/components/tools/mcp/create-card.tsx new file mode 100644 index 0000000000..51cd03ba61 --- /dev/null +++ b/web/app/components/tools/mcp/create-card.tsx @@ -0,0 +1,57 @@ +'use client' +import { useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useContext } from 'use-context-selector' +import { + RiAddCircleFill, + RiArrowRightUpLine, + RiBookOpenLine, +} from '@remixicon/react' +import I18n from '@/context/i18n' +import { getLanguage } from '@/i18n/language' +import { useAppContext } from '@/context/app-context' + +type Props = { + handleCreate: () => void +} + +const NewMCPCard = ({ handleCreate }: Props) => { + const { t } = useTranslation() + const { locale } = useContext(I18n) + const language = getLanguage(locale) + const { isCurrentWorkspaceManager } = useAppContext() + + const linkUrl = useMemo(() => { + // TODO help link + if (language.startsWith('zh_')) + return 'https://docs.dify.ai/zh-hans/guides/tools#ru-he-chuang-jian-zi-ding-yi-gong-ju' + return 'https://docs.dify.ai/en/guides/tools#how-to-create-custom-tools' + }, [language]) + + const [showModal, setShowModal] = useState(false) + + return ( + <> + {isCurrentWorkspaceManager && ( +
+
setShowModal(true)}> +
+
+ +
+
{t('tools.mcp.create.cardTitle')}
+
+
+
+ + +
{t('tools.mcp.create.cardLink')}
+ +
+
+
+ )} + + ) +} +export default NewMCPCard diff --git a/web/app/components/tools/mcp/index.tsx b/web/app/components/tools/mcp/index.tsx index 930be386a1..0e2c733a90 100644 --- a/web/app/components/tools/mcp/index.tsx +++ b/web/app/components/tools/mcp/index.tsx @@ -1,5 +1,7 @@ 'use client' import React from 'react' +import NewMCPCard from './create-card' +import cn from '@/utils/classnames' type Props = { searchText: string @@ -8,10 +10,18 @@ type Props = { const MCPList = ({ searchText, }: Props) => { + const handleCreate = () => { + console.log('handleCreate') + } return ( <> -
- MCP +
+ {searchText}
diff --git a/web/app/components/tools/provider-list.tsx b/web/app/components/tools/provider-list.tsx index 55c3c2212d..d5ef0f0130 100644 --- a/web/app/components/tools/provider-list.tsx +++ b/web/app/components/tools/provider-list.tsx @@ -97,7 +97,7 @@ const ProviderList = () => { />
- {(filteredCollectionList.length > 0 || activeTab !== 'builtin') && ( + {(filteredCollectionList.length > 0 || (activeTab !== 'builtin' && activeTab !== 'mcp')) && (
{ return ( <> {isCurrentWorkspaceManager && ( -
-
setIsShowEditCustomCollectionModal(true)}> +
+
setIsShowEditCustomCollectionModal(true)}>
-
- +
+
-
{t('tools.createCustomTool')}
+
{t('tools.createCustomTool')}
- diff --git a/web/i18n/en-US/tools.ts b/web/i18n/en-US/tools.ts index f624fac945..550989b58a 100644 --- a/web/i18n/en-US/tools.ts +++ b/web/i18n/en-US/tools.ts @@ -153,6 +153,12 @@ const translation = { toolNameUsageTip: 'Tool call name for agent reasoning and prompting', copyToolName: 'Copy Name', noTools: 'No tools found', + mcp: { + create: { + cardTitle: 'Add MCP Server (HTTP)', + cardLink: 'Learn more about MCP server integration', + }, + }, } export default translation diff --git a/web/i18n/zh-Hans/tools.ts b/web/i18n/zh-Hans/tools.ts index 98e7b6e271..b1a9978940 100644 --- a/web/i18n/zh-Hans/tools.ts +++ b/web/i18n/zh-Hans/tools.ts @@ -153,6 +153,12 @@ const translation = { toolNameUsageTip: '工具调用名称,用于 Agent 推理和提示词', copyToolName: '复制名称', noTools: '没有工具', + mcp: { + create: { + cardTitle: '添加 MCP 服务 (HTTP)', + cardLink: '了解更多关于 MCP 服务集成的信息', + }, + }, } export default translation From 5a8c12470cc9a2d94efd3b331cc9cd1f7b52381e Mon Sep 17 00:00:00 2001 From: jZonG Date: Wed, 7 May 2025 20:40:38 +0800 Subject: [PATCH 03/58] empty list --- web/app/components/tools/mcp/index.tsx | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/web/app/components/tools/mcp/index.tsx b/web/app/components/tools/mcp/index.tsx index 0e2c733a90..15abf9c2ea 100644 --- a/web/app/components/tools/mcp/index.tsx +++ b/web/app/components/tools/mcp/index.tsx @@ -13,16 +13,35 @@ const MCPList = ({ const handleCreate = () => { console.log('handleCreate') } + + function renderDefaultCard() { + const defaultCards = Array.from({ length: 36 }, (_, index) => ( +
= 4 && index < 8 && 'opacity-50', + index >= 8 && index < 12 && 'opacity-40', + index >= 12 && index < 16 && 'opacity-30', + index >= 16 && index < 20 && 'opacity-25', + index >= 20 && index < 24 && 'opacity-20', + )} + >
+ )) + return defaultCards + } + return ( <>
- {searchText} + {renderDefaultCard()}
) From 2dbad07433436e0d7c6991b9a3057e4a1e83d30f Mon Sep 17 00:00:00 2001 From: jZonG Date: Wed, 7 May 2025 21:14:37 +0800 Subject: [PATCH 04/58] mock data of list --- web/app/components/tools/mcp/index.tsx | 25 ++++++++++++------ web/app/components/tools/mcp/mock.ts | 35 ++++++++++++++++++++++++++ web/app/components/tools/types.ts | 13 ++++++++++ web/service/use-tools.ts | 18 +++++++++++++ 4 files changed, 84 insertions(+), 7 deletions(-) create mode 100644 web/app/components/tools/mcp/mock.ts diff --git a/web/app/components/tools/mcp/index.tsx b/web/app/components/tools/mcp/index.tsx index 15abf9c2ea..fe92c6b288 100644 --- a/web/app/components/tools/mcp/index.tsx +++ b/web/app/components/tools/mcp/index.tsx @@ -1,6 +1,7 @@ 'use client' -import React from 'react' +import { useMemo } from 'react' import NewMCPCard from './create-card' +import { useAllMCPTools, useInvalidateAllMCPTools } from '@/service/use-tools' import cn from '@/utils/classnames' type Props = { @@ -10,9 +11,16 @@ type Props = { const MCPList = ({ searchText, }: Props) => { - const handleCreate = () => { - console.log('handleCreate') - } + const { data: list = [] } = useAllMCPTools() + const invalidateMCPList = useInvalidateAllMCPTools() + + const filteredList = useMemo(() => { + return list.filter((collection) => { + if (searchText) + return Object.values(collection.name).some(value => (value as string).toLowerCase().includes(searchText.toLowerCase())) + return true + }) + }, [list, searchText]) function renderDefaultCard() { const defaultCards = Array.from({ length: 36 }, (_, index) => ( @@ -37,11 +45,14 @@ const MCPList = ({
- - {renderDefaultCard()} + + {filteredList.map((item, index) => ( +
+ ))} + {!list.length && renderDefaultCard()}
) diff --git a/web/app/components/tools/mcp/mock.ts b/web/app/components/tools/mcp/mock.ts new file mode 100644 index 0000000000..26633f824f --- /dev/null +++ b/web/app/components/tools/mcp/mock.ts @@ -0,0 +1,35 @@ +export const listData = [ + { + id: 'fdjklajfkljadslf', + author: 'KVOJJJin', + name: 'GOGOGO', + icon: 'https://cloud.dify.dev/console/api/workspaces/694cc430-fa36-4458-86a0-4a98c09c4684/model-providers/langgenius/openai/openai/icon_large/en_US', + server_url: 'https://mcp.composio.dev/notion/****/abc', + type: 'mcp', + is_team_authorization: false, + tools: ['aaa', 'bbb'], + update_elapsed_time: 1742892299, + }, + { + id: 'fdjklajfkljadslf', + author: 'KVOJJJin', + name: 'GOGOGO2', + icon: 'https://cloud.dify.dev/console/api/workspaces/694cc430-fa36-4458-86a0-4a98c09c4684/model-providers/langgenius/openai/openai/icon_large/en_US', + server_url: 'https://mcp.composio.dev/notion/****/abc', + type: 'mcp', + is_team_authorization: false, + tools: ['aaa', 'bbb'], + update_elapsed_time: 1742892299, + }, + { + id: 'fdjklajfkljadslf', + author: 'KVOJJJin', + name: 'GOGOGO3', + icon: 'https://cloud.dify.dev/console/api/workspaces/694cc430-fa36-4458-86a0-4a98c09c4684/model-providers/langgenius/openai/openai/icon_large/en_US', + server_url: 'https://mcp.composio.dev/notion/****/abc', + type: 'mcp', + is_team_authorization: false, + tools: ['aaa', 'bbb'], + update_elapsed_time: 1742892299, + }, +] diff --git a/web/app/components/tools/types.ts b/web/app/components/tools/types.ts index 32c468cde8..a2e5fc135d 100644 --- a/web/app/components/tools/types.ts +++ b/web/app/components/tools/types.ts @@ -29,6 +29,7 @@ export enum CollectionType { custom = 'api', model = 'model', workflow = 'workflow', + mcp = 'mcp', } export type Emoji = { @@ -168,3 +169,15 @@ export type WorkflowToolProviderResponse = { } privacy_policy: string } + +export type MCPProvider = { + id: string + author: string + name: string + icon: string | Emoji + server_url: string + type: CollectionType + is_team_authorization: boolean + tools: string[] + update_elapsed_time: number +} diff --git a/web/service/use-tools.ts b/web/service/use-tools.ts index ceaa4b14b3..2a785a0150 100644 --- a/web/service/use-tools.ts +++ b/web/service/use-tools.ts @@ -4,6 +4,7 @@ import type { Tool, } from '@/app/components/tools/types' import type { ToolWithProvider } from '@/app/components/workflow/types' +import type { MCPProvider } from '@/app/components/tools/types' import { useInvalid } from './use-base' import { useMutation, @@ -11,6 +12,8 @@ import { useQueryClient, } from '@tanstack/react-query' +import { listData } from '@/app/components/tools/mcp/mock' + const NAME_SPACE = 'tools' const useAllToolProvidersKey = [NAME_SPACE, 'allToolProviders'] @@ -61,6 +64,21 @@ export const useInvalidateAllWorkflowTools = () => { return useInvalid(useAllWorkflowToolsKey) } +const useAllMCPToolsKey = [NAME_SPACE, 'MCPTools'] +export const useAllMCPTools = () => { + return useQuery({ + queryKey: useAllMCPToolsKey, + // queryFn: () => get('/workspaces/current/tools/mcp'), + queryFn: () => { + return listData as unknown as MCPProvider[] + }, + }) +} + +export const useInvalidateAllMCPTools = () => { + return useInvalid(useAllMCPToolsKey) +} + export const useBuiltinProviderInfo = (providerName: string) => { return useQuery({ queryKey: [NAME_SPACE, 'builtin-provider-info', providerName], From ec31bbc24a4d13aed6cb7f70b264665d17285b60 Mon Sep 17 00:00:00 2001 From: jZonG Date: Wed, 7 May 2025 23:13:15 +0800 Subject: [PATCH 05/58] mcp card --- web/app/components/tools/mcp/hooks.ts | 12 ++++ web/app/components/tools/mcp/index.tsx | 19 +++-- web/app/components/tools/mcp/mock.ts | 18 ++--- .../components/tools/mcp/provider-card.tsx | 72 +++++++++++++++++++ web/i18n/en-US/tools.ts | 4 ++ web/i18n/zh-Hans/tools.ts | 4 ++ 6 files changed, 115 insertions(+), 14 deletions(-) create mode 100644 web/app/components/tools/mcp/hooks.ts create mode 100644 web/app/components/tools/mcp/provider-card.tsx diff --git a/web/app/components/tools/mcp/hooks.ts b/web/app/components/tools/mcp/hooks.ts new file mode 100644 index 0000000000..b2b521557f --- /dev/null +++ b/web/app/components/tools/mcp/hooks.ts @@ -0,0 +1,12 @@ +import dayjs from 'dayjs' +import { useCallback } from 'react' +import { useI18N } from '@/context/i18n' + +export const useFormatTimeFromNow = () => { + const { locale } = useI18N() + const formatTimeFromNow = useCallback((time: number) => { + return dayjs(time).locale(locale === 'zh-Hans' ? 'zh-cn' : locale).fromNow() + }, [locale]) + + return { formatTimeFromNow } +} diff --git a/web/app/components/tools/mcp/index.tsx b/web/app/components/tools/mcp/index.tsx index fe92c6b288..248d791202 100644 --- a/web/app/components/tools/mcp/index.tsx +++ b/web/app/components/tools/mcp/index.tsx @@ -1,7 +1,9 @@ 'use client' -import { useMemo } from 'react' +import { useMemo, useState } from 'react' import NewMCPCard from './create-card' +import MCPCard from './provider-card' import { useAllMCPTools, useInvalidateAllMCPTools } from '@/service/use-tools' +import type { MCPProvider } from '@/app/components/tools/types' import cn from '@/utils/classnames' type Props = { @@ -22,12 +24,14 @@ const MCPList = ({ }) }, [list, searchText]) + const [currentProvider, setCurrentProvider] = useState() + function renderDefaultCard() { const defaultCards = Array.from({ length: 36 }, (_, index) => (
= 4 && index < 8 && 'opacity-50', index >= 8 && index < 12 && 'opacity-40', @@ -44,13 +48,18 @@ const MCPList = ({ <>
- {filteredList.map((item, index) => ( -
+ {filteredList.map(provider => ( + ))} {!list.length && renderDefaultCard()}
diff --git a/web/app/components/tools/mcp/mock.ts b/web/app/components/tools/mcp/mock.ts index 26633f824f..083e84bddb 100644 --- a/web/app/components/tools/mcp/mock.ts +++ b/web/app/components/tools/mcp/mock.ts @@ -1,35 +1,35 @@ export const listData = [ { - id: 'fdjklajfkljadslf', + id: 'fdjklajfkljadslf111', author: 'KVOJJJin', name: 'GOGOGO', icon: 'https://cloud.dify.dev/console/api/workspaces/694cc430-fa36-4458-86a0-4a98c09c4684/model-providers/langgenius/openai/openai/icon_large/en_US', server_url: 'https://mcp.composio.dev/notion/****/abc', type: 'mcp', - is_team_authorization: false, + is_team_authorization: true, tools: ['aaa', 'bbb'], - update_elapsed_time: 1742892299, + update_elapsed_time: 1744793369, }, { - id: 'fdjklajfkljadslf', + id: 'fdjklajfkljadslf222', author: 'KVOJJJin', name: 'GOGOGO2', icon: 'https://cloud.dify.dev/console/api/workspaces/694cc430-fa36-4458-86a0-4a98c09c4684/model-providers/langgenius/openai/openai/icon_large/en_US', server_url: 'https://mcp.composio.dev/notion/****/abc', type: 'mcp', is_team_authorization: false, - tools: ['aaa', 'bbb'], - update_elapsed_time: 1742892299, + tools: [], + update_elapsed_time: 1744793369, }, { - id: 'fdjklajfkljadslf', + id: 'fdjklajfkljadslf333', author: 'KVOJJJin', name: 'GOGOGO3', icon: 'https://cloud.dify.dev/console/api/workspaces/694cc430-fa36-4458-86a0-4a98c09c4684/model-providers/langgenius/openai/openai/icon_large/en_US', server_url: 'https://mcp.composio.dev/notion/****/abc', type: 'mcp', - is_team_authorization: false, + is_team_authorization: true, tools: ['aaa', 'bbb'], - update_elapsed_time: 1742892299, + update_elapsed_time: 1744793369, }, ] diff --git a/web/app/components/tools/mcp/provider-card.tsx b/web/app/components/tools/mcp/provider-card.tsx new file mode 100644 index 0000000000..9080c45e53 --- /dev/null +++ b/web/app/components/tools/mcp/provider-card.tsx @@ -0,0 +1,72 @@ +'use client' +// import { useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' +// import { useContext } from 'use-context-selector' +// import I18n from '@/context/i18n' +// import { getLanguage } from '@/i18n/language' +// import { useAppContext } from '@/context/app-context' +import { RiHammerFill } from '@remixicon/react' +import Indicator from '@/app/components/header/indicator' +import Icon from '@/app/components/plugins/card/base/card-icon' +import { useFormatTimeFromNow } from './hooks' +import type { MCPProvider } from '@/app/components/tools/types' +import cn from '@/utils/classnames' + +type Props = { + currentProvider?: MCPProvider + data: MCPProvider + handleSelect: (provider: MCPProvider) => void +} + +const MCPCard = ({ + currentProvider, + data, + handleSelect, +}: Props) => { + const { t } = useTranslation() + const { formatTimeFromNow } = useFormatTimeFromNow() + // const { locale } = useContext(I18n) + // const language = getLanguage(locale) +// const { isCurrentWorkspaceManager } = useAppContext() + + return ( +
handleSelect(data)} + className={cn( + 'relative flex cursor-pointer flex-col rounded-xl border-[1.5px] border-transparent bg-components-card-bg shadow-xs hover:bg-components-card-bg-alt hover:shadow-md', + currentProvider?.id === data.id && 'border-components-option-card-option-selected-border bg-components-card-bg-alt', + )} + > +
+ +
+
{data.name}
+
+
+ + {data.tools.length > 0 && ( +
{t('tools.mcp.toolsCount', { count: data.tools.length })}
+ )} + {!data.tools.length && ( +
{t('tools.mcp.noTools')}
+ )} +
+
/
+
{`${t('tools.mcp.updateTime')} ${formatTimeFromNow(data.update_elapsed_time * 1000)}`}
+
+
+
+
+
{data.server_url}
+ {data.is_team_authorization && } + {!data.is_team_authorization && ( +
+ {t('tools.mcp.noConfigured')} + +
+ )} +
+
+ ) +} +export default MCPCard diff --git a/web/i18n/en-US/tools.ts b/web/i18n/en-US/tools.ts index 550989b58a..624819efae 100644 --- a/web/i18n/en-US/tools.ts +++ b/web/i18n/en-US/tools.ts @@ -158,6 +158,10 @@ const translation = { cardTitle: 'Add MCP Server (HTTP)', cardLink: 'Learn more about MCP server integration', }, + noConfigured: 'Unconfigured Server', + updateTime: 'Updated', + toolsCount: '{{count}} tools', + noTools: 'No tools available', }, } diff --git a/web/i18n/zh-Hans/tools.ts b/web/i18n/zh-Hans/tools.ts index b1a9978940..4f8dcc09e2 100644 --- a/web/i18n/zh-Hans/tools.ts +++ b/web/i18n/zh-Hans/tools.ts @@ -158,6 +158,10 @@ const translation = { cardTitle: '添加 MCP 服务 (HTTP)', cardLink: '了解更多关于 MCP 服务集成的信息', }, + noConfigured: '未配置', + updateTime: '更新于', + toolsCount: '{{count}} 个工具', + noTools: '没有可用的工具', }, } From b3faaa37542b26f7d6d15a978efa78f3828446ea Mon Sep 17 00:00:00 2001 From: jZonG Date: Wed, 7 May 2025 23:20:51 +0800 Subject: [PATCH 06/58] detail drawer --- web/app/components/tools/mcp/index.tsx | 44 +++++++++------- .../components/tools/mcp/provider-detail.tsx | 50 +++++++++++++++++++ 2 files changed, 76 insertions(+), 18 deletions(-) create mode 100644 web/app/components/tools/mcp/provider-detail.tsx diff --git a/web/app/components/tools/mcp/index.tsx b/web/app/components/tools/mcp/index.tsx index 248d791202..557d187562 100644 --- a/web/app/components/tools/mcp/index.tsx +++ b/web/app/components/tools/mcp/index.tsx @@ -2,6 +2,7 @@ import { useMemo, useState } from 'react' import NewMCPCard from './create-card' import MCPCard from './provider-card' +import MCPDetailPanel from './provider-detail' import { useAllMCPTools, useInvalidateAllMCPTools } from '@/service/use-tools' import type { MCPProvider } from '@/app/components/tools/types' import cn from '@/utils/classnames' @@ -10,6 +11,24 @@ type Props = { searchText: string } +function renderDefaultCard() { + const defaultCards = Array.from({ length: 36 }, (_, index) => ( +
= 4 && index < 8 && 'opacity-50', + index >= 8 && index < 12 && 'opacity-40', + index >= 12 && index < 16 && 'opacity-30', + index >= 16 && index < 20 && 'opacity-25', + index >= 20 && index < 24 && 'opacity-20', + )} + >
+ )) + return defaultCards +} + const MCPList = ({ searchText, }: Props) => { @@ -26,24 +45,6 @@ const MCPList = ({ const [currentProvider, setCurrentProvider] = useState() - function renderDefaultCard() { - const defaultCards = Array.from({ length: 36 }, (_, index) => ( -
= 4 && index < 8 && 'opacity-50', - index >= 8 && index < 12 && 'opacity-40', - index >= 12 && index < 16 && 'opacity-30', - index >= 16 && index < 20 && 'opacity-25', - index >= 20 && index < 24 && 'opacity-20', - )} - >
- )) - return defaultCards - } - return ( <>
+ {currentProvider && ( + setCurrentProvider(undefined)} + onUpdate={() => invalidateMCPList()} + /> + )} ) } diff --git a/web/app/components/tools/mcp/provider-detail.tsx b/web/app/components/tools/mcp/provider-detail.tsx new file mode 100644 index 0000000000..46d3077378 --- /dev/null +++ b/web/app/components/tools/mcp/provider-detail.tsx @@ -0,0 +1,50 @@ +'use client' +import React from 'react' +import type { FC } from 'react' +import Drawer from '@/app/components/base/drawer' +import type { MCPProvider } from '@/app/components/tools/types' +import cn from '@/utils/classnames' + +type Props = { + detail?: MCPProvider + onUpdate: () => void + onHide: () => void +} + +const MCPDetailPanel: FC = ({ + detail, + onUpdate, + onHide, +}) => { + const handleUpdate = (isDelete = false) => { + if (isDelete) + onHide() + onUpdate() + } + + if (!detail) + return null + + return ( + + {detail && ( + <> +
HEADER
+
+ TOOL list +
+ + )} +
+ ) +} + +export default MCPDetailPanel From 347cb2685eb960a456c0464ce042d745dd576cfd Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 8 May 2025 14:55:22 +0800 Subject: [PATCH 07/58] feat: add mcp tab and merge tool with server ts define --- web/app/components/tools/mcp/mock.ts | 12 ++++++++++++ web/app/components/tools/mcp/provider-card.tsx | 12 ++++++------ web/app/components/tools/mcp/provider-detail.tsx | 4 ++-- web/app/components/tools/types.ts | 15 +++------------ .../workflow/block-selector/all-tools.tsx | 8 ++++++-- .../components/workflow/block-selector/hooks.ts | 4 ++++ .../components/workflow/block-selector/tabs.tsx | 4 +++- .../workflow/block-selector/tool-picker.tsx | 4 +++- .../components/workflow/block-selector/types.ts | 1 + web/service/use-tools.ts | 7 +++---- 10 files changed, 43 insertions(+), 28 deletions(-) diff --git a/web/app/components/tools/mcp/mock.ts b/web/app/components/tools/mcp/mock.ts index 083e84bddb..2d497f7a22 100644 --- a/web/app/components/tools/mcp/mock.ts +++ b/web/app/components/tools/mcp/mock.ts @@ -9,6 +9,10 @@ export const listData = [ is_team_authorization: true, tools: ['aaa', 'bbb'], update_elapsed_time: 1744793369, + label: { + en_US: 'GOGOGO', + zh_Hans: 'GOGOGO', + }, }, { id: 'fdjklajfkljadslf222', @@ -20,6 +24,10 @@ export const listData = [ is_team_authorization: false, tools: [], update_elapsed_time: 1744793369, + label: { + en_US: 'GOGOGO2', + zh_Hans: 'GOGOGO2', + }, }, { id: 'fdjklajfkljadslf333', @@ -31,5 +39,9 @@ export const listData = [ is_team_authorization: true, tools: ['aaa', 'bbb'], update_elapsed_time: 1744793369, + label: { + en_US: 'GOGOGO3', + zh_Hans: 'GOGOGO3', + }, }, ] diff --git a/web/app/components/tools/mcp/provider-card.tsx b/web/app/components/tools/mcp/provider-card.tsx index 9080c45e53..53d387eb70 100644 --- a/web/app/components/tools/mcp/provider-card.tsx +++ b/web/app/components/tools/mcp/provider-card.tsx @@ -9,13 +9,13 @@ import { RiHammerFill } from '@remixicon/react' import Indicator from '@/app/components/header/indicator' import Icon from '@/app/components/plugins/card/base/card-icon' import { useFormatTimeFromNow } from './hooks' -import type { MCPProvider } from '@/app/components/tools/types' +import type { ToolWithProvider } from '../../workflow/types' import cn from '@/utils/classnames' type Props = { - currentProvider?: MCPProvider - data: MCPProvider - handleSelect: (provider: MCPProvider) => void + currentProvider?: ToolWithProvider + data: ToolWithProvider + handleSelect: (provider: ToolWithProvider) => void } const MCPCard = ({ @@ -27,7 +27,7 @@ const MCPCard = ({ const { formatTimeFromNow } = useFormatTimeFromNow() // const { locale } = useContext(I18n) // const language = getLanguage(locale) -// const { isCurrentWorkspaceManager } = useAppContext() + // const { isCurrentWorkspaceManager } = useAppContext() return (
/
-
{`${t('tools.mcp.updateTime')} ${formatTimeFromNow(data.update_elapsed_time * 1000)}`}
+
{`${t('tools.mcp.updateTime')} ${formatTimeFromNow(data.update_elapsed_time! * 1000)}`}
diff --git a/web/app/components/tools/mcp/provider-detail.tsx b/web/app/components/tools/mcp/provider-detail.tsx index 46d3077378..849736b7aa 100644 --- a/web/app/components/tools/mcp/provider-detail.tsx +++ b/web/app/components/tools/mcp/provider-detail.tsx @@ -2,11 +2,11 @@ import React from 'react' import type { FC } from 'react' import Drawer from '@/app/components/base/drawer' -import type { MCPProvider } from '@/app/components/tools/types' import cn from '@/utils/classnames' +import type { ToolWithProvider } from '../../workflow/types' type Props = { - detail?: MCPProvider + detail?: ToolWithProvider onUpdate: () => void onHide: () => void } diff --git a/web/app/components/tools/types.ts b/web/app/components/tools/types.ts index a2e5fc135d..c5a8a00acd 100644 --- a/web/app/components/tools/types.ts +++ b/web/app/components/tools/types.ts @@ -51,6 +51,9 @@ export type Collection = { labels: string[] plugin_id?: string letter?: string + // MCP Server + server_url?: string + update_elapsed_time?: number } export type ToolParameter = { @@ -169,15 +172,3 @@ export type WorkflowToolProviderResponse = { } privacy_policy: string } - -export type MCPProvider = { - id: string - author: string - name: string - icon: string | Emoji - server_url: string - type: CollectionType - is_team_authorization: boolean - tools: string[] - update_elapsed_time: number -} diff --git a/web/app/components/workflow/block-selector/all-tools.tsx b/web/app/components/workflow/block-selector/all-tools.tsx index 3ad0a41d54..7b3f62d5a7 100644 --- a/web/app/components/workflow/block-selector/all-tools.tsx +++ b/web/app/components/workflow/block-selector/all-tools.tsx @@ -31,6 +31,7 @@ type AllToolsProps = { buildInTools: ToolWithProvider[] customTools: ToolWithProvider[] workflowTools: ToolWithProvider[] + mcpTools: ToolWithProvider[] onSelect: OnSelectBlock supportAddCustomTool?: boolean onAddedCustomTool?: () => void @@ -49,6 +50,7 @@ const AllTools = ({ buildInTools, workflowTools, customTools, + mcpTools = [], supportAddCustomTool, onShowAddCustomCollectionModal, selectedTools, @@ -64,13 +66,15 @@ const AllTools = ({ const tools = useMemo(() => { let mergedTools: ToolWithProvider[] = [] if (activeTab === ToolTypeEnum.All) - mergedTools = [...buildInTools, ...customTools, ...workflowTools] + mergedTools = [...buildInTools, ...customTools, ...workflowTools, ...mcpTools] if (activeTab === ToolTypeEnum.BuiltIn) mergedTools = buildInTools if (activeTab === ToolTypeEnum.Custom) mergedTools = customTools if (activeTab === ToolTypeEnum.Workflow) mergedTools = workflowTools + if (activeTab === ToolTypeEnum.MCP) + mergedTools = mcpTools if (!hasFilter) return mergedTools.filter(toolWithProvider => toolWithProvider.tools.length > 0) @@ -80,7 +84,7 @@ const AllTools = ({ return tool.label[language].toLowerCase().includes(searchText.toLowerCase()) || tool.name.toLowerCase().includes(searchText.toLowerCase()) }) }) - }, [activeTab, buildInTools, customTools, workflowTools, searchText, language, hasFilter]) + }, [activeTab, buildInTools, customTools, workflowTools, mcpTools, searchText, language, hasFilter]) const { queryPluginsWithDebounced: fetchPlugins, diff --git a/web/app/components/workflow/block-selector/hooks.ts b/web/app/components/workflow/block-selector/hooks.ts index a8b1759506..791eb7f73f 100644 --- a/web/app/components/workflow/block-selector/hooks.ts +++ b/web/app/components/workflow/block-selector/hooks.ts @@ -51,5 +51,9 @@ export const useToolTabs = () => { key: ToolTypeEnum.Workflow, name: t('workflow.tabs.workflowTool'), }, + { + key: ToolTypeEnum.MCP, + name: 'MCP', + }, ] } diff --git a/web/app/components/workflow/block-selector/tabs.tsx b/web/app/components/workflow/block-selector/tabs.tsx index 67aaaba1a5..457315b5b8 100644 --- a/web/app/components/workflow/block-selector/tabs.tsx +++ b/web/app/components/workflow/block-selector/tabs.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' import { memo } from 'react' -import { useAllBuiltInTools, useAllCustomTools, useAllWorkflowTools } from '@/service/use-tools' +import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools } from '@/service/use-tools' import type { BlockEnum } from '../types' import { useTabs } from './hooks' import type { ToolDefaultValue } from './types' @@ -31,6 +31,7 @@ const Tabs: FC = ({ const { data: buildInTools } = useAllBuiltInTools() const { data: customTools } = useAllCustomTools() const { data: workflowTools } = useAllWorkflowTools() + const { data: mcpTools } = useAllMCPTools() return (
e.stopPropagation()}> @@ -75,6 +76,7 @@ const Tabs: FC = ({ buildInTools={buildInTools || []} customTools={customTools || []} workflowTools={workflowTools || []} + mcpTools={mcpTools || []} /> ) } diff --git a/web/app/components/workflow/block-selector/tool-picker.tsx b/web/app/components/workflow/block-selector/tool-picker.tsx index dbb49fde75..3d2ac1cb1f 100644 --- a/web/app/components/workflow/block-selector/tool-picker.tsx +++ b/web/app/components/workflow/block-selector/tool-picker.tsx @@ -23,7 +23,7 @@ import { } from '@/service/tools' import type { CustomCollectionBackend } from '@/app/components/tools/types' import Toast from '@/app/components/base/toast' -import { useAllBuiltInTools, useAllCustomTools, useAllWorkflowTools, useInvalidateAllCustomTools } from '@/service/use-tools' +import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools, useInvalidateAllCustomTools } from '@/service/use-tools' import cn from '@/utils/classnames' type Props = { @@ -61,6 +61,7 @@ const ToolPicker: FC = ({ const { data: customTools } = useAllCustomTools() const invalidateCustomTools = useInvalidateAllCustomTools() const { data: workflowTools } = useAllWorkflowTools() + const { data: mcpTools } = useAllMCPTools() const { builtinToolList, customToolList, workflowToolList } = useMemo(() => { if (scope === 'plugins') { @@ -162,6 +163,7 @@ const ToolPicker: FC = ({ buildInTools={builtinToolList || []} customTools={customToolList || []} workflowTools={workflowToolList || []} + mcpTools={mcpTools || []} supportAddCustomTool={supportAddCustomTool} onAddedCustomTool={handleAddedCustomTool} onShowAddCustomCollectionModal={showEditCustomCollectionModal} diff --git a/web/app/components/workflow/block-selector/types.ts b/web/app/components/workflow/block-selector/types.ts index 0abf7b9031..50e3cc24a8 100644 --- a/web/app/components/workflow/block-selector/types.ts +++ b/web/app/components/workflow/block-selector/types.ts @@ -8,6 +8,7 @@ export enum ToolTypeEnum { BuiltIn = 'built-in', Custom = 'custom', Workflow = 'workflow', + MCP = 'mcp', } export enum BlockClassificationEnum { diff --git a/web/service/use-tools.ts b/web/service/use-tools.ts index 2a785a0150..9a61a0792d 100644 --- a/web/service/use-tools.ts +++ b/web/service/use-tools.ts @@ -4,7 +4,6 @@ import type { Tool, } from '@/app/components/tools/types' import type { ToolWithProvider } from '@/app/components/workflow/types' -import type { MCPProvider } from '@/app/components/tools/types' import { useInvalid } from './use-base' import { useMutation, @@ -66,11 +65,11 @@ export const useInvalidateAllWorkflowTools = () => { const useAllMCPToolsKey = [NAME_SPACE, 'MCPTools'] export const useAllMCPTools = () => { - return useQuery({ + return useQuery({ queryKey: useAllMCPToolsKey, - // queryFn: () => get('/workspaces/current/tools/mcp'), + // queryFn: () => get('/workspaces/current/tools/mcp'), queryFn: () => { - return listData as unknown as MCPProvider[] + return listData as unknown as ToolWithProvider[] }, }) } From 46899597caf19efa8d1d11b90ceccd0c1dcc0c4a Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 8 May 2025 15:29:21 +0800 Subject: [PATCH 08/58] feat: tools picker can choose mcp item --- .../tool-selector/index.tsx | 6 +- web/app/components/tools/mcp/mock.ts | 111 +++++++++++++++++- 2 files changed, 113 insertions(+), 4 deletions(-) diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx index 577de19484..4bbcb58bfb 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx @@ -30,6 +30,7 @@ import { useAppContext } from '@/context/app-context' import { useAllBuiltInTools, useAllCustomTools, + useAllMCPTools, useAllWorkflowTools, useInvalidateAllBuiltInTools, useUpdateProviderCredentials, @@ -103,6 +104,7 @@ const ToolSelector: FC = ({ const { data: buildInTools } = useAllBuiltInTools() const { data: customTools } = useAllCustomTools() const { data: workflowTools } = useAllWorkflowTools() + const { data: mcpTools } = useAllMCPTools() const invalidateAllBuiltinTools = useInvalidateAllBuiltInTools() const invalidateInstalledPluginList = useInvalidateInstalledPluginList() @@ -110,11 +112,11 @@ const ToolSelector: FC = ({ const { inMarketPlace, manifest } = usePluginInstalledCheck(value?.provider_name) const currentProvider = useMemo(() => { - const mergedTools = [...(buildInTools || []), ...(customTools || []), ...(workflowTools || [])] + const mergedTools = [...(buildInTools || []), ...(customTools || []), ...(workflowTools || []), ...(mcpTools || [])] return mergedTools.find((toolWithProvider) => { return toolWithProvider.id === value?.provider_name }) - }, [value, buildInTools, customTools, workflowTools]) + }, [value, buildInTools, customTools, workflowTools, mcpTools]) const [isShowChooseTool, setIsShowChooseTool] = useState(false) const handleSelectTool = (tool: ToolDefaultValue) => { diff --git a/web/app/components/tools/mcp/mock.ts b/web/app/components/tools/mcp/mock.ts index 2d497f7a22..341061c44e 100644 --- a/web/app/components/tools/mcp/mock.ts +++ b/web/app/components/tools/mcp/mock.ts @@ -1,3 +1,110 @@ +const tools = [ + { + author: 'Novice', + name: 'NOTION_ADD_PAGE_CONTENT', + label: { + en_US: 'NOTION_ADD_PAGE_CONTENT', + zh_Hans: 'NOTION_ADD_PAGE_CONTENT', + pt_BR: 'NOTION_ADD_PAGE_CONTENT', + ja_JP: 'NOTION_ADD_PAGE_CONTENT', + }, + description: { + en_US: 'Adds a single content block to a notion page. multiple calls needed for multiple blocks. note: only supports adding to notion pages. blocks that can contain children: - page (any block type) - toggle (any nested content) - to-do (nested to-dos/blocks) - bulleted list (nested lists/blocks) - numbered list (nested lists/blocks) - callout (child blocks) - quote (nested blocks)', + zh_Hans: 'Adds a single content block to a notion page. multiple calls needed for multiple blocks. note: only supports adding to notion pages. blocks that can contain children: - page (any block type) - toggle (any nested content) - to-do (nested to-dos/blocks) - bulleted list (nested lists/blocks) - numbered list (nested lists/blocks) - callout (child blocks) - quote (nested blocks)', + pt_BR: 'Adds a single content block to a notion page. multiple calls needed for multiple blocks. note: only supports adding to notion pages. blocks that can contain children: - page (any block type) - toggle (any nested content) - to-do (nested to-dos/blocks) - bulleted list (nested lists/blocks) - numbered list (nested lists/blocks) - callout (child blocks) - quote (nested blocks)', + ja_JP: 'Adds a single content block to a notion page. multiple calls needed for multiple blocks. note: only supports adding to notion pages. blocks that can contain children: - page (any block type) - toggle (any nested content) - to-do (nested to-dos/blocks) - bulleted list (nested lists/blocks) - numbered list (nested lists/blocks) - callout (child blocks) - quote (nested blocks)', + }, + parameters: [ + { + name: 'after', + label: { + en_US: 'after', + zh_Hans: 'after', + pt_BR: 'after', + ja_JP: 'after', + }, + placeholder: null, + scope: null, + auto_generate: null, + template: null, + required: false, + default: null, + min: null, + max: null, + precision: null, + options: [], + type: 'string', + human_description: { + en_US: 'The ID of the existing block that the new block should be appended after. If not provided, content will be appended at the end of the page.', + zh_Hans: 'The ID of the existing block that the new block should be appended after. If not provided, content will be appended at the end of the page.', + pt_BR: 'The ID of the existing block that the new block should be appended after. If not provided, content will be appended at the end of the page.', + ja_JP: 'The ID of the existing block that the new block should be appended after. If not provided, content will be appended at the end of the page.', + }, + form: 'llm', + llm_description: 'The ID of the existing block that the new block should be appended after. If not provided, content will be appended at the end of the page.', + }, + { + name: 'content_block', + label: { + en_US: 'content_block', + zh_Hans: 'content_block', + pt_BR: 'content_block', + ja_JP: 'content_block', + }, + placeholder: null, + scope: null, + auto_generate: null, + template: null, + required: false, + default: null, + min: null, + max: null, + precision: null, + options: [], + type: 'string', + human_description: { + en_US: 'Child content to append to a page.', + zh_Hans: 'Child content to append to a page.', + pt_BR: 'Child content to append to a page.', + ja_JP: 'Child content to append to a page.', + }, + form: 'llm', + llm_description: 'Child content to append to a page.', + }, + { + name: 'parent_block_id', + label: { + en_US: 'parent_block_id', + zh_Hans: 'parent_block_id', + pt_BR: 'parent_block_id', + ja_JP: 'parent_block_id', + }, + placeholder: null, + scope: null, + auto_generate: null, + template: null, + required: false, + default: null, + min: null, + max: null, + precision: null, + options: [], + type: 'string', + human_description: { + en_US: 'The ID of the page which the children will be added.', + zh_Hans: 'The ID of the page which the children will be added.', + pt_BR: 'The ID of the page which the children will be added.', + ja_JP: 'The ID of the page which the children will be added.', + }, + form: 'llm', + llm_description: 'The ID of the page which the children will be added.', + }, + ], + labels: [], + output_schema: null, + }, +] + export const listData = [ { id: 'fdjklajfkljadslf111', @@ -7,7 +114,7 @@ export const listData = [ server_url: 'https://mcp.composio.dev/notion/****/abc', type: 'mcp', is_team_authorization: true, - tools: ['aaa', 'bbb'], + tools, update_elapsed_time: 1744793369, label: { en_US: 'GOGOGO', @@ -37,7 +144,7 @@ export const listData = [ server_url: 'https://mcp.composio.dev/notion/****/abc', type: 'mcp', is_team_authorization: true, - tools: ['aaa', 'bbb'], + tools, update_elapsed_time: 1744793369, label: { en_US: 'GOGOGO3', From 61d46a512e6920595326fc1931e494369f1efd57 Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 8 May 2025 18:17:32 +0800 Subject: [PATCH 09/58] feat: agent app can choose mcp --- .../config/agent/agent-tools/index.tsx | 27 ++++++++++++++----- .../agent-tools/setting-built-in-tool.tsx | 12 +++++---- 2 files changed, 28 insertions(+), 11 deletions(-) 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 4b773c01ba..8f08d26bd2 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 @@ -30,14 +30,30 @@ import ConfigCredential from '@/app/components/tools/setting/build-in/config-cre import { updateBuiltInToolCredential } from '@/service/tools' import cn from '@/utils/classnames' import ToolPicker from '@/app/components/workflow/block-selector/tool-picker' -import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types' +import type { ToolDefaultValue, ToolValue } from '@/app/components/workflow/block-selector/types' import { canFindTool } from '@/utils' +import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools } from '@/service/use-tools' +import type { ToolWithProvider } from '@/app/components/workflow/types' type AgentToolWithMoreInfo = AgentTool & { icon: any; collection?: Collection } | null const AgentTools: FC = () => { const { t } = useTranslation() const [isShowChooseTool, setIsShowChooseTool] = useState(false) - const { modelConfig, setModelConfig, collectionList } = useContext(ConfigContext) + const { modelConfig, setModelConfig } = useContext(ConfigContext) + const { data: buildInTools } = useAllBuiltInTools() + const { data: customTools } = useAllCustomTools() + const { data: workflowTools } = useAllWorkflowTools() + const { data: mcpTools } = useAllMCPTools() + const collectionList = useMemo(() => { + const allTools = [ + ...(buildInTools || []), + ...(customTools || []), + ...(workflowTools || []), + ...(mcpTools || []), + ] + return allTools + }, [buildInTools, customTools, workflowTools, mcpTools]) + const formattingChangedDispatcher = useFormattingChangedDispatcher() const [currentTool, setCurrentTool] = useState(null) @@ -132,7 +148,7 @@ const AgentTools: FC = () => { disabled={false} supportAddCustomTool onSelect={handleSelectTool} - selectedTools={tools} + selectedTools={tools as unknown as ToolValue[]} /> )} @@ -150,7 +166,7 @@ const AgentTools: FC = () => {
{item.isDeleted && } {!item.isDeleted && ( -
+
{typeof item.icon === 'string' &&
} {typeof item.icon !== 'string' && }
@@ -274,8 +290,7 @@ const AgentTools: FC = () => { setIsShowSettingTool(false)} 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 952ad66fc4..1ad814c6e9 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 @@ -24,10 +24,11 @@ import { fetchBuiltInToolList, fetchCustomToolList, fetchModelToolList, fetchWor import I18n from '@/context/i18n' import { getLanguage } from '@/i18n/language' import cn from '@/utils/classnames' +import type { ToolWithProvider } from '@/app/components/workflow/types' type Props = { showBackButton?: boolean - collection: Collection + collection: Collection | ToolWithProvider isBuiltIn?: boolean isModel?: boolean toolName: string @@ -51,9 +52,10 @@ const SettingBuiltInTool: FC = ({ const { locale } = useContext(I18n) const language = getLanguage(locale) const { t } = useTranslation() - - const [isLoading, setIsLoading] = useState(true) - const [tools, setTools] = useState([]) + const passedTools = (collection as ToolWithProvider).tools + const hasPassedTools = passedTools?.length > 0 + const [isLoading, setIsLoading] = useState(!hasPassedTools) + const [tools, setTools] = useState(hasPassedTools ? passedTools : []) const currTool = tools.find(tool => tool.name === toolName) const formSchemas = currTool ? toolParametersToFormSchemas(currTool.parameters) : [] const infoSchemas = formSchemas.filter(item => item.form === 'llm') @@ -63,7 +65,7 @@ const SettingBuiltInTool: FC = ({ const [currType, setCurrType] = useState('info') const isInfoActive = currType === 'info' useEffect(() => { - if (!collection) + if (!collection || hasPassedTools) return (async () => { From 79390ca0dc0ec882cbd377c217a54aa644dd7133 Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 8 May 2025 18:39:46 +0800 Subject: [PATCH 10/58] chore: tag select place --- .../plugins/marketplace/search-box/index.tsx | 14 +++--- .../marketplace/search-box/tags-filter.tsx | 50 +++---------------- 2 files changed, 15 insertions(+), 49 deletions(-) diff --git a/web/app/components/plugins/marketplace/search-box/index.tsx b/web/app/components/plugins/marketplace/search-box/index.tsx index 217007846c..803001e1c7 100644 --- a/web/app/components/plugins/marketplace/search-box/index.tsx +++ b/web/app/components/plugins/marketplace/search-box/index.tsx @@ -33,13 +33,6 @@ const SearchBox = ({ inputClassName, )} > - -
+
+
) } diff --git a/web/app/components/plugins/marketplace/search-box/tags-filter.tsx b/web/app/components/plugins/marketplace/search-box/tags-filter.tsx index edf50dc874..bae6491727 100644 --- a/web/app/components/plugins/marketplace/search-box/tags-filter.tsx +++ b/web/app/components/plugins/marketplace/search-box/tags-filter.tsx @@ -2,9 +2,7 @@ import { useState } from 'react' import { - RiArrowDownSLine, - RiCloseCircleFill, - RiFilter3Line, + RiPriceTag3Line, } from '@remixicon/react' import { PortalToFollowElem, @@ -57,47 +55,15 @@ const TagsFilter = ({ onClick={() => setOpen(v => !v)} >
-
- +
+
-
- { - !selectedTagsLength && t('pluginTags.allTags') - } - { - !!selectedTagsLength && tags.map(tag => tagsMap[tag].label).slice(0, 2).join(',') - } - { - selectedTagsLength > 2 && ( -
- +{selectedTagsLength - 2} -
- ) - } -
- { - !!selectedTagsLength && ( - onTagsChange([])} - /> - ) - } - { - !selectedTagsLength && ( - - ) - }
From 68202076e82fce151c35a1e5565c8e8d4f5df802 Mon Sep 17 00:00:00 2001 From: jZonG Date: Thu, 8 May 2025 14:07:09 +0800 Subject: [PATCH 11/58] add icon --- .../base/icons/assets/vender/other/mcp.svg | 8 +++ .../base/icons/src/vender/other/Mcp.json | 54 +++++++++++++++++++ .../base/icons/src/vender/other/Mcp.tsx | 20 +++++++ .../base/icons/src/vender/other/index.ts | 1 + 4 files changed, 83 insertions(+) create mode 100644 web/app/components/base/icons/assets/vender/other/mcp.svg create mode 100644 web/app/components/base/icons/src/vender/other/Mcp.json create mode 100644 web/app/components/base/icons/src/vender/other/Mcp.tsx diff --git a/web/app/components/base/icons/assets/vender/other/mcp.svg b/web/app/components/base/icons/assets/vender/other/mcp.svg new file mode 100644 index 0000000000..532ff90bd0 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/other/mcp.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/web/app/components/base/icons/src/vender/other/Mcp.json b/web/app/components/base/icons/src/vender/other/Mcp.json new file mode 100644 index 0000000000..aec139827d --- /dev/null +++ b/web/app/components/base/icons/src/vender/other/Mcp.json @@ -0,0 +1,54 @@ +{ + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "width": "24", + "height": "24", + "viewBox": "0 0 24 24", + "fill": "none", + "xmlns": "http://www.w3.org/2000/svg" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "mcp", + "opacity": "0.35" + }, + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "id": "Vector" + }, + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M13.8095 2.52976C14.4275 2.52976 15.0212 2.77094 15.4641 3.20199C15.6806 3.41274 15.8527 3.6647 15.9704 3.94301C16.088 4.22133 16.1487 4.52037 16.149 4.82252C16.1492 5.12467 16.089 5.42382 15.9719 5.70233C15.8547 5.98085 15.683 6.23309 15.4668 6.44421L8.79997 12.9827C8.72768 13.053 8.67022 13.1371 8.63098 13.23C8.59174 13.3229 8.57152 13.4227 8.57152 13.5236C8.57152 13.6244 8.59174 13.7242 8.63098 13.8171C8.67022 13.91 8.72768 13.9941 8.79997 14.0644C8.94767 14.2082 9.14566 14.2886 9.3518 14.2886C9.55793 14.2886 9.75593 14.2082 9.90363 14.0644L9.99346 13.9755L9.99529 13.9736L16.5696 7.52587C17.0127 7.09601 17.6059 6.85583 18.2233 6.85635C18.8407 6.85686 19.4335 7.09802 19.876 7.52862L19.9218 7.57353C20.1385 7.78451 20.3108 8.03678 20.4285 8.31545C20.5461 8.59412 20.6067 8.89354 20.6067 9.19602C20.6067 9.4985 20.5461 9.79792 20.4285 10.0766C20.3108 10.3553 20.1385 10.6075 19.9218 10.8185L11.9414 18.6449C11.7725 18.809 11.6384 19.0052 11.5467 19.222C11.4551 19.4388 11.4079 19.6718 11.4079 19.9072C11.4079 20.1425 11.4551 20.3755 11.5467 20.5923C11.6384 20.8092 11.7725 21.0054 11.9414 21.1694L13.5803 22.7763C13.728 22.9198 13.9258 23.0001 14.1317 23.0001C14.3376 23.0001 14.5354 22.9198 14.6831 22.7763C14.7554 22.706 14.8128 22.6219 14.8521 22.529C14.8913 22.4361 14.9115 22.3363 14.9115 22.2355C14.9115 22.1346 14.8913 22.0348 14.8521 21.9419C14.8128 21.849 14.7554 21.765 14.6831 21.6947L13.0441 20.0868C13.02 20.0634 13.0009 20.0354 12.9878 20.0045C12.9747 19.9735 12.968 19.9403 12.968 19.9067C12.968 19.8731 12.9747 19.8399 12.9878 19.8089C13.0009 19.778 13.02 19.75 13.0441 19.7266L21.0245 11.9011C21.386 11.5496 21.6733 11.1291 21.8695 10.6647C22.0657 10.2002 22.1668 9.70113 22.1668 9.19694C22.1668 8.69274 22.0657 8.19366 21.8695 7.72919C21.6733 7.26473 21.386 6.84431 21.0245 6.49279L20.9787 6.44696C20.5469 6.02546 20.024 5.70874 19.4504 5.5212C18.8769 5.33367 18.2679 5.28033 17.6705 5.3653C17.7558 4.7757 17.7002 4.17428 17.5084 3.61026C17.3166 3.04625 16.9939 2.53568 16.5668 2.12033C15.8287 1.40203 14.8394 1.00012 13.8095 1.00012C12.7796 1.00012 11.7903 1.40203 11.0522 2.12033L2.22845 10.7736C2.15615 10.8439 2.09869 10.928 2.05945 11.0209C2.02022 11.1138 2 11.2136 2 11.3144C2 11.4153 2.02022 11.5151 2.05945 11.608C2.09869 11.7009 2.15615 11.7849 2.22845 11.8552C2.3761 11.9988 2.5739 12.0791 2.77982 12.0791C2.98573 12.0791 3.18353 11.9988 3.33119 11.8552L12.1549 3.20199C12.5978 2.77094 13.1915 2.52976 13.8095 2.52976Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M14.5304 5.11801C14.4912 5.2109 14.4337 5.29499 14.3614 5.36529L7.83484 11.7654C7.61808 11.9764 7.44579 12.2286 7.32815 12.5073C7.21051 12.786 7.1499 13.0854 7.1499 13.3879C7.1499 13.6904 7.21051 13.9898 7.32815 14.2685C7.44579 14.5471 7.61808 14.7994 7.83484 15.0104C8.27774 15.4414 8.87138 15.6826 9.48941 15.6826C10.1074 15.6826 10.7011 15.4414 11.144 15.0104L17.6697 8.61026C17.8174 8.46647 18.0154 8.38601 18.2215 8.38601C18.4276 8.38601 18.6256 8.46647 18.7733 8.61026C18.8456 8.68056 18.9031 8.76465 18.9423 8.85754C18.9816 8.95043 19.0018 9.05025 19.0018 9.15109C19.0018 9.25193 18.9816 9.35174 18.9423 9.44464C18.9031 9.53753 18.8456 9.62161 18.7733 9.69192L12.2467 16.092C11.5085 16.8101 10.5193 17.2119 9.48941 17.2119C8.45955 17.2119 7.47032 16.8101 6.7321 16.092C6.37064 15.7405 6.08333 15.3201 5.88714 14.8556C5.69095 14.3912 5.58987 13.8921 5.58987 13.3879C5.58987 12.8837 5.69095 12.3846 5.88714 11.9201C6.08333 11.4557 6.37064 11.0353 6.7321 10.6837L13.2578 4.28363C13.4055 4.13984 13.6035 4.05938 13.8096 4.05938C14.0158 4.05938 14.2137 4.13984 14.3614 4.28363C14.4337 4.35394 14.4912 4.43802 14.5304 4.53091C14.5697 4.62381 14.5899 4.72362 14.5899 4.82446C14.5899 4.9253 14.5697 5.02512 14.5304 5.11801Z", + "fill": "currentColor" + }, + "children": [] + } + ] + } + ] + } + ] + }, + "name": "Mcp" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/other/Mcp.tsx b/web/app/components/base/icons/src/vender/other/Mcp.tsx new file mode 100644 index 0000000000..00ffa4a831 --- /dev/null +++ b/web/app/components/base/icons/src/vender/other/Mcp.tsx @@ -0,0 +1,20 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './Mcp.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconData } from '@/app/components/base/icons/IconBase' + +const Icon = ( + { + ref, + ...props + }: React.SVGProps & { + ref?: React.RefObject>; + }, +) => + +Icon.displayName = 'Mcp' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/other/index.ts b/web/app/components/base/icons/src/vender/other/index.ts index 8ddf5e7a86..7114e4fd40 100644 --- a/web/app/components/base/icons/src/vender/other/index.ts +++ b/web/app/components/base/icons/src/vender/other/index.ts @@ -1,5 +1,6 @@ export { default as AnthropicText } from './AnthropicText' export { default as Generator } from './Generator' export { default as Group } from './Group' +export { default as Mcp } from './Mcp' export { default as Openai } from './Openai' export { default as ReplayLine } from './ReplayLine' From bed412d1e2fd6d3b2ac5b7ddd6bcb9be4d6e6d33 Mon Sep 17 00:00:00 2001 From: jZonG Date: Thu, 8 May 2025 15:51:19 +0800 Subject: [PATCH 12/58] MCP create --- web/app/components/tools/mcp/create-card.tsx | 13 ++ web/app/components/tools/mcp/index.tsx | 4 +- web/app/components/tools/mcp/modal.tsx | 131 +++++++++++++++++++ web/i18n/en-US/tools.ts | 9 ++ web/i18n/zh-Hans/tools.ts | 9 ++ web/service/use-tools.ts | 27 ++++ 6 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 web/app/components/tools/mcp/modal.tsx diff --git a/web/app/components/tools/mcp/create-card.tsx b/web/app/components/tools/mcp/create-card.tsx index 51cd03ba61..2896372450 100644 --- a/web/app/components/tools/mcp/create-card.tsx +++ b/web/app/components/tools/mcp/create-card.tsx @@ -7,9 +7,11 @@ import { RiArrowRightUpLine, RiBookOpenLine, } from '@remixicon/react' +import MCPModal from './modal' import I18n from '@/context/i18n' import { getLanguage } from '@/i18n/language' import { useAppContext } from '@/context/app-context' +import { useCreateMCP } from '@/service/use-tools' type Props = { handleCreate: () => void @@ -21,6 +23,10 @@ const NewMCPCard = ({ handleCreate }: Props) => { const language = getLanguage(locale) const { isCurrentWorkspaceManager } = useAppContext() + const { mutate: createMCP } = useCreateMCP({ + onSuccess: handleCreate, + }) + const linkUrl = useMemo(() => { // TODO help link if (language.startsWith('zh_')) @@ -51,6 +57,13 @@ const NewMCPCard = ({ handleCreate }: Props) => {
)} + {showModal && ( + setShowModal(false)} + /> + )} ) } diff --git a/web/app/components/tools/mcp/index.tsx b/web/app/components/tools/mcp/index.tsx index 557d187562..2fe9c86fa6 100644 --- a/web/app/components/tools/mcp/index.tsx +++ b/web/app/components/tools/mcp/index.tsx @@ -4,7 +4,7 @@ import NewMCPCard from './create-card' import MCPCard from './provider-card' import MCPDetailPanel from './provider-detail' import { useAllMCPTools, useInvalidateAllMCPTools } from '@/service/use-tools' -import type { MCPProvider } from '@/app/components/tools/types' +import type { ToolWithProvider } from '@/app/components/workflow/types' import cn from '@/utils/classnames' type Props = { @@ -43,7 +43,7 @@ const MCPList = ({ }) }, [list, searchText]) - const [currentProvider, setCurrentProvider] = useState() + const [currentProvider, setCurrentProvider] = useState() return ( <> diff --git a/web/app/components/tools/mcp/modal.tsx b/web/app/components/tools/mcp/modal.tsx new file mode 100644 index 0000000000..d1a652de04 --- /dev/null +++ b/web/app/components/tools/mcp/modal.tsx @@ -0,0 +1,131 @@ +'use client' +import React, { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { RiCloseLine } from '@remixicon/react' +import AppIconPicker from '@/app/components/base/app-icon-picker' +import type { AppIconSelection } from '@/app/components/base/app-icon-picker' +import AppIcon from '@/app/components/base/app-icon' +import Modal from '@/app/components/base/modal' +import Button from '@/app/components/base/button' +import Input from '@/app/components/base/input' +import type { AppIconType } from '@/types/app' +import type { ToolWithProvider } from '@/app/components/workflow/types' +import { noop } from 'lodash-es' +import cn from '@/utils/classnames' + +export type DuplicateAppModalProps = { + data?: ToolWithProvider + show: boolean + onConfirm: (info: { + name: string + server_url: string + icon_type: AppIconType + icon: string + icon_background?: string | null + }) => void + onHide: () => void +} + +const DEFAULT_ICON = { type: 'emoji', icon: '🧿', background: '#EFF1F5' } +const extractFileId = (url: string) => { + const match = url.match(/files\/(.+?)\/file-preview/) + return match ? match[1] : null +} +const getIcon = (data?: ToolWithProvider) => { + if (!data) + return DEFAULT_ICON as AppIconSelection + if (typeof data.icon === 'string') + return { type: 'image', url: data.icon, fileId: extractFileId(data.icon) } as AppIconSelection + return data.icon as unknown as AppIconSelection +} + +const MCPModal = ({ + data, + show, + onConfirm, + onHide, +}: DuplicateAppModalProps) => { + const { t } = useTranslation() + + const [name, setName] = React.useState(data?.name || '') + const [appIcon, setAppIcon] = useState(getIcon(data)) + const [url, setUrl] = React.useState(data?.server_url || '') + const [showAppIconPicker, setShowAppIconPicker] = useState(false) + + const submit = async () => { + await onConfirm({ + name, + server_url: url, + icon_type: appIcon.type, + icon: appIcon.type === 'emoji' ? appIcon.icon : appIcon.fileId, + icon_background: appIcon.type === 'emoji' ? appIcon.background : undefined, + }) + onHide() + } + + return ( + <> + +
+ +
+
{t('tools.mcp.modal.title')}
+
+
+
+
+ {t('tools.mcp.modal.name')} +
+ setName(e.target.value)} + placeholder={t('tools.mcp.modal.namePlaceholder')} + /> +
+
+ { setShowAppIconPicker(true) }} + /> +
+
+
+
+ {t('tools.mcp.modal.serverUrl')} +
+ setUrl(e.target.value)} + placeholder={t('tools.mcp.modal.serverUrlPlaceholder')} + /> +
+
+
+ + +
+
+ {showAppIconPicker && { + setAppIcon(payload) + setShowAppIconPicker(false) + }} + onClose={() => { + setAppIcon(getIcon(data)) + setShowAppIconPicker(false) + }} + />} + + + ) +} + +export default MCPModal diff --git a/web/i18n/en-US/tools.ts b/web/i18n/en-US/tools.ts index 624819efae..1954f264f3 100644 --- a/web/i18n/en-US/tools.ts +++ b/web/i18n/en-US/tools.ts @@ -162,6 +162,15 @@ const translation = { updateTime: 'Updated', toolsCount: '{{count}} tools', noTools: 'No tools available', + modal: { + title: 'Add MCP Server (HTTP)', + name: 'Name & Icon', + namePlaceholder: 'Name your MCP server', + serverUrl: 'Server URL', + serverUrlPlaceholder: 'URL to server endpiont', + cancel: 'Cancel', + confirm: 'Add & Authorize', + }, }, } diff --git a/web/i18n/zh-Hans/tools.ts b/web/i18n/zh-Hans/tools.ts index 4f8dcc09e2..1c5b5a81e1 100644 --- a/web/i18n/zh-Hans/tools.ts +++ b/web/i18n/zh-Hans/tools.ts @@ -162,6 +162,15 @@ const translation = { updateTime: '更新于', toolsCount: '{{count}} 个工具', noTools: '没有可用的工具', + modal: { + title: '添加 MCP 服务 (HTTP)', + name: '名称和图标', + namePlaceholder: '命名你的 MCP 服务', + serverUrl: '服务端点 URL', + serverUrlPlaceholder: '服务端点的 URL', + cancel: '取消', + confirm: '添加并授权', + }, }, } diff --git a/web/service/use-tools.ts b/web/service/use-tools.ts index 9a61a0792d..78797a2cb3 100644 --- a/web/service/use-tools.ts +++ b/web/service/use-tools.ts @@ -4,6 +4,7 @@ import type { Tool, } from '@/app/components/tools/types' import type { ToolWithProvider } from '@/app/components/workflow/types' +import type { AppIconType } from '@/types/app' import { useInvalid } from './use-base' import { useMutation, @@ -78,6 +79,32 @@ export const useInvalidateAllMCPTools = () => { return useInvalid(useAllMCPToolsKey) } +export const useCreateMCP = ({ + onSuccess, +}: { + onSuccess?: () => void +}) => { + return useMutation({ + mutationKey: [NAME_SPACE, 'create-mcp'], + mutationFn: (payload: { + name: string + server_url: string + icon_type: AppIconType + icon: string + icon_background?: string | null + }) => { + console.log('payload', payload) + return Promise.resolve(payload) + // return post('/console/api/workspaces/current/tool-provider/mcp', { + // body: { + // ...payload, + // }, + // }) + }, + onSuccess, + }) +} + export const useBuiltinProviderInfo = (providerName: string) => { return useQuery({ queryKey: [NAME_SPACE, 'builtin-provider-info', providerName], From af311432eca2ba2e97da73261bbef8266b046552 Mon Sep 17 00:00:00 2001 From: jZonG Date: Thu, 8 May 2025 17:07:36 +0800 Subject: [PATCH 13/58] detail header --- .../components/tools/mcp/detail/content.tsx | 89 +++++++++++++++++++ .../mcp/{ => detail}/provider-detail.tsx | 14 +-- web/app/components/tools/mcp/index.tsx | 2 +- web/app/components/tools/mcp/mock.ts | 6 +- .../components/tools/mcp/provider-card.tsx | 4 +- 5 files changed, 103 insertions(+), 12 deletions(-) create mode 100644 web/app/components/tools/mcp/detail/content.tsx rename web/app/components/tools/mcp/{ => detail}/provider-detail.tsx (79%) diff --git a/web/app/components/tools/mcp/detail/content.tsx b/web/app/components/tools/mcp/detail/content.tsx new file mode 100644 index 0000000000..5ae9370b2f --- /dev/null +++ b/web/app/components/tools/mcp/detail/content.tsx @@ -0,0 +1,89 @@ +'use client' +import React from 'react' +import type { FC } from 'react' +import { useTranslation } from 'react-i18next' +import { useAppContext } from '@/context/app-context' +import { + RiCloseLine, +} from '@remixicon/react' +import type { ToolWithProvider } from '../../../workflow/types' +import Icon from '@/app/components/plugins/card/base/card-icon' +import ActionButton from '@/app/components/base/action-button' +import Button from '@/app/components/base/button' +// import Toast from '@/app/components/base/toast' +import Indicator from '@/app/components/header/indicator' +import cn from '@/utils/classnames' + +type Props = { + detail?: ToolWithProvider + onUpdate: () => void + onHide: () => void +} + +const MCPDetailContent: FC = ({ + detail, + // onUpdate, + onHide, +}) => { + const { t } = useTranslation() + const { isCurrentWorkspaceManager } = useAppContext() + + if (!detail) + return null + + return ( + <> +
+
+
+ +
+
+
+
{detail.name}
+
+
{detail.server_url}
+
+
+ {/* */} + + + +
+
+
+ {detail.is_team_authorization && ( + + )} + {detail.is_team_authorization && ( + + )} +
+
+
+ TOOL list +
+ + ) +} + +export default MCPDetailContent diff --git a/web/app/components/tools/mcp/provider-detail.tsx b/web/app/components/tools/mcp/detail/provider-detail.tsx similarity index 79% rename from web/app/components/tools/mcp/provider-detail.tsx rename to web/app/components/tools/mcp/detail/provider-detail.tsx index 849736b7aa..effb2363c9 100644 --- a/web/app/components/tools/mcp/provider-detail.tsx +++ b/web/app/components/tools/mcp/detail/provider-detail.tsx @@ -2,8 +2,9 @@ import React from 'react' import type { FC } from 'react' import Drawer from '@/app/components/base/drawer' +import MCPDetailContent from './content' +import type { ToolWithProvider } from '../../../workflow/types' import cn from '@/utils/classnames' -import type { ToolWithProvider } from '../../workflow/types' type Props = { detail?: ToolWithProvider @@ -36,12 +37,11 @@ const MCPDetailPanel: FC = ({ panelClassName={cn('mb-2 mr-2 mt-[64px] !w-[420px] !max-w-[420px] justify-start rounded-2xl border-[0.5px] border-components-panel-border !bg-components-panel-bg !p-0 shadow-xl')} > {detail && ( - <> -
HEADER
-
- TOOL list -
- + )} ) diff --git a/web/app/components/tools/mcp/index.tsx b/web/app/components/tools/mcp/index.tsx index 2fe9c86fa6..be8421e2f0 100644 --- a/web/app/components/tools/mcp/index.tsx +++ b/web/app/components/tools/mcp/index.tsx @@ -2,7 +2,7 @@ import { useMemo, useState } from 'react' import NewMCPCard from './create-card' import MCPCard from './provider-card' -import MCPDetailPanel from './provider-detail' +import MCPDetailPanel from './detail/provider-detail' import { useAllMCPTools, useInvalidateAllMCPTools } from '@/service/use-tools' import type { ToolWithProvider } from '@/app/components/workflow/types' import cn from '@/utils/classnames' diff --git a/web/app/components/tools/mcp/mock.ts b/web/app/components/tools/mcp/mock.ts index 341061c44e..f271f67ed3 100644 --- a/web/app/components/tools/mcp/mock.ts +++ b/web/app/components/tools/mcp/mock.ts @@ -110,7 +110,7 @@ export const listData = [ id: 'fdjklajfkljadslf111', author: 'KVOJJJin', name: 'GOGOGO', - icon: 'https://cloud.dify.dev/console/api/workspaces/694cc430-fa36-4458-86a0-4a98c09c4684/model-providers/langgenius/openai/openai/icon_large/en_US', + icon: 'https://cloud.dify.dev/console/api/workspaces/694cc430-fa36-4458-86a0-4a98c09c4684/model-providers/langgenius/openai/openai/icon_small/en_US', server_url: 'https://mcp.composio.dev/notion/****/abc', type: 'mcp', is_team_authorization: true, @@ -125,7 +125,7 @@ export const listData = [ id: 'fdjklajfkljadslf222', author: 'KVOJJJin', name: 'GOGOGO2', - icon: 'https://cloud.dify.dev/console/api/workspaces/694cc430-fa36-4458-86a0-4a98c09c4684/model-providers/langgenius/openai/openai/icon_large/en_US', + icon: 'https://cloud.dify.dev/console/api/workspaces/694cc430-fa36-4458-86a0-4a98c09c4684/model-providers/langgenius/openai/openai/icon_small/en_US', server_url: 'https://mcp.composio.dev/notion/****/abc', type: 'mcp', is_team_authorization: false, @@ -140,7 +140,7 @@ export const listData = [ id: 'fdjklajfkljadslf333', author: 'KVOJJJin', name: 'GOGOGO3', - icon: 'https://cloud.dify.dev/console/api/workspaces/694cc430-fa36-4458-86a0-4a98c09c4684/model-providers/langgenius/openai/openai/icon_large/en_US', + icon: 'https://cloud.dify.dev/console/api/workspaces/694cc430-fa36-4458-86a0-4a98c09c4684/model-providers/langgenius/openai/openai/icon_small/en_US', server_url: 'https://mcp.composio.dev/notion/****/abc', type: 'mcp', is_team_authorization: true, diff --git a/web/app/components/tools/mcp/provider-card.tsx b/web/app/components/tools/mcp/provider-card.tsx index 53d387eb70..0ca5e3e205 100644 --- a/web/app/components/tools/mcp/provider-card.tsx +++ b/web/app/components/tools/mcp/provider-card.tsx @@ -38,7 +38,9 @@ const MCPCard = ({ )} >
- +
+ +
{data.name}
From 1bb70f9af93c05813d36001a39202c44af55810d Mon Sep 17 00:00:00 2001 From: jZonG Date: Thu, 8 May 2025 19:36:12 +0800 Subject: [PATCH 14/58] edit & delete --- .../components/tools/mcp/detail/content.tsx | 93 ++++++++++++++-- .../tools/mcp/detail/operation-dropdown.tsx | 88 +++++++++++++++ web/app/components/tools/mcp/index.tsx | 1 + web/app/components/tools/mcp/modal.tsx | 2 +- .../components/tools/mcp/provider-card.tsx | 100 ++++++++++++++++-- web/i18n/en-US/tools.ts | 7 ++ web/i18n/zh-Hans/tools.ts | 7 ++ web/service/use-tools.ts | 57 ++++++++-- 8 files changed, 325 insertions(+), 30 deletions(-) create mode 100644 web/app/components/tools/mcp/detail/operation-dropdown.tsx diff --git a/web/app/components/tools/mcp/detail/content.tsx b/web/app/components/tools/mcp/detail/content.tsx index 5ae9370b2f..7efe997af4 100644 --- a/web/app/components/tools/mcp/detail/content.tsx +++ b/web/app/components/tools/mcp/detail/content.tsx @@ -1,6 +1,7 @@ 'use client' -import React from 'react' +import React, { useCallback } from 'react' import type { FC } from 'react' +import { useBoolean } from 'ahooks' import { useTranslation } from 'react-i18next' import { useAppContext } from '@/context/app-context' import { @@ -10,24 +11,74 @@ import type { ToolWithProvider } from '../../../workflow/types' import Icon from '@/app/components/plugins/card/base/card-icon' import ActionButton from '@/app/components/base/action-button' import Button from '@/app/components/base/button' -// import Toast from '@/app/components/base/toast' +import Confirm from '@/app/components/base/confirm' import Indicator from '@/app/components/header/indicator' +import MCPModal from '../modal' +import OperationDropdown from './operation-dropdown' +import { useDeleteMCP, useUpdateMCP } from '@/service/use-tools' import cn from '@/utils/classnames' type Props = { detail?: ToolWithProvider - onUpdate: () => void + onUpdate: (isDelete?: boolean) => void onHide: () => void } const MCPDetailContent: FC = ({ detail, - // onUpdate, + onUpdate, onHide, }) => { const { t } = useTranslation() const { isCurrentWorkspaceManager } = useAppContext() + const { mutate: updateMCP } = useUpdateMCP({ + onSuccess: onUpdate, + }) + const { mutate: deleteMCP } = useDeleteMCP({ + onSuccess: onUpdate, + }) + + const [isShowUpdateModal, { + setTrue: showUpdateModal, + setFalse: hideUpdateModal, + }] = useBoolean(false) + + const [isShowDeleteConfirm, { + setTrue: showDeleteConfirm, + setFalse: hideDeleteConfirm, + }] = useBoolean(false) + + const [deleting, { + setTrue: showDeleting, + setFalse: hideDeleting, + }] = useBoolean(false) + + const handleUpdate = useCallback(async (data: any) => { + if (!detail) + return + const res = await updateMCP({ + ...data, + provider_id: detail.id, + }) + if ((res as any)?.result === 'success') { + hideUpdateModal() + onUpdate() + } + }, [detail, updateMCP, hideUpdateModal, onUpdate]) + + const handleDelete = useCallback(async () => { + if (!detail) + return + showDeleting() + const res = await deleteMCP(detail.id) + hideDeleting() + if ((res as any)?.result === 'success') { + hideDeleteConfirm() + onUpdate(true) + } + }, [detail, showDeleting, hideDeleting, hideDeleteConfirm, onUpdate]) + if (!detail) return null @@ -45,13 +96,10 @@ const MCPDetailContent: FC = ({
{detail.server_url}
- {/* */} + /> @@ -69,7 +117,7 @@ const MCPDetailContent: FC = ({ {t('tools.auth.authorized')} )} - {detail.is_team_authorization && ( + {!detail.is_team_authorization && (
+ } + onCancel={hideDeleteConfirm} + onConfirm={handleDelete} + isLoading={deleting} + isDisabled={deleting} + /> + )} ) } diff --git a/web/app/components/tools/mcp/detail/operation-dropdown.tsx b/web/app/components/tools/mcp/detail/operation-dropdown.tsx new file mode 100644 index 0000000000..d2cbc8825d --- /dev/null +++ b/web/app/components/tools/mcp/detail/operation-dropdown.tsx @@ -0,0 +1,88 @@ +'use client' +import type { FC } from 'react' +import React, { useCallback, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { + RiDeleteBinLine, + RiEditLine, + RiMoreFill, +} from '@remixicon/react' +import ActionButton from '@/app/components/base/action-button' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import cn from '@/utils/classnames' + +type Props = { + inCard?: boolean + onOpenChange?: (open: boolean) => void + onEdit: () => void + onRemove: () => void +} + +const OperationDropdown: FC = ({ + inCard, + onOpenChange, + onEdit, + onRemove, +}) => { + const { t } = useTranslation() + const [open, doSetOpen] = useState(false) + const openRef = useRef(open) + const setOpen = useCallback((v: boolean) => { + doSetOpen(v) + openRef.current = v + onOpenChange?.(v) + }, [doSetOpen]) + + const handleTrigger = useCallback(() => { + setOpen(!openRef.current) + }, [setOpen]) + + return ( + + +
+ + + +
+
+ +
+
{ + onEdit() + handleTrigger() + }} + > + +
{t('tools.mcp.operation.edit')}
+
+
{ + onRemove() + handleTrigger() + }} + > + +
{t('tools.mcp.operation.remove')}
+
+
+
+
+ ) +} +export default React.memo(OperationDropdown) diff --git a/web/app/components/tools/mcp/index.tsx b/web/app/components/tools/mcp/index.tsx index be8421e2f0..08a9f177be 100644 --- a/web/app/components/tools/mcp/index.tsx +++ b/web/app/components/tools/mcp/index.tsx @@ -60,6 +60,7 @@ const MCPList = ({ data={provider} currentProvider={currentProvider} handleSelect={setCurrentProvider} + onUpdate={() => invalidateMCPList()} /> ))} {!list.length && renderDefaultCard()} diff --git a/web/app/components/tools/mcp/modal.tsx b/web/app/components/tools/mcp/modal.tsx index d1a652de04..8edae8a2a5 100644 --- a/web/app/components/tools/mcp/modal.tsx +++ b/web/app/components/tools/mcp/modal.tsx @@ -109,7 +109,7 @@ const MCPModal = ({
- +
diff --git a/web/app/components/tools/mcp/provider-card.tsx b/web/app/components/tools/mcp/provider-card.tsx index 0ca5e3e205..72e6b56241 100644 --- a/web/app/components/tools/mcp/provider-card.tsx +++ b/web/app/components/tools/mcp/provider-card.tsx @@ -1,43 +1,90 @@ 'use client' -// import { useMemo, useState } from 'react' +import { useCallback, useState } from 'react' +import { useBoolean } from 'ahooks' import { useTranslation } from 'react-i18next' -// import { useContext } from 'use-context-selector' -// import I18n from '@/context/i18n' -// import { getLanguage } from '@/i18n/language' -// import { useAppContext } from '@/context/app-context' +import { useAppContext } from '@/context/app-context' import { RiHammerFill } from '@remixicon/react' import Indicator from '@/app/components/header/indicator' import Icon from '@/app/components/plugins/card/base/card-icon' import { useFormatTimeFromNow } from './hooks' import type { ToolWithProvider } from '../../workflow/types' +import Confirm from '@/app/components/base/confirm' +import MCPModal from './modal' +import OperationDropdown from './detail/operation-dropdown' +import { useDeleteMCP, useUpdateMCP } from '@/service/use-tools' import cn from '@/utils/classnames' type Props = { currentProvider?: ToolWithProvider data: ToolWithProvider handleSelect: (provider: ToolWithProvider) => void + onUpdate: () => void } const MCPCard = ({ currentProvider, data, + onUpdate, handleSelect, }: Props) => { const { t } = useTranslation() const { formatTimeFromNow } = useFormatTimeFromNow() - // const { locale } = useContext(I18n) - // const language = getLanguage(locale) - // const { isCurrentWorkspaceManager } = useAppContext() + const { isCurrentWorkspaceManager } = useAppContext() + + const { mutate: updateMCP } = useUpdateMCP({ + onSuccess: onUpdate, + }) + const { mutate: deleteMCP } = useDeleteMCP({ + onSuccess: onUpdate, + }) + + const [isOperationShow, setIsOperationShow] = useState(false) + + const [isShowUpdateModal, { + setTrue: showUpdateModal, + setFalse: hideUpdateModal, + }] = useBoolean(false) + + const [isShowDeleteConfirm, { + setTrue: showDeleteConfirm, + setFalse: hideDeleteConfirm, + }] = useBoolean(false) + + const [deleting, { + setTrue: showDeleting, + setFalse: hideDeleting, + }] = useBoolean(false) + + const handleUpdate = useCallback(async (form: any) => { + const res = await updateMCP({ + ...form, + provider_id: data.id, + }) + if ((res as any)?.result === 'success') { + hideUpdateModal() + onUpdate() + } + }, [data, updateMCP, hideUpdateModal, onUpdate]) + + const handleDelete = useCallback(async () => { + showDeleting() + const res = await deleteMCP(data.id) + hideDeleting() + if ((res as any)?.result === 'success') { + hideDeleteConfirm() + onUpdate() + } + }, [data, showDeleting, hideDeleting, hideDeleteConfirm, onUpdate]) return (
handleSelect(data)} className={cn( - 'relative flex cursor-pointer flex-col rounded-xl border-[1.5px] border-transparent bg-components-card-bg shadow-xs hover:bg-components-card-bg-alt hover:shadow-md', + 'group relative flex cursor-pointer flex-col rounded-xl border-[1.5px] border-transparent bg-components-card-bg shadow-xs hover:bg-components-card-bg-alt hover:shadow-md', currentProvider?.id === data.id && 'border-components-option-card-option-selected-border bg-components-card-bg-alt', )} > -
+
@@ -68,6 +115,39 @@ const MCPCard = ({
)}
+ {isCurrentWorkspaceManager && ( + + )} + {isShowUpdateModal && ( + + )} + {isShowDeleteConfirm && ( + + {t('tools.mcp.deleteConfirmTitle', { mcp: data.name })} +
+ } + onCancel={hideDeleteConfirm} + onConfirm={handleDelete} + isLoading={deleting} + isDisabled={deleting} + /> + )}
) } diff --git a/web/i18n/en-US/tools.ts b/web/i18n/en-US/tools.ts index 1954f264f3..e4f965f041 100644 --- a/web/i18n/en-US/tools.ts +++ b/web/i18n/en-US/tools.ts @@ -169,8 +169,15 @@ const translation = { serverUrl: 'Server URL', serverUrlPlaceholder: 'URL to server endpiont', cancel: 'Cancel', + save: 'Save', confirm: 'Add & Authorize', }, + delete: 'Remove MCP Server', + deleteConfirmTitle: 'Would you like to remove {{mcp}}?', + operation: { + edit: 'Edit', + remove: 'Remove', + }, }, } diff --git a/web/i18n/zh-Hans/tools.ts b/web/i18n/zh-Hans/tools.ts index 1c5b5a81e1..990e445fa1 100644 --- a/web/i18n/zh-Hans/tools.ts +++ b/web/i18n/zh-Hans/tools.ts @@ -169,8 +169,15 @@ const translation = { serverUrl: '服务端点 URL', serverUrlPlaceholder: '服务端点的 URL', cancel: '取消', + save: '保存', confirm: '添加并授权', }, + delete: '删除 MCP 服务', + deleteConfirmTitle: '你想要删除 {{mcp}} 吗?', + operation: { + edit: '修改', + remove: '删除', + }, }, } diff --git a/web/service/use-tools.ts b/web/service/use-tools.ts index 78797a2cb3..2d062819a6 100644 --- a/web/service/use-tools.ts +++ b/web/service/use-tools.ts @@ -1,4 +1,4 @@ -import { get, post } from './base' +import { del, get, post, put } from './base' import type { Collection, Tool, @@ -93,13 +93,54 @@ export const useCreateMCP = ({ icon: string icon_background?: string | null }) => { - console.log('payload', payload) - return Promise.resolve(payload) - // return post('/console/api/workspaces/current/tool-provider/mcp', { - // body: { - // ...payload, - // }, - // }) + return post('workspaces/current/tool-provider/mcp', { + body: { + ...payload, + }, + }) + }, + onSuccess, + }) +} + +export const useUpdateMCP = ({ + onSuccess, +}: { + onSuccess?: () => void +}) => { + return useMutation({ + mutationKey: [NAME_SPACE, 'update-mcp'], + mutationFn: (payload: { + name: string + server_url: string + icon_type: AppIconType + icon: string + icon_background?: string | null + provider_id: string + }) => { + return put('workspaces/current/tool-provider/mcp', { + body: { + ...payload, + }, + }) + }, + onSuccess, + }) +} + +export const useDeleteMCP = ({ + onSuccess, +}: { + onSuccess?: () => void +}) => { + return useMutation({ + mutationKey: [NAME_SPACE, 'delete-mcp'], + mutationFn: (id: string) => { + return del('/console/api/workspaces/current/tool-provider/mcp', { + body: { + provider_id: id, + }, + }) }, onSuccess, }) From 27c27223e11e27cd777cf9107d8a7279158e239d Mon Sep 17 00:00:00 2001 From: jZonG Date: Thu, 8 May 2025 20:39:58 +0800 Subject: [PATCH 15/58] tool empty list --- .../components/tools/mcp/detail/content.tsx | 36 +++++++++++++++++-- web/i18n/en-US/tools.ts | 10 ++++++ web/i18n/zh-Hans/tools.ts | 10 ++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/web/app/components/tools/mcp/detail/content.tsx b/web/app/components/tools/mcp/detail/content.tsx index 7efe997af4..0b6e74cd4a 100644 --- a/web/app/components/tools/mcp/detail/content.tsx +++ b/web/app/components/tools/mcp/detail/content.tsx @@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next' import { useAppContext } from '@/context/app-context' import { RiCloseLine, + RiLoader2Line, } from '@remixicon/react' import type { ToolWithProvider } from '../../../workflow/types' import Icon from '@/app/components/plugins/card/base/card-icon' @@ -123,12 +124,43 @@ const MCPDetailContent: FC = ({ className='w-full' // onClick={() => setShowSettingAuth(true)} disabled={!isCurrentWorkspaceManager} - >{t('tools.auth.unauthorized')} + > + {t('tools.mcp.authorize')} + + )} + {/* TODO */} + {deleting && ( + )}
- TOOL list + {!detail.is_team_authorization && ( +
+
{t('tools.mcp.authorizingRequired')}
+ {deleting &&
{t('tools.mcp.authorizing')}
} +
{t('tools.mcp.authorizeTip')}
+
+ )} + {detail.is_team_authorization && ( +
+
{t('tools.mcp.toolsEmpty')}
+ +
+ )}
{isShowUpdateModal && ( Date: Fri, 9 May 2025 10:49:08 +0800 Subject: [PATCH 16/58] feat: add button place and view type control --- .../plugins/marketplace/search-box/index.tsx | 17 ++++++++++++++ .../edit-custom-collection-modal/modal.tsx | 1 + .../workflow/block-selector/all-tools.tsx | 23 ++++--------------- .../workflow/block-selector/tool-picker.tsx | 8 ++++--- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/web/app/components/plugins/marketplace/search-box/index.tsx b/web/app/components/plugins/marketplace/search-box/index.tsx index 803001e1c7..6c8e449ca6 100644 --- a/web/app/components/plugins/marketplace/search-box/index.tsx +++ b/web/app/components/plugins/marketplace/search-box/index.tsx @@ -3,6 +3,7 @@ import { RiCloseLine } from '@remixicon/react' import TagsFilter from './tags-filter' import ActionButton from '@/app/components/base/action-button' import cn from '@/utils/classnames' +import { RiAddLine } from '@remixicon/react' type SearchBoxProps = { search: string @@ -13,6 +14,9 @@ type SearchBoxProps = { size?: 'small' | 'large' placeholder?: string locale?: string + supportAddCustomTool?: boolean + onShowAddCustomCollectionModal?: () => void + onAddedCustomTool?: () => void } const SearchBox = ({ search, @@ -23,6 +27,8 @@ const SearchBox = ({ size = 'small', placeholder = '', locale, + supportAddCustomTool, + onShowAddCustomCollectionModal, }: SearchBoxProps) => { return (
+ {supportAddCustomTool && ( +
+
+ + + +
+ )}
) } diff --git a/web/app/components/tools/edit-custom-collection-modal/modal.tsx b/web/app/components/tools/edit-custom-collection-modal/modal.tsx index 190c72790e..ce7ba8a735 100644 --- a/web/app/components/tools/edit-custom-collection-modal/modal.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/modal.tsx @@ -184,6 +184,7 @@ const EditCustomCollectionModal: FC = ({ onClose={onHide} closable className='!h-[calc(100vh-16px)] !max-w-[630px] !p-0' + wrapperClassName='z-[1000]' >
diff --git a/web/app/components/workflow/block-selector/all-tools.tsx b/web/app/components/workflow/block-selector/all-tools.tsx index 7b3f62d5a7..b37824f5b2 100644 --- a/web/app/components/workflow/block-selector/all-tools.tsx +++ b/web/app/components/workflow/block-selector/all-tools.tsx @@ -17,8 +17,6 @@ import cn from '@/utils/classnames' import { useGetLanguage } from '@/context/i18n' import type { ListRef } from '@/app/components/workflow/block-selector/market-place-plugin/list' import PluginList, { type ListProps } from '@/app/components/workflow/block-selector/market-place-plugin/list' -import ActionButton from '../../base/action-button' -import { RiAddLine } from '@remixicon/react' import { PluginType } from '../../plugins/types' import { useMarketplacePlugins } from '../../plugins/marketplace/hooks' import { useSelector as useAppContextSelector } from '@/context/app-context' @@ -33,9 +31,6 @@ type AllToolsProps = { workflowTools: ToolWithProvider[] mcpTools: ToolWithProvider[] onSelect: OnSelectBlock - supportAddCustomTool?: boolean - onAddedCustomTool?: () => void - onShowAddCustomCollectionModal?: () => void selectedTools?: ToolValue[] } @@ -51,8 +46,6 @@ const AllTools = ({ workflowTools, customTools, mcpTools = [], - supportAddCustomTool, - onShowAddCustomCollectionModal, selectedTools, }: AllToolsProps) => { const language = useGetLanguage() @@ -107,6 +100,7 @@ const AllTools = ({ const pluginRef = useRef(null) const wrapElemRef = useRef(null) + const isSupportGroupView = [ToolTypeEnum.All, ToolTypeEnum.BuiltIn].includes(activeTab) return (
@@ -128,17 +122,8 @@ const AllTools = ({ )) }
- - {supportAddCustomTool && ( -
-
- - - -
+ {isSupportGroupView && ( + )}
diff --git a/web/app/components/workflow/block-selector/tool-picker.tsx b/web/app/components/workflow/block-selector/tool-picker.tsx index 3d2ac1cb1f..8a79117086 100644 --- a/web/app/components/workflow/block-selector/tool-picker.tsx +++ b/web/app/components/workflow/block-selector/tool-picker.tsx @@ -152,6 +152,10 @@ const ToolPicker: FC = ({ onTagsChange={setTags} size='small' placeholder={t('plugin.searchTools')!} + supportAddCustomTool={supportAddCustomTool} + onAddedCustomTool={handleAddedCustomTool} + onShowAddCustomCollectionModal={showEditCustomCollectionModal} + />
= ({ customTools={customToolList || []} workflowTools={workflowToolList || []} mcpTools={mcpTools || []} - supportAddCustomTool={supportAddCustomTool} - onAddedCustomTool={handleAddedCustomTool} - onShowAddCustomCollectionModal={showEditCustomCollectionModal} + selectedTools={selectedTools} />
From 5fd352c56285d0714e5ae74acd812c1d576603f1 Mon Sep 17 00:00:00 2001 From: jZonG Date: Fri, 9 May 2025 14:06:55 +0800 Subject: [PATCH 17/58] get & update api of tools --- web/service/use-tools.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/web/service/use-tools.ts b/web/service/use-tools.ts index 2d062819a6..35efaff781 100644 --- a/web/service/use-tools.ts +++ b/web/service/use-tools.ts @@ -146,6 +146,20 @@ export const useDeleteMCP = ({ }) } +export const useMCPTools = (providerID: string) => { + return useQuery({ + queryKey: [NAME_SPACE, 'get-MCP-provider-tool', providerID], + queryFn: () => get(`/workspaces/current/tool-provider/mcp/tools/${providerID}`), + }) +} + +export const useUpdateMCPTools = (providerID: string) => { + return useQuery({ + queryKey: [NAME_SPACE, 'update-MCP-provider-tool', providerID], + queryFn: () => get(`/workspaces/current/tool-provider/mcp/update/${providerID}`), + }) +} + export const useBuiltinProviderInfo = (providerName: string) => { return useQuery({ queryKey: [NAME_SPACE, 'builtin-provider-info', providerName], From 32f87db951a57b9c63b2f0dcd1f109223e663335 Mon Sep 17 00:00:00 2001 From: jZonG Date: Fri, 9 May 2025 14:38:37 +0800 Subject: [PATCH 18/58] list loading --- .../components/tools/mcp/detail/content.tsx | 61 ++++++++++++++----- .../tools/mcp/detail/list-loading.tsx | 37 +++++++++++ web/i18n/en-US/tools.ts | 4 +- 3 files changed, 84 insertions(+), 18 deletions(-) create mode 100644 web/app/components/tools/mcp/detail/list-loading.tsx diff --git a/web/app/components/tools/mcp/detail/content.tsx b/web/app/components/tools/mcp/detail/content.tsx index 0b6e74cd4a..f34a7c6ceb 100644 --- a/web/app/components/tools/mcp/detail/content.tsx +++ b/web/app/components/tools/mcp/detail/content.tsx @@ -1,5 +1,5 @@ 'use client' -import React, { useCallback } from 'react' +import React, { useCallback, useState } from 'react' import type { FC } from 'react' import { useBoolean } from 'ahooks' import { useTranslation } from 'react-i18next' @@ -7,6 +7,7 @@ import { useAppContext } from '@/context/app-context' import { RiCloseLine, RiLoader2Line, + RiLoopLeftLine, } from '@remixicon/react' import type { ToolWithProvider } from '../../../workflow/types' import Icon from '@/app/components/plugins/card/base/card-icon' @@ -16,6 +17,7 @@ import Confirm from '@/app/components/base/confirm' import Indicator from '@/app/components/header/indicator' import MCPModal from '../modal' import OperationDropdown from './operation-dropdown' +import ListLoading from './list-loading' import { useDeleteMCP, useUpdateMCP } from '@/service/use-tools' import cn from '@/utils/classnames' @@ -80,6 +82,8 @@ const MCPDetailContent: FC = ({ } }, [detail, showDeleting, hideDeleting, hideDeleteConfirm, onUpdate]) + const [loading, setLoading] = useState(true) + if (!detail) return null @@ -142,23 +146,48 @@ const MCPDetailContent: FC = ({ )}
-
- {!detail.is_team_authorization && ( -
-
{t('tools.mcp.authorizingRequired')}
- {deleting &&
{t('tools.mcp.authorizing')}
} -
{t('tools.mcp.authorizeTip')}
+
+
+
+
{t('tools.mcp.gettingTools')}
+ {/*
{t('tools.mcp.updateTools')}
*/} +
+
+ + {/* */} +
+
+ {loading && ( +
+
)} - {detail.is_team_authorization && ( -
-
{t('tools.mcp.toolsEmpty')}
- + {!loading && ( +
+ {!detail.is_team_authorization && ( +
+
{t('tools.mcp.authorizingRequired')}
+ {deleting &&
{t('tools.mcp.authorizing')}
} +
{t('tools.mcp.authorizeTip')}
+
+ )} + {detail.is_team_authorization && ( +
+
{t('tools.mcp.toolsEmpty')}
+ +
+ )}
)}
diff --git a/web/app/components/tools/mcp/detail/list-loading.tsx b/web/app/components/tools/mcp/detail/list-loading.tsx new file mode 100644 index 0000000000..babf050d8b --- /dev/null +++ b/web/app/components/tools/mcp/detail/list-loading.tsx @@ -0,0 +1,37 @@ +'use client' +import React from 'react' +import cn from '@/utils/classnames' + +const ListLoading = () => { + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ) +} + +export default ListLoading diff --git a/web/i18n/en-US/tools.ts b/web/i18n/en-US/tools.ts index 7f03c507f3..b144033132 100644 --- a/web/i18n/en-US/tools.ts +++ b/web/i18n/en-US/tools.ts @@ -184,8 +184,8 @@ const translation = { authorizeTip: 'After authorization, tools will be displayed here.', update: 'Update', updating: 'Updating', - gettingTools: 'Getting Tools', - updateTools: 'Updating Tools', + gettingTools: 'Getting Tools...', + updateTools: 'Updating Tools...', toolsEmpty: 'Tools not loaded', getTools: 'Get tools', }, From 9556866d38a7c315633352216fd7b4d1187af744 Mon Sep 17 00:00:00 2001 From: jZonG Date: Fri, 9 May 2025 15:38:24 +0800 Subject: [PATCH 19/58] get list & update list --- .../components/tools/mcp/detail/content.tsx | 114 ++++++++++++------ web/service/use-tools.ts | 15 ++- 2 files changed, 86 insertions(+), 43 deletions(-) diff --git a/web/app/components/tools/mcp/detail/content.tsx b/web/app/components/tools/mcp/detail/content.tsx index f34a7c6ceb..4d664c2a2e 100644 --- a/web/app/components/tools/mcp/detail/content.tsx +++ b/web/app/components/tools/mcp/detail/content.tsx @@ -18,11 +18,17 @@ import Indicator from '@/app/components/header/indicator' import MCPModal from '../modal' import OperationDropdown from './operation-dropdown' import ListLoading from './list-loading' -import { useDeleteMCP, useUpdateMCP } from '@/service/use-tools' +import { + useDeleteMCP, + useInvalidateMCPTools, + useMCPTools, + useUpdateMCP, + useUpdateMCPTools, +} from '@/service/use-tools' import cn from '@/utils/classnames' type Props = { - detail?: ToolWithProvider + detail: ToolWithProvider onUpdate: (isDelete?: boolean) => void onHide: () => void } @@ -35,6 +41,17 @@ const MCPDetailContent: FC = ({ const { t } = useTranslation() const { isCurrentWorkspaceManager } = useAppContext() + const { data: toolList = [], isPending: isGettingTools } = useMCPTools(detail.is_team_authorization ? detail.id : '') + const invalidateMCPTools = useInvalidateMCPTools() + const { mutateAsync, isPending: isUpdating } = useUpdateMCPTools(detail.id) + + const handleUpdateTools = useCallback(async () => { + if (!detail) + return + await mutateAsync() + invalidateMCPTools(detail.id) + }, [detail, mutateAsync]) + const { mutate: updateMCP } = useUpdateMCP({ onSuccess: onUpdate, }) @@ -82,7 +99,7 @@ const MCPDetailContent: FC = ({ } }, [detail, showDeleting, hideDeleting, hideDeleteConfirm, onUpdate]) - const [loading, setLoading] = useState(true) + const [loading, setLoading] = useState(false) if (!detail) return null @@ -147,47 +164,64 @@ const MCPDetailContent: FC = ({
-
-
-
{t('tools.mcp.gettingTools')}
- {/*
{t('tools.mcp.updateTools')}
*/} -
-
- - {/* */} -
-
- {loading && ( -
- + {detail.is_team_authorization && isGettingTools && ( + <> +
+
+
{t('tools.mcp.gettingTools')}
+
+
+
+
+ +
+ + )} + {!isGettingTools && !toolList.length && ( +
+
{t('tools.mcp.toolsEmpty')}
+
)} - {!loading && ( -
- {!detail.is_team_authorization && ( -
-
{t('tools.mcp.authorizingRequired')}
- {deleting &&
{t('tools.mcp.authorizing')}
} -
{t('tools.mcp.authorizeTip')}
+ {!isGettingTools && toolList.length > 0 && ( + <> +
+
+
{t('tools.mcp.gettingTools')}
- )} - {detail.is_team_authorization && ( -
-
{t('tools.mcp.toolsEmpty')}
- +
+
- )} +
+
+ {/* list */} +
+ + )} + {isUpdating && ( + <> +
+
+
{t('tools.mcp.updateTools')}
+
+
+
+
+ +
+ + )} + {!detail.is_team_authorization && ( +
+ {!loading &&
{t('tools.mcp.authorizingRequired')}
} + {loading &&
{t('tools.mcp.authorizing')}
} +
{t('tools.mcp.authorizeTip')}
)}
diff --git a/web/service/use-tools.ts b/web/service/use-tools.ts index 35efaff781..83ce2afeab 100644 --- a/web/service/use-tools.ts +++ b/web/service/use-tools.ts @@ -148,15 +148,24 @@ export const useDeleteMCP = ({ export const useMCPTools = (providerID: string) => { return useQuery({ + enabled: !!providerID, queryKey: [NAME_SPACE, 'get-MCP-provider-tool', providerID], queryFn: () => get(`/workspaces/current/tool-provider/mcp/tools/${providerID}`), }) } +export const useInvalidateMCPTools = () => { + const queryClient = useQueryClient() + return (providerID: string) => { + queryClient.invalidateQueries( + { + queryKey: [NAME_SPACE, 'get-MCP-provider-tool', providerID], + }) + } +} export const useUpdateMCPTools = (providerID: string) => { - return useQuery({ - queryKey: [NAME_SPACE, 'update-MCP-provider-tool', providerID], - queryFn: () => get(`/workspaces/current/tool-provider/mcp/update/${providerID}`), + return useMutation({ + mutationFn: () => get(`/workspaces/current/tool-provider/mcp/update/${providerID}`), }) } From d3d8822b6fc86bca79a2059d273f8f523118bc35 Mon Sep 17 00:00:00 2001 From: jZonG Date: Fri, 9 May 2025 16:20:40 +0800 Subject: [PATCH 20/58] tool list --- .../components/tools/mcp/detail/content.tsx | 32 +++++++-------- .../components/tools/mcp/detail/tool-item.tsx | 41 +++++++++++++++++++ web/i18n/en-US/tools.ts | 2 + web/i18n/zh-Hans/tools.ts | 2 + 4 files changed, 59 insertions(+), 18 deletions(-) create mode 100644 web/app/components/tools/mcp/detail/tool-item.tsx diff --git a/web/app/components/tools/mcp/detail/content.tsx b/web/app/components/tools/mcp/detail/content.tsx index 4d664c2a2e..d9f8554754 100644 --- a/web/app/components/tools/mcp/detail/content.tsx +++ b/web/app/components/tools/mcp/detail/content.tsx @@ -18,6 +18,7 @@ import Indicator from '@/app/components/header/indicator' import MCPModal from '../modal' import OperationDropdown from './operation-dropdown' import ListLoading from './list-loading' +import ToolItem from './tool-item' import { useDeleteMCP, useInvalidateMCPTools, @@ -164,11 +165,12 @@ const MCPDetailContent: FC = ({
- {detail.is_team_authorization && isGettingTools && ( + {((detail.is_team_authorization && isGettingTools) || isUpdating) && ( <>
-
{t('tools.mcp.gettingTools')}
+ {!isUpdating &&
{t('tools.mcp.gettingTools')}
} + {isUpdating &&
{t('tools.mcp.updateTools')}
}
@@ -190,7 +192,8 @@ const MCPDetailContent: FC = ({ <>
-
{t('tools.mcp.gettingTools')}
+ {toolList.length > 1 &&
{t('tools.mcp.toolsNum', { count: toolList.length })}
} + {toolList.length === 1 &&
{t('tools.mcp.onlyTool')}
}
-
- {/* list */} -
- - )} - {isUpdating && ( - <> -
-
-
{t('tools.mcp.updateTools')}
-
-
-
-
- +
+ {toolList.map(tool => ( + + ))}
)} + {!detail.is_team_authorization && (
{!loading &&
{t('tools.mcp.authorizingRequired')}
} diff --git a/web/app/components/tools/mcp/detail/tool-item.tsx b/web/app/components/tools/mcp/detail/tool-item.tsx new file mode 100644 index 0000000000..eea6c09f03 --- /dev/null +++ b/web/app/components/tools/mcp/detail/tool-item.tsx @@ -0,0 +1,41 @@ +'use client' +import React from 'react' +import { useContext } from 'use-context-selector' +import type { Tool } from '@/app/components/tools/types' +import I18n from '@/context/i18n' +import { getLanguage } from '@/i18n/language' +import Tooltip from '@/app/components/base/tooltip' +import cn from '@/utils/classnames' + +type Props = { + tool: Tool +} + +const MCPToolItem = ({ + tool, +}: Props) => { + const { locale } = useContext(I18n) + const language = getLanguage(locale) + + return ( + +
{tool.label[language]}
+
{tool.description[language]}
+
+ )} + > +
+
{tool.label[language]}
+
{tool.description[language]}
+
+ + ) +} +export default MCPToolItem diff --git a/web/i18n/en-US/tools.ts b/web/i18n/en-US/tools.ts index b144033132..8c4306e86f 100644 --- a/web/i18n/en-US/tools.ts +++ b/web/i18n/en-US/tools.ts @@ -188,6 +188,8 @@ const translation = { updateTools: 'Updating Tools...', toolsEmpty: 'Tools not loaded', getTools: 'Get tools', + toolsNum: '{{count}} tools included', + onlyTool: '1 tool included', }, } diff --git a/web/i18n/zh-Hans/tools.ts b/web/i18n/zh-Hans/tools.ts index 889da1b2f9..2c746766ab 100644 --- a/web/i18n/zh-Hans/tools.ts +++ b/web/i18n/zh-Hans/tools.ts @@ -188,6 +188,8 @@ const translation = { updateTools: '更新工具中...', toolsEmpty: '工具未加载', getTools: '获取工具', + toolsNum: '包含 {{count}} 个工具', + onlyTool: '包含 1 个工具', }, } From 1d99895304ec1d4a078c1c385dcfd8daa7feae94 Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 9 May 2025 16:45:44 +0800 Subject: [PATCH 21/58] feat: search input to new --- .../plugins/marketplace/search-box/index.tsx | 79 ++++++++++--------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/web/app/components/plugins/marketplace/search-box/index.tsx b/web/app/components/plugins/marketplace/search-box/index.tsx index 6c8e449ca6..0c09660195 100644 --- a/web/app/components/plugins/marketplace/search-box/index.tsx +++ b/web/app/components/plugins/marketplace/search-box/index.tsx @@ -1,5 +1,5 @@ 'use client' -import { RiCloseLine } from '@remixicon/react' +import { RiCloseLine, RiSearchLine } from '@remixicon/react' import TagsFilter from './tags-filter' import ActionButton from '@/app/components/base/action-button' import cn from '@/utils/classnames' @@ -32,48 +32,51 @@ const SearchBox = ({ }: SearchBoxProps) => { return (
-
-
- { - onSearchChange(e.target.value) - }} - placeholder={placeholder} - /> - { - search && ( -
- onSearchChange('')}> - - -
- ) - } +
+
+
+ + { + onSearchChange(e.target.value) + }} + placeholder={placeholder} + /> + { + search && ( +
+ onSearchChange('')}> + + +
+ ) + } +
+
+
-
- {supportAddCustomTool && ( -
-
+
From 9c3817a8e86573d14cf231a2b89c481445b85619 Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 9 May 2025 16:56:34 +0800 Subject: [PATCH 22/58] fix: handle added tools style --- .../block-selector/tool/action-item.tsx | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/web/app/components/workflow/block-selector/tool/action-item.tsx b/web/app/components/workflow/block-selector/tool/action-item.tsx index dc9b9b9114..9a52e820f7 100644 --- a/web/app/components/workflow/block-selector/tool/action-item.tsx +++ b/web/app/components/workflow/block-selector/tool/action-item.tsx @@ -10,8 +10,6 @@ import { useGetLanguage } from '@/context/i18n' import BlockIcon from '../../block-icon' import cn from '@/utils/classnames' import { useTranslation } from 'react-i18next' -import { RiCheckLine } from '@remixicon/react' -import Badge from '@/app/components/base/badge' type Props = { provider: ToolWithProvider @@ -74,15 +72,12 @@ const ToolItem: FC = ({ }) }} > -
{payload.label[language]}
- {disabled && - -
{t('tools.addToolModal.added')}
-
- } +
+ {payload.label[language]} +
+ {disabled && ( +
{t('tools.addToolModal.added')}
+ )}
) From aaa5309ba0ef6bd872983decbd643ee8e7419b93 Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 9 May 2025 17:30:02 +0800 Subject: [PATCH 23/58] feat: has added all tools --- .../workflow/block-selector/tool/tool.tsx | 46 ++++++++++++++++--- web/i18n/en-US/workflow.ts | 2 + web/i18n/zh-Hans/workflow.ts | 2 + 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/web/app/components/workflow/block-selector/tool/tool.tsx b/web/app/components/workflow/block-selector/tool/tool.tsx index f135b5bf4e..8c13fd38e0 100644 --- a/web/app/components/workflow/block-selector/tool/tool.tsx +++ b/web/app/components/workflow/block-selector/tool/tool.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React, { useEffect, useMemo } from 'react' +import React, { useEffect, useMemo, useRef } from 'react' import cn from '@/utils/classnames' import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react' import { useGetLanguage } from '@/context/i18n' @@ -13,6 +13,7 @@ import { ViewType } from '../view-type-select' import ActonItem from './action-item' import BlockIcon from '../../block-icon' import { useTranslation } from 'react-i18next' +import { useHover } from 'ahooks' type Props = { className?: string @@ -39,10 +40,39 @@ const Tool: FC = ({ const actions = payload.tools const hasAction = true // Now always support actions const [isFold, setFold] = React.useState(true) + const ref = useRef(null) + const isHovering = useHover(ref) const getIsDisabled = (tool: ToolType) => { if (!selectedTools || !selectedTools.length) return false return selectedTools.some(selectedTool => selectedTool.provider_name === payload.name && selectedTool.tool_name === tool.name) } + + const totalToolsNum = actions.length + const selectedToolsNum = actions.filter(action => getIsDisabled(action)).length + const isAllSelected = selectedToolsNum === totalToolsNum + + const selectedInfo = useMemo(() => { + if (isHovering && !isAllSelected) { + return ( + + {t('workflow.tabs.addAll')} + + ) + } + + if (selectedToolsNum === 0) + return <> + + return ( + + {isAllSelected + ? t('workflow.tabs.allAdded') + : `${selectedToolsNum} / ${totalToolsNum}` + } + + ) + }, [isAllSelected, isHovering, selectedToolsNum, t, totalToolsNum]) + useEffect(() => { if (hasSearchText && isFold) { setFold(false) @@ -72,6 +102,7 @@ const Tool: FC = ({
= ({ type={BlockEnum.Tool} toolIcon={payload.icon} /> -
{payload.label[language]}
+
+ {payload.label[language]} + {isFlatView && ( + {groupName} + )} +
-
- {isFlatView && ( -
{groupName}
- )} +
+ {selectedInfo} {hasAction && ( )} diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index ab0c6a5879..0bd2d85586 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -229,6 +229,8 @@ const translation = { 'utilities': 'Utilities', 'noResult': 'No match found', 'agent': 'Agent Strategy', + 'allAdded': 'All added', + 'addAll': 'Add all', }, blocks: { 'start': 'Start', diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index 9f8d2c6964..77e0fb6412 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -230,6 +230,8 @@ const translation = { 'utilities': '工具', 'noResult': '未找到匹配项', 'agent': 'Agent 策略', + 'allAdded': '已添加全部', + 'addAll': '添加全部', }, blocks: { 'start': '开始', From 4ee5156afb2c9f6d8b73f53365052ed6ae21c875 Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 9 May 2025 18:21:59 +0800 Subject: [PATCH 24/58] feat: can choose all in agent node --- .../multiple-tool-selector/index.tsx | 15 ++++++++++ .../tool-selector/index.tsx | 23 +++++++------- .../workflow/block-selector/all-tools.tsx | 6 +++- .../workflow/block-selector/tool-picker.tsx | 7 +++++ .../tool/tool-list-flat-view/list.tsx | 3 ++ .../tool/tool-list-tree-view/item.tsx | 3 ++ .../tool/tool-list-tree-view/list.tsx | 3 ++ .../workflow/block-selector/tool/tool.tsx | 30 +++++++++++++++++-- .../workflow/block-selector/tools.tsx | 4 +++ 9 files changed, 81 insertions(+), 13 deletions(-) diff --git a/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.tsx index f243d30aff..e2b6b06fd6 100644 --- a/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.tsx @@ -66,6 +66,19 @@ const MultipleToolSelector = ({ setOpen(false) } + const handleAddMultiple = (val: ToolValue[]) => { + const newValue = [...value, ...val] + // deduplication + const deduplication = newValue.reduce((acc, cur) => { + if (!acc.find(item => item.provider_name === cur.provider_name && item.tool_name === cur.tool_name)) + acc.push(cur) + return acc + }, [] as ToolValue[]) + // update value + onChange(deduplication) + setOpen(false) + } + // delete tool const handleDelete = (index: number) => { const newValue = [...value] @@ -134,6 +147,7 @@ const MultipleToolSelector = ({ value={undefined} selectedTools={value} onSelect={handleAdd} + onSelectMultiple={handleAddMultiple} controlledState={open} onControlledStateChange={setOpen} trigger={ @@ -156,6 +170,7 @@ const MultipleToolSelector = ({ value={item} selectedTools={value} onSelect={item => handleConfigure(item, index)} + onSelectMultiple={handleAddMultiple} onDelete={() => handleDelete(index)} supportEnableSwitch /> diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx index 4bbcb58bfb..954263b966 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx @@ -55,14 +55,8 @@ type Props = { scope?: string value?: ToolValue selectedTools?: ToolValue[] - onSelect: (tool: { - provider_name: string - tool_name: string - tool_label: string - settings?: Record - parameters?: Record - extra?: Record - }) => void + onSelect: (tool: ToolValue) => void + onSelectMultiple: (tool: ToolValue[]) => void onDelete?: () => void supportEnableSwitch?: boolean supportAddCustomTool?: boolean @@ -82,6 +76,7 @@ const ToolSelector: FC = ({ placement = 'left', offset = 4, onSelect, + onSelectMultiple, onDelete, scope, supportEnableSwitch, @@ -119,10 +114,10 @@ const ToolSelector: FC = ({ }, [value, buildInTools, customTools, workflowTools, mcpTools]) const [isShowChooseTool, setIsShowChooseTool] = useState(false) - const handleSelectTool = (tool: ToolDefaultValue) => { + const getToolValue = (tool: ToolDefaultValue) => { const settingValues = generateFormValue(tool.params, toolParametersToFormSchemas(tool.paramSchemas.filter(param => param.form !== 'llm') as any)) const paramValues = generateFormValue(tool.params, toolParametersToFormSchemas(tool.paramSchemas.filter(param => param.form === 'llm') as any), true) - const toolValue = { + return { provider_name: tool.provider_id, type: tool.provider_type, tool_name: tool.tool_name, @@ -136,9 +131,16 @@ const ToolSelector: FC = ({ }, schemas: tool.paramSchemas, } + } + const handleSelectTool = (tool: ToolDefaultValue) => { + const toolValue = getToolValue(tool) onSelect(toolValue) // setIsShowChooseTool(false) } + const handleSelectMultipleTool = (tool: ToolDefaultValue[]) => { + const toolValues = tool.map(item => getToolValue(item)) + onSelectMultiple(toolValues) + } const handleDescriptionChange = (e: React.ChangeEvent) => { onSelect({ @@ -300,6 +302,7 @@ const ToolSelector: FC = ({ disabled={false} supportAddCustomTool onSelect={handleSelectTool} + onSelectMultiple={handleSelectMultipleTool} scope={scope} selectedTools={selectedTools} /> diff --git a/web/app/components/workflow/block-selector/all-tools.tsx b/web/app/components/workflow/block-selector/all-tools.tsx index b37824f5b2..fd17c1f187 100644 --- a/web/app/components/workflow/block-selector/all-tools.tsx +++ b/web/app/components/workflow/block-selector/all-tools.tsx @@ -5,10 +5,11 @@ import { useState, } from 'react' import type { + BlockEnum, OnSelectBlock, ToolWithProvider, } from '../types' -import type { ToolValue } from './types' +import type { ToolDefaultValue, ToolValue } from './types' import { ToolTypeEnum } from './types' import Tools from './tools' import { useToolTabs } from './hooks' @@ -31,6 +32,7 @@ type AllToolsProps = { workflowTools: ToolWithProvider[] mcpTools: ToolWithProvider[] onSelect: OnSelectBlock + onSelectMultiple: (type: BlockEnum, tools: ToolDefaultValue[]) => void selectedTools?: ToolValue[] } @@ -42,6 +44,7 @@ const AllTools = ({ searchText, tags = DEFAULT_TAGS, onSelect, + onSelectMultiple, buildInTools, workflowTools, customTools, @@ -136,6 +139,7 @@ const AllTools = ({ showWorkflowEmpty={activeTab === ToolTypeEnum.Workflow} tools={tools} onSelect={onSelect} + onSelectMultiple={onSelectMultiple} viewType={isSupportGroupView ? activeView : ViewType.flat} hasSearchText={!!searchText} selectedTools={selectedTools} diff --git a/web/app/components/workflow/block-selector/tool-picker.tsx b/web/app/components/workflow/block-selector/tool-picker.tsx index 8a79117086..bd24e204ce 100644 --- a/web/app/components/workflow/block-selector/tool-picker.tsx +++ b/web/app/components/workflow/block-selector/tool-picker.tsx @@ -35,6 +35,7 @@ type Props = { isShow: boolean onShowChange: (isShow: boolean) => void onSelect: (tool: ToolDefaultValue) => void + onSelectMultiple: (tools: ToolDefaultValue[]) => void supportAddCustomTool?: boolean scope?: string selectedTools?: ToolValue[] @@ -48,6 +49,7 @@ const ToolPicker: FC = ({ isShow, onShowChange, onSelect, + onSelectMultiple, supportAddCustomTool, scope = 'all', selectedTools, @@ -103,6 +105,10 @@ const ToolPicker: FC = ({ onSelect(tool!) } + const handleSelectMultiple = (_type: BlockEnum, tools: ToolDefaultValue[]) => { + onSelectMultiple(tools) + } + const [isShowEditCollectionToolModal, { setFalse: hideEditCustomCollectionModal, setTrue: showEditCustomCollectionModal, @@ -164,6 +170,7 @@ const ToolPicker: FC = ({ tags={tags} searchText={searchText} onSelect={handleSelect} + onSelectMultiple={handleSelectMultiple} buildInTools={builtinToolList || []} customTools={customToolList || []} workflowTools={workflowToolList || []} diff --git a/web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx b/web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx index ef671ca1f8..91f2ea4677 100644 --- a/web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx +++ b/web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx @@ -13,6 +13,7 @@ type Props = { isShowLetterIndex: boolean hasSearchText: boolean onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void + onSelectMultiple: (type: BlockEnum, tools: ToolDefaultValue[]) => void letters: string[] toolRefs: any selectedTools?: ToolValue[] @@ -24,6 +25,7 @@ const ToolViewFlatView: FC = ({ isShowLetterIndex, hasSearchText, onSelect, + onSelectMultiple, toolRefs, selectedTools, }) => { @@ -53,6 +55,7 @@ const ToolViewFlatView: FC = ({ isShowLetterIndex={isShowLetterIndex} hasSearchText={hasSearchText} onSelect={onSelect} + onSelectMultiple={onSelectMultiple} selectedTools={selectedTools} />
diff --git a/web/app/components/workflow/block-selector/tool/tool-list-tree-view/item.tsx b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/item.tsx index d6c567f8e2..b09a0604ff 100644 --- a/web/app/components/workflow/block-selector/tool/tool-list-tree-view/item.tsx +++ b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/item.tsx @@ -12,6 +12,7 @@ type Props = { toolList: ToolWithProvider[] hasSearchText: boolean onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void + onSelectMultiple: (type: BlockEnum, tools: ToolValue[]) => void selectedTools?: ToolValue[] } @@ -20,6 +21,7 @@ const Item: FC = ({ toolList, hasSearchText, onSelect, + onSelectMultiple, selectedTools, }) => { return ( @@ -36,6 +38,7 @@ const Item: FC = ({ isShowLetterIndex={false} hasSearchText={hasSearchText} onSelect={onSelect} + onSelectMultiple={onSelectMultiple} selectedTools={selectedTools} /> ))} diff --git a/web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx index f3f98279c8..c471709823 100644 --- a/web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx +++ b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx @@ -12,6 +12,7 @@ type Props = { payload: Record hasSearchText: boolean onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void + onSelectMultiple: (type: BlockEnum, tools: ToolValue[]) => void selectedTools?: ToolValue[] } @@ -19,6 +20,7 @@ const ToolListTreeView: FC = ({ payload, hasSearchText, onSelect, + onSelectMultiple, selectedTools, }) => { const { t } = useTranslation() @@ -46,6 +48,7 @@ const ToolListTreeView: FC = ({ toolList={payload[groupName]} hasSearchText={hasSearchText} onSelect={onSelect} + onSelectMultiple={onSelectMultiple} selectedTools={selectedTools} /> ))} diff --git a/web/app/components/workflow/block-selector/tool/tool.tsx b/web/app/components/workflow/block-selector/tool/tool.tsx index 8c13fd38e0..40e9dda431 100644 --- a/web/app/components/workflow/block-selector/tool/tool.tsx +++ b/web/app/components/workflow/block-selector/tool/tool.tsx @@ -22,6 +22,7 @@ type Props = { isShowLetterIndex: boolean hasSearchText: boolean onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void + onSelectMultiple: (type: BlockEnum, tools: ToolDefaultValue[]) => void selectedTools?: ToolValue[] } @@ -32,6 +33,7 @@ const Tool: FC = ({ isShowLetterIndex, hasSearchText, onSelect, + onSelectMultiple, selectedTools, }) => { const { t } = useTranslation() @@ -44,7 +46,7 @@ const Tool: FC = ({ const isHovering = useHover(ref) const getIsDisabled = (tool: ToolType) => { if (!selectedTools || !selectedTools.length) return false - return selectedTools.some(selectedTool => selectedTool.provider_name === payload.name && selectedTool.tool_name === tool.name) + return selectedTools.some(selectedTool => (selectedTool.provider_name === payload.name || selectedTool.provider_name === payload.id) && selectedTool.tool_name === tool.name) } const totalToolsNum = actions.length @@ -54,7 +56,31 @@ const Tool: FC = ({ const selectedInfo = useMemo(() => { if (isHovering && !isAllSelected) { return ( - + { + onSelectMultiple(BlockEnum.Tool, actions.filter(action => !getIsDisabled(action)).map((tool) => { + const params: Record = {} + if (tool.parameters) { + tool.parameters.forEach((item) => { + params[item.name] = '' + }) + } + return { + provider_id: payload.id, + provider_type: payload.type, + provider_name: payload.name, + tool_name: tool.name, + tool_label: tool.label[language], + tool_description: tool.description[language], + title: tool.label[language], + is_team_authorization: payload.is_team_authorization, + output_schema: tool.output_schema, + paramSchemas: tool.parameters, + params, + } + })) + }} + > {t('workflow.tabs.addAll')} ) diff --git a/web/app/components/workflow/block-selector/tools.tsx b/web/app/components/workflow/block-selector/tools.tsx index 2562501524..53e74117d2 100644 --- a/web/app/components/workflow/block-selector/tools.tsx +++ b/web/app/components/workflow/block-selector/tools.tsx @@ -17,6 +17,7 @@ import classNames from '@/utils/classnames' type ToolsProps = { showWorkflowEmpty: boolean onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void + onSelectMultiple: (type: BlockEnum, tools: ToolDefaultValue[]) => void tools: ToolWithProvider[] viewType: ViewType hasSearchText: boolean @@ -27,6 +28,7 @@ type ToolsProps = { const Blocks = ({ showWorkflowEmpty, onSelect, + onSelectMultiple, tools, viewType, hasSearchText, @@ -107,6 +109,7 @@ const Blocks = ({ isShowLetterIndex={isShowLetterIndex} hasSearchText={hasSearchText} onSelect={onSelect} + onSelectMultiple={onSelectMultiple} selectedTools={selectedTools} /> ) : ( @@ -114,6 +117,7 @@ const Blocks = ({ payload={treeViewToolsData} hasSearchText={hasSearchText} onSelect={onSelect} + onSelectMultiple={onSelectMultiple} selectedTools={selectedTools} /> ) From 9c21294f406fde6ec4b4a072e3578220f40ba8dc Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 9 May 2025 18:27:03 +0800 Subject: [PATCH 25/58] chore: agent app tools added all --- .../config/agent/agent-tools/index.tsx | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) 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 8f08d26bd2..fdb0bc3b49 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 @@ -101,19 +101,28 @@ const AgentTools: FC = () => { } const [isDeleting, setIsDeleting] = useState(-1) - + const getToolValue = (tool: ToolDefaultValue) => { + return { + provider_id: tool.provider_id, + provider_type: tool.provider_type as CollectionType, + provider_name: tool.provider_name, + tool_name: tool.tool_name, + tool_label: tool.tool_label, + tool_parameters: tool.params, + notAuthor: !tool.is_team_authorization, + enabled: true, + } + } const handleSelectTool = (tool: ToolDefaultValue) => { const newModelConfig = produce(modelConfig, (draft) => { - draft.agentConfig.tools.push({ - provider_id: tool.provider_id, - provider_type: tool.provider_type as CollectionType, - provider_name: tool.provider_name, - tool_name: tool.tool_name, - tool_label: tool.tool_label, - tool_parameters: tool.params, - notAuthor: !tool.is_team_authorization, - enabled: true, - }) + draft.agentConfig.tools.push(getToolValue(tool)) + }) + setModelConfig(newModelConfig) + } + + const handleSelectMultipleTool = (tool: ToolDefaultValue[]) => { + const newModelConfig = produce(modelConfig, (draft) => { + draft.agentConfig.tools.push(...tool.map(getToolValue)) }) setModelConfig(newModelConfig) } @@ -148,6 +157,7 @@ const AgentTools: FC = () => { disabled={false} supportAddCustomTool onSelect={handleSelectTool} + onSelectMultiple={handleSelectMultipleTool} selectedTools={tools as unknown as ToolValue[]} /> From c427aafb94cd04f8c9eff1f97b2063cab82d49f1 Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 9 May 2025 18:47:44 +0800 Subject: [PATCH 26/58] fix: handle add workflow --- .../workflow/block-selector/tool/tool.tsx | 65 ++++++++++++------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/web/app/components/workflow/block-selector/tool/tool.tsx b/web/app/components/workflow/block-selector/tool/tool.tsx index 40e9dda431..eeed5fcf3b 100644 --- a/web/app/components/workflow/block-selector/tool/tool.tsx +++ b/web/app/components/workflow/block-selector/tool/tool.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React, { useEffect, useMemo, useRef } from 'react' +import React, { useCallback, useEffect, useMemo, useRef } from 'react' import cn from '@/utils/classnames' import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react' import { useGetLanguage } from '@/context/i18n' @@ -39,20 +39,30 @@ const Tool: FC = ({ const { t } = useTranslation() const language = useGetLanguage() const isFlatView = viewType === ViewType.flat + const notShowProvider = payload.type === CollectionType.workflow const actions = payload.tools - const hasAction = true // Now always support actions + const hasAction = !notShowProvider const [isFold, setFold] = React.useState(true) const ref = useRef(null) const isHovering = useHover(ref) - const getIsDisabled = (tool: ToolType) => { + const getIsDisabled = useCallback((tool: ToolType) => { if (!selectedTools || !selectedTools.length) return false return selectedTools.some(selectedTool => (selectedTool.provider_name === payload.name || selectedTool.provider_name === payload.id) && selectedTool.tool_name === tool.name) - } + }, [payload.id, payload.name, selectedTools]) const totalToolsNum = actions.length const selectedToolsNum = actions.filter(action => getIsDisabled(action)).length const isAllSelected = selectedToolsNum === totalToolsNum + const notShowProviderSelectInfo = useMemo(() => { + if (isAllSelected) { + return ( + + {t('tools.addToolModal.added')} + + ) + } + }, [isAllSelected, t]) const selectedInfo = useMemo(() => { if (isHovering && !isAllSelected) { return ( @@ -97,7 +107,7 @@ const Tool: FC = ({ } ) - }, [isAllSelected, isHovering, selectedToolsNum, t, totalToolsNum]) + }, [actions, getIsDisabled, isAllSelected, isHovering, language, onSelectMultiple, payload.id, payload.is_team_authorization, payload.name, payload.type, selectedToolsNum, t, totalToolsNum]) useEffect(() => { if (hasSearchText && isFold) { @@ -134,24 +144,31 @@ const Tool: FC = ({
{ - if (hasAction) + if (hasAction) { setFold(!isFold) + return + } - // Now always support actions - // if (payload.parameters) { - // payload.parameters.forEach((item) => { - // params[item.name] = '' - // }) - // } - // onSelect(BlockEnum.Tool, { - // provider_id: payload.id, - // provider_type: payload.type, - // provider_name: payload.name, - // tool_name: payload.name, - // tool_label: payload.label[language], - // title: payload.label[language], - // params: {}, - // }) + const tool = actions[0] + const params: Record = {} + if (tool.parameters) { + tool.parameters.forEach((item) => { + params[item.name] = '' + }) + } + onSelect(BlockEnum.Tool, { + provider_id: payload.id, + provider_type: payload.type, + provider_name: payload.name, + tool_name: tool.name, + tool_label: tool.label[language], + tool_description: tool.description[language], + title: tool.label[language], + is_team_authorization: payload.is_team_authorization, + output_schema: tool.output_schema, + paramSchemas: tool.parameters, + params, + }) }} >
@@ -161,7 +178,7 @@ const Tool: FC = ({ toolIcon={payload.icon} />
- {payload.label[language]} + {notShowProvider ? actions[0]?.label[language] : payload.label[language]} {isFlatView && ( {groupName} )} @@ -169,14 +186,14 @@ const Tool: FC = ({
- {selectedInfo} + {notShowProvider ? notShowProviderSelectInfo : selectedInfo} {hasAction && ( )}
- {hasAction && !isFold && ( + {!notShowProvider && hasAction && !isFold && ( actions.map(action => ( Date: Mon, 12 May 2025 11:17:36 +0800 Subject: [PATCH 27/58] configure error of provider card --- web/app/components/tools/mcp/provider-card.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/app/components/tools/mcp/provider-card.tsx b/web/app/components/tools/mcp/provider-card.tsx index 72e6b56241..228b7b19c9 100644 --- a/web/app/components/tools/mcp/provider-card.tsx +++ b/web/app/components/tools/mcp/provider-card.tsx @@ -74,7 +74,7 @@ const MCPCard = ({ hideDeleteConfirm() onUpdate() } - }, [data, showDeleting, hideDeleting, hideDeleteConfirm, onUpdate]) + }, [showDeleting, deleteMCP, data.id, hideDeleting, hideDeleteConfirm, onUpdate]) return (
{data.server_url}
{data.is_team_authorization && } - {!data.is_team_authorization && ( + {(!data.is_team_authorization || !data.tools.length) && (
{t('tools.mcp.noConfigured')} From 626f2524e2a383cb78782efdfb3482a0ab8932b6 Mon Sep 17 00:00:00 2001 From: Joel Date: Mon, 12 May 2025 13:37:40 +0800 Subject: [PATCH 28/58] fix: select tool can select all --- web/app/components/workflow/block-selector/all-tools.tsx | 5 ++++- web/app/components/workflow/block-selector/tabs.tsx | 1 + .../block-selector/tool/tool-list-flat-view/list.tsx | 5 ++++- .../block-selector/tool/tool-list-tree-view/item.tsx | 5 ++++- .../block-selector/tool/tool-list-tree-view/list.tsx | 5 ++++- web/app/components/workflow/block-selector/tool/tool.tsx | 8 +++++--- web/app/components/workflow/block-selector/tools.tsx | 6 +++++- 7 files changed, 27 insertions(+), 8 deletions(-) diff --git a/web/app/components/workflow/block-selector/all-tools.tsx b/web/app/components/workflow/block-selector/all-tools.tsx index fd17c1f187..3e5bc8af43 100644 --- a/web/app/components/workflow/block-selector/all-tools.tsx +++ b/web/app/components/workflow/block-selector/all-tools.tsx @@ -32,7 +32,8 @@ type AllToolsProps = { workflowTools: ToolWithProvider[] mcpTools: ToolWithProvider[] onSelect: OnSelectBlock - onSelectMultiple: (type: BlockEnum, tools: ToolDefaultValue[]) => void + canNotSelectMultiple?: boolean + onSelectMultiple?: (type: BlockEnum, tools: ToolDefaultValue[]) => void selectedTools?: ToolValue[] } @@ -44,6 +45,7 @@ const AllTools = ({ searchText, tags = DEFAULT_TAGS, onSelect, + canNotSelectMultiple, onSelectMultiple, buildInTools, workflowTools, @@ -139,6 +141,7 @@ const AllTools = ({ showWorkflowEmpty={activeTab === ToolTypeEnum.Workflow} tools={tools} onSelect={onSelect} + canNotSelectMultiple={canNotSelectMultiple} onSelectMultiple={onSelectMultiple} viewType={isSupportGroupView ? activeView : ViewType.flat} hasSearchText={!!searchText} diff --git a/web/app/components/workflow/block-selector/tabs.tsx b/web/app/components/workflow/block-selector/tabs.tsx index 457315b5b8..acebc4039b 100644 --- a/web/app/components/workflow/block-selector/tabs.tsx +++ b/web/app/components/workflow/block-selector/tabs.tsx @@ -73,6 +73,7 @@ const Tabs: FC = ({ searchText={searchText} onSelect={onSelect} tags={tags} + canNotSelectMultiple buildInTools={buildInTools || []} customTools={customTools || []} workflowTools={workflowTools || []} diff --git a/web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx b/web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx index 91f2ea4677..abb28dead0 100644 --- a/web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx +++ b/web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx @@ -13,7 +13,8 @@ type Props = { isShowLetterIndex: boolean hasSearchText: boolean onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void - onSelectMultiple: (type: BlockEnum, tools: ToolDefaultValue[]) => void + canNotSelectMultiple?: boolean + onSelectMultiple?: (type: BlockEnum, tools: ToolDefaultValue[]) => void letters: string[] toolRefs: any selectedTools?: ToolValue[] @@ -25,6 +26,7 @@ const ToolViewFlatView: FC = ({ isShowLetterIndex, hasSearchText, onSelect, + canNotSelectMultiple, onSelectMultiple, toolRefs, selectedTools, @@ -55,6 +57,7 @@ const ToolViewFlatView: FC = ({ isShowLetterIndex={isShowLetterIndex} hasSearchText={hasSearchText} onSelect={onSelect} + canNotSelectMultiple={canNotSelectMultiple} onSelectMultiple={onSelectMultiple} selectedTools={selectedTools} /> diff --git a/web/app/components/workflow/block-selector/tool/tool-list-tree-view/item.tsx b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/item.tsx index b09a0604ff..acec666822 100644 --- a/web/app/components/workflow/block-selector/tool/tool-list-tree-view/item.tsx +++ b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/item.tsx @@ -12,7 +12,8 @@ type Props = { toolList: ToolWithProvider[] hasSearchText: boolean onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void - onSelectMultiple: (type: BlockEnum, tools: ToolValue[]) => void + canNotSelectMultiple?: boolean + onSelectMultiple?: (type: BlockEnum, tools: ToolDefaultValue[]) => void selectedTools?: ToolValue[] } @@ -21,6 +22,7 @@ const Item: FC = ({ toolList, hasSearchText, onSelect, + canNotSelectMultiple, onSelectMultiple, selectedTools, }) => { @@ -38,6 +40,7 @@ const Item: FC = ({ isShowLetterIndex={false} hasSearchText={hasSearchText} onSelect={onSelect} + canNotSelectMultiple={canNotSelectMultiple} onSelectMultiple={onSelectMultiple} selectedTools={selectedTools} /> diff --git a/web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx index c471709823..a82df0570f 100644 --- a/web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx +++ b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx @@ -12,7 +12,8 @@ type Props = { payload: Record hasSearchText: boolean onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void - onSelectMultiple: (type: BlockEnum, tools: ToolValue[]) => void + canNotSelectMultiple?: boolean + onSelectMultiple?: (type: BlockEnum, tools: ToolDefaultValue[]) => void selectedTools?: ToolValue[] } @@ -20,6 +21,7 @@ const ToolListTreeView: FC = ({ payload, hasSearchText, onSelect, + canNotSelectMultiple, onSelectMultiple, selectedTools, }) => { @@ -48,6 +50,7 @@ const ToolListTreeView: FC = ({ toolList={payload[groupName]} hasSearchText={hasSearchText} onSelect={onSelect} + canNotSelectMultiple={canNotSelectMultiple} onSelectMultiple={onSelectMultiple} selectedTools={selectedTools} /> diff --git a/web/app/components/workflow/block-selector/tool/tool.tsx b/web/app/components/workflow/block-selector/tool/tool.tsx index eeed5fcf3b..415400ec04 100644 --- a/web/app/components/workflow/block-selector/tool/tool.tsx +++ b/web/app/components/workflow/block-selector/tool/tool.tsx @@ -22,7 +22,8 @@ type Props = { isShowLetterIndex: boolean hasSearchText: boolean onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void - onSelectMultiple: (type: BlockEnum, tools: ToolDefaultValue[]) => void + canNotSelectMultiple?: boolean + onSelectMultiple?: (type: BlockEnum, tools: ToolDefaultValue[]) => void selectedTools?: ToolValue[] } @@ -33,6 +34,7 @@ const Tool: FC = ({ isShowLetterIndex, hasSearchText, onSelect, + canNotSelectMultiple, onSelectMultiple, selectedTools, }) => { @@ -68,7 +70,7 @@ const Tool: FC = ({ return ( { - onSelectMultiple(BlockEnum.Tool, actions.filter(action => !getIsDisabled(action)).map((tool) => { + onSelectMultiple?.(BlockEnum.Tool, actions.filter(action => !getIsDisabled(action)).map((tool) => { const params: Record = {} if (tool.parameters) { tool.parameters.forEach((item) => { @@ -186,7 +188,7 @@ const Tool: FC = ({
- {notShowProvider ? notShowProviderSelectInfo : selectedInfo} + {!canNotSelectMultiple && (notShowProvider ? notShowProviderSelectInfo : selectedInfo)} {hasAction && ( )} diff --git a/web/app/components/workflow/block-selector/tools.tsx b/web/app/components/workflow/block-selector/tools.tsx index 53e74117d2..dbe8c3a81a 100644 --- a/web/app/components/workflow/block-selector/tools.tsx +++ b/web/app/components/workflow/block-selector/tools.tsx @@ -17,7 +17,8 @@ import classNames from '@/utils/classnames' type ToolsProps = { showWorkflowEmpty: boolean onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void - onSelectMultiple: (type: BlockEnum, tools: ToolDefaultValue[]) => void + canNotSelectMultiple?: boolean + onSelectMultiple?: (type: BlockEnum, tools: ToolDefaultValue[]) => void tools: ToolWithProvider[] viewType: ViewType hasSearchText: boolean @@ -28,6 +29,7 @@ type ToolsProps = { const Blocks = ({ showWorkflowEmpty, onSelect, + canNotSelectMultiple, onSelectMultiple, tools, viewType, @@ -109,6 +111,7 @@ const Blocks = ({ isShowLetterIndex={isShowLetterIndex} hasSearchText={hasSearchText} onSelect={onSelect} + canNotSelectMultiple={canNotSelectMultiple} onSelectMultiple={onSelectMultiple} selectedTools={selectedTools} /> @@ -117,6 +120,7 @@ const Blocks = ({ payload={treeViewToolsData} hasSearchText={hasSearchText} onSelect={onSelect} + canNotSelectMultiple={canNotSelectMultiple} onSelectMultiple={onSelectMultiple} selectedTools={selectedTools} /> From 8540233193c6eb5818eef6de938b0b622cc1464d Mon Sep 17 00:00:00 2001 From: Joel Date: Mon, 12 May 2025 14:15:40 +0800 Subject: [PATCH 29/58] chore: node new toolbar --- .../workflow/block-selector/all-tools.tsx | 2 +- .../workflow/block-selector/index.tsx | 49 ++++++++++--------- .../workflow/block-selector/tabs.tsx | 21 +++++--- web/tailwind-common-config.ts | 1 + 4 files changed, 40 insertions(+), 33 deletions(-) diff --git a/web/app/components/workflow/block-selector/all-tools.tsx b/web/app/components/workflow/block-selector/all-tools.tsx index 3e5bc8af43..1bb5bfd04f 100644 --- a/web/app/components/workflow/block-selector/all-tools.tsx +++ b/web/app/components/workflow/block-selector/all-tools.tsx @@ -109,7 +109,7 @@ const AllTools = ({ return (
-
+
{ tabs.map(tab => ( diff --git a/web/app/components/workflow/block-selector/index.tsx b/web/app/components/workflow/block-selector/index.tsx index 9e55a24d9e..f8573d2b92 100644 --- a/web/app/components/workflow/block-selector/index.tsx +++ b/web/app/components/workflow/block-selector/index.tsx @@ -129,33 +129,34 @@ const NodeSelector: FC = ({
-
e.stopPropagation()}> - {activeTab === TabsEnum.Blocks && ( - setSearchText(e.target.value)} - onClear={() => setSearchText('')} - /> - )} - {activeTab === TabsEnum.Tools && ( - - )} - -
e.stopPropagation()}> + {activeTab === TabsEnum.Blocks && ( + setSearchText(e.target.value)} + onClear={() => setSearchText('')} + /> + )} + {activeTab === TabsEnum.Tools && ( + + )} +
+ } onSelect={handleSelect} searchText={searchText} tags={tags} diff --git a/web/app/components/workflow/block-selector/tabs.tsx b/web/app/components/workflow/block-selector/tabs.tsx index acebc4039b..f32ab89692 100644 --- a/web/app/components/workflow/block-selector/tabs.tsx +++ b/web/app/components/workflow/block-selector/tabs.tsx @@ -16,6 +16,7 @@ export type TabsProps = { tags: string[] onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void availableBlocksTypes?: BlockEnum[] + filterElem: React.ReactNode noBlocks?: boolean } const Tabs: FC = ({ @@ -25,6 +26,7 @@ const Tabs: FC = ({ searchText, onSelect, availableBlocksTypes, + filterElem, noBlocks, }) => { const tabs = useTabs() @@ -37,15 +39,15 @@ const Tabs: FC = ({
e.stopPropagation()}> { !noBlocks && ( -
+
{ tabs.map(tab => (
onActiveTabChange(tab.key)} @@ -57,13 +59,16 @@ const Tabs: FC = ({
) } + {filterElem} { activeTab === TabsEnum.Blocks && !noBlocks && ( - +
+ +
) } { diff --git a/web/tailwind-common-config.ts b/web/tailwind-common-config.ts index 3f64afcc29..eff1530017 100644 --- a/web/tailwind-common-config.ts +++ b/web/tailwind-common-config.ts @@ -71,6 +71,7 @@ const config = { boxShadow: { 'xs': '0px 1px 2px 0px rgba(16, 24, 40, 0.05)', 'sm': '0px 1px 2px 0px rgba(16, 24, 40, 0.06), 0px 1px 3px 0px rgba(16, 24, 40, 0.10)', + 'sm-no-bottom': '0px -1px 2px 0px rgba(16, 24, 40, 0.06), 0px -1px 3px 0px rgba(16, 24, 40, 0.10)', 'md': '0px 2px 4px -2px rgba(16, 24, 40, 0.06), 0px 4px 8px -2px rgba(16, 24, 40, 0.10)', 'lg': '0px 4px 6px -2px rgba(16, 24, 40, 0.03), 0px 12px 16px -4px rgba(16, 24, 40, 0.08)', 'xl': '0px 8px 8px -4px rgba(16, 24, 40, 0.03), 0px 20px 24px -4px rgba(16, 24, 40, 0.08)', From b58b908a8b76ccde72ed5ce0cac71f103365a913 Mon Sep 17 00:00:00 2001 From: jZonG Date: Thu, 15 May 2025 14:04:10 +0800 Subject: [PATCH 30/58] add mcp service card --- .../[appId]/overview/cardView.tsx | 7 + web/app/components/app/overview/appCard.tsx | 6 +- .../components/tools/mcp/mcp-service-card.tsx | 129 ++++++++++++++++++ 3 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 web/app/components/tools/mcp/mcp-service-card.tsx diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx index 79b45941f1..98e65235f7 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx @@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next' import { useContext, useContextSelector } from 'use-context-selector' import AppCard from '@/app/components/app/overview/appCard' import Loading from '@/app/components/base/loading' +import MCPServiceCard from '@/app/components/tools/mcp/mcp-service-card' import { ToastContext } from '@/app/components/base/toast' import { fetchAppDetail, @@ -137,6 +138,12 @@ const CardView: FC = ({ appId, isInPanel, className }) => { isInPanel={isInPanel} onChangeStatus={onChangeApiStatus} /> + {isInPanel && appDetail.mode === 'workflow' && ( + + )}
) } diff --git a/web/app/components/app/overview/appCard.tsx b/web/app/components/app/overview/appCard.tsx index 7c12f1edee..684276f496 100644 --- a/web/app/components/app/overview/appCard.tsx +++ b/web/app/components/app/overview/appCard.tsx @@ -16,7 +16,7 @@ import style from './style.module.css' import type { ConfigParams } from './settings' import Tooltip from '@/app/components/base/tooltip' import AppBasic from '@/app/components/app-sidebar/basic' -import { asyncRunSafe, randomString } from '@/utils' +import { asyncRunSafe } from '@/utils' import { basePath } from '@/utils/var' import Button from '@/app/components/base/button' import Switch from '@/app/components/base/switch' @@ -147,7 +147,7 @@ function AppCard({ : t('appOverview.overview.apiInfo.explanation') } /> -
+
{runningStatus @@ -173,7 +173,7 @@ function AppCard({ content={isApp ? appUrl : apiUrl} className={'!size-6'} /> - {isApp && } + {isApp && } {isApp && } {/* button copy link/ button regenerate */} {showConfirmDelete && ( diff --git a/web/app/components/tools/mcp/mcp-service-card.tsx b/web/app/components/tools/mcp/mcp-service-card.tsx new file mode 100644 index 0000000000..2454e90e62 --- /dev/null +++ b/web/app/components/tools/mcp/mcp-service-card.tsx @@ -0,0 +1,129 @@ +'use client' +import React, { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { + RiLoopLeftLine, +} from '@remixicon/react' +import Tooltip from '@/app/components/base/tooltip' +import AppBasic from '@/app/components/app-sidebar/basic' +import { asyncRunSafe } from '@/utils' +import { basePath } from '@/utils/var' +import Switch from '@/app/components/base/switch' +import Divider from '@/app/components/base/divider' +import CopyFeedback from '@/app/components/base/copy-feedback' +import Confirm from '@/app/components/base/confirm' +import ShareQRCode from '@/app/components/base/qrcode' +import type { AppDetailResponse } from '@/models/app' +import { useAppContext } from '@/context/app-context' +import type { AppSSO } from '@/types/app' +import Indicator from '@/app/components/header/indicator' +import cn from '@/utils/classnames' + +export type IAppCardProps = { + appInfo: AppDetailResponse & Partial + onGenerateCode?: () => Promise +} + +function MCPServiceCard({ + appInfo, + onGenerateCode, +}: IAppCardProps) { + const { t } = useTranslation() + const { isCurrentWorkspaceManager, isCurrentWorkspaceEditor } = useAppContext() + const [genLoading, setGenLoading] = useState(false) + const [showConfirmDelete, setShowConfirmDelete] = useState(false) + + const basicName = t('appOverview.overview.apiInfo.title') + const toggleDisabled = !isCurrentWorkspaceEditor + const runningStatus = appInfo.enable_site // TODO + const { app_base_url, access_token } = appInfo.site ?? {} + const appMode = (appInfo.mode !== 'completion' && appInfo.mode !== 'workflow') ? 'chat' : appInfo.mode + const appUrl = `${app_base_url}${basePath}/${appMode}/${access_token}` + + const onGenCode = async () => { + if (onGenerateCode) { + setGenLoading(true) + await asyncRunSafe(onGenerateCode()) + setGenLoading(false) + } + } + + const onChangeStatus = async (status: boolean) => { + // TODO + } + + return ( +
+
+
+
+ +
+ +
+ {runningStatus + ? t('appOverview.overview.status.running') + : t('appOverview.overview.status.disable')} +
+
+ +
+
+
+ {t('appOverview.overview.appInfo.accessibleAddress')} +
+
+
+
+ {appUrl} +
+
+ + + + {/* button copy link/ button regenerate */} + {showConfirmDelete && ( + { + onGenCode() + setShowConfirmDelete(false) + }} + onCancel={() => setShowConfirmDelete(false)} + /> + )} + {isCurrentWorkspaceManager && ( + +
setShowConfirmDelete(true)} + > + +
+
+ )} +
+
+
+
+
+
+
+ ) +} + +export default MCPServiceCard From 434187f9f05690ec87875c480d856f5fec3b7e9d Mon Sep 17 00:00:00 2001 From: jZonG Date: Fri, 23 May 2025 14:39:42 +0800 Subject: [PATCH 31/58] mcp tool list --- web/service/use-tools.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/web/service/use-tools.ts b/web/service/use-tools.ts index 83ce2afeab..d248fe6a7c 100644 --- a/web/service/use-tools.ts +++ b/web/service/use-tools.ts @@ -12,8 +12,6 @@ import { useQueryClient, } from '@tanstack/react-query' -import { listData } from '@/app/components/tools/mcp/mock' - const NAME_SPACE = 'tools' const useAllToolProvidersKey = [NAME_SPACE, 'allToolProviders'] @@ -68,10 +66,7 @@ const useAllMCPToolsKey = [NAME_SPACE, 'MCPTools'] export const useAllMCPTools = () => { return useQuery({ queryKey: useAllMCPToolsKey, - // queryFn: () => get('/workspaces/current/tools/mcp'), - queryFn: () => { - return listData as unknown as ToolWithProvider[] - }, + queryFn: () => get('/workspaces/current/tools/mcp'), }) } From f213663e17234dd9cf7859821ffbbee7bf242395 Mon Sep 17 00:00:00 2001 From: jZonG Date: Fri, 23 May 2025 14:56:58 +0800 Subject: [PATCH 32/58] create & delete --- web/app/components/tools/mcp/provider-card.tsx | 2 +- web/app/components/tools/provider-list.tsx | 2 +- web/app/components/tools/types.ts | 2 +- web/service/use-tools.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/app/components/tools/mcp/provider-card.tsx b/web/app/components/tools/mcp/provider-card.tsx index 228b7b19c9..bf43efe62c 100644 --- a/web/app/components/tools/mcp/provider-card.tsx +++ b/web/app/components/tools/mcp/provider-card.tsx @@ -101,7 +101,7 @@ const MCPCard = ({ )}
/
-
{`${t('tools.mcp.updateTime')} ${formatTimeFromNow(data.update_elapsed_time! * 1000)}`}
+
{`${t('tools.mcp.updateTime')} ${formatTimeFromNow(data.updated_at!)}`}
diff --git a/web/app/components/tools/provider-list.tsx b/web/app/components/tools/provider-list.tsx index d5ef0f0130..23a1dd9531 100644 --- a/web/app/components/tools/provider-list.tsx +++ b/web/app/components/tools/provider-list.tsx @@ -97,7 +97,7 @@ const ProviderList = () => { />
- {(filteredCollectionList.length > 0 || (activeTab !== 'builtin' && activeTab !== 'mcp')) && ( + {(filteredCollectionList.length > 0 && (activeTab === 'api' || activeTab === 'workflow')) && (
{ - return del('/console/api/workspaces/current/tool-provider/mcp', { + return del('/workspaces/current/tool-provider/mcp', { body: { provider_id: id, }, From 62b4be9bb106d26e3b8e34166e90026b66e9b95f Mon Sep 17 00:00:00 2001 From: jZonG Date: Fri, 23 May 2025 15:09:12 +0800 Subject: [PATCH 33/58] mcp update --- web/app/components/tools/mcp/modal.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/web/app/components/tools/mcp/modal.tsx b/web/app/components/tools/mcp/modal.tsx index 8edae8a2a5..4e8e38959c 100644 --- a/web/app/components/tools/mcp/modal.tsx +++ b/web/app/components/tools/mcp/modal.tsx @@ -36,7 +36,11 @@ const getIcon = (data?: ToolWithProvider) => { return DEFAULT_ICON as AppIconSelection if (typeof data.icon === 'string') return { type: 'image', url: data.icon, fileId: extractFileId(data.icon) } as AppIconSelection - return data.icon as unknown as AppIconSelection + return { + ...data.icon, + icon: data.icon.content, + type: 'emoji', + } as unknown as AppIconSelection } const MCPModal = ({ From bbd0dbf29bc6380b2a4332253935d71f12422177 Mon Sep 17 00:00:00 2001 From: jZonG Date: Fri, 23 May 2025 15:59:32 +0800 Subject: [PATCH 34/58] authorizing --- .../components/tools/mcp/detail/content.tsx | 34 +++++++++++++------ web/service/use-tools.ts | 11 ++++++ 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/web/app/components/tools/mcp/detail/content.tsx b/web/app/components/tools/mcp/detail/content.tsx index d9f8554754..cd43df09e5 100644 --- a/web/app/components/tools/mcp/detail/content.tsx +++ b/web/app/components/tools/mcp/detail/content.tsx @@ -20,6 +20,7 @@ import OperationDropdown from './operation-dropdown' import ListLoading from './list-loading' import ToolItem from './tool-item' import { + useAuthorizeMCP, useDeleteMCP, useInvalidateMCPTools, useMCPTools, @@ -44,14 +45,15 @@ const MCPDetailContent: FC = ({ const { data: toolList = [], isPending: isGettingTools } = useMCPTools(detail.is_team_authorization ? detail.id : '') const invalidateMCPTools = useInvalidateMCPTools() - const { mutateAsync, isPending: isUpdating } = useUpdateMCPTools(detail.id) + const { mutateAsync: updateTools, isPending: isUpdating } = useUpdateMCPTools(detail.id) + const { mutateAsync: authorizeMcp, isPending: isAuthorizing } = useAuthorizeMCP() const handleUpdateTools = useCallback(async () => { if (!detail) return - await mutateAsync() + await updateTools() invalidateMCPTools(detail.id) - }, [detail, mutateAsync]) + }, [detail, updateTools]) const { mutate: updateMCP } = useUpdateMCP({ onSuccess: onUpdate, @@ -75,6 +77,20 @@ const MCPDetailContent: FC = ({ setFalse: hideDeleting, }] = useBoolean(false) + const handleAuthorize = useCallback(async () => { + if (!detail) + return + const res = await authorizeMcp({ + provider_id: detail.id, + server_url: detail.server_url!, + }) + // TODO + if ((res as any)?.result === 'success') { + hideUpdateModal() + onUpdate() + } + }, [detail, updateMCP, hideUpdateModal, onUpdate]) + const handleUpdate = useCallback(async (data: any) => { if (!detail) return @@ -140,22 +156,20 @@ const MCPDetailContent: FC = ({ {t('tools.auth.authorized')} )} - {!detail.is_team_authorization && ( + {!detail.is_team_authorization && !isAuthorizing && ( )} - {/* TODO */} - {deleting && ( + {isAuthorizing && (
- {isExtraInLine ? ( + {!hideType && isExtraInLine && (
{type}
- ) : ( + )} + {!hideType && !isExtraInLine && (
{isExternal ? t('dataset.externalTag') : type}
)}
} diff --git a/web/app/components/app/overview/appCard.tsx b/web/app/components/app/overview/appCard.tsx index 684276f496..ffb7a80612 100644 --- a/web/app/components/app/overview/appCard.tsx +++ b/web/app/components/app/overview/appCard.tsx @@ -141,6 +141,7 @@ function AppCard({ icon={appInfo.icon} icon_background={appInfo.icon_background} name={basicName} + hideType type={ isApp ? t('appOverview.overview.appInfo.explanation') diff --git a/web/app/components/base/icons/assets/vender/workflow/window-cursor.svg b/web/app/components/base/icons/assets/vender/workflow/window-cursor.svg new file mode 100644 index 0000000000..af8a9bac94 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/workflow/window-cursor.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/web/app/components/base/icons/src/vender/workflow/WindowCursor.json b/web/app/components/base/icons/src/vender/workflow/WindowCursor.json new file mode 100644 index 0000000000..b64ba912bb --- /dev/null +++ b/web/app/components/base/icons/src/vender/workflow/WindowCursor.json @@ -0,0 +1,62 @@ +{ + "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": "M1.33325 4.66663C1.33325 3.56206 2.22869 2.66663 3.33325 2.66663H12.6666C13.7712 2.66663 14.6666 3.56206 14.6666 4.66663V8.16663C14.6666 8.53483 14.3681 8.83329 13.9999 8.83329C13.6317 8.83329 13.3333 8.53483 13.3333 8.16663V4.66663C13.3333 4.29844 13.0348 3.99996 12.6666 3.99996H3.33325C2.96507 3.99996 2.66659 4.29844 2.66659 4.66663V12C2.66659 12.3682 2.96507 12.6666 3.33325 12.6666H7.99992C8.36812 12.6666 8.66658 12.9651 8.66658 13.3333C8.66658 13.7015 8.36812 14 7.99992 14H3.33325C2.22869 14 1.33325 13.1046 1.33325 12V4.66663Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M3.66659 5.83329C3.66659 6.29353 4.03968 6.66663 4.49992 6.66663C4.96016 6.66663 5.33325 6.29353 5.33325 5.83329C5.33325 5.37305 4.96016 4.99996 4.49992 4.99996C4.03968 4.99996 3.66659 5.37305 3.66659 5.83329Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M5.99992 5.83329C5.99992 6.29353 6.37301 6.66663 6.83325 6.66663C7.29352 6.66663 7.66658 6.29353 7.66658 5.83329C7.66658 5.37305 7.29352 4.99996 6.83325 4.99996C6.37301 4.99996 5.99992 5.37305 5.99992 5.83329Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M8.33325 5.83329C8.33325 6.29353 8.70632 6.66663 9.16658 6.66663C9.62685 6.66663 9.99992 6.29353 9.99992 5.83329C9.99992 5.37305 9.62685 4.99996 9.16658 4.99996C8.70632 4.99996 8.33325 5.37305 8.33325 5.83329Z", + "fill": "currentColor" + }, + "children": [] + }, + { + "type": "element", + "name": "path", + "attributes": { + "d": "M10.5293 9.69609C10.2933 9.62349 10.0365 9.68729 9.86185 9.86189C9.68725 10.0365 9.62345 10.2934 9.69605 10.5294L11.0294 14.8627C11.1095 15.1231 11.3401 15.3086 11.6116 15.331C11.8832 15.3535 12.1411 15.2085 12.2629 14.9648L13.1635 13.1636L14.9647 12.263C15.2085 12.1411 15.3535 11.8832 15.331 11.6116C15.3085 11.3401 15.1231 11.1096 14.8627 11.0294L10.5293 9.69609Z", + "fill": "currentColor" + }, + "children": [] + } + ] + }, + "name": "WindowCursor" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/workflow/WindowCursor.tsx b/web/app/components/base/icons/src/vender/workflow/WindowCursor.tsx new file mode 100644 index 0000000000..8f48dc0b14 --- /dev/null +++ b/web/app/components/base/icons/src/vender/workflow/WindowCursor.tsx @@ -0,0 +1,20 @@ +// GENERATE BY script +// DON NOT EDIT IT MANUALLY + +import * as React from 'react' +import data from './WindowCursor.json' +import IconBase from '@/app/components/base/icons/IconBase' +import type { IconData } from '@/app/components/base/icons/IconBase' + +const Icon = ( + { + ref, + ...props + }: React.SVGProps & { + ref?: React.RefObject>; + }, +) => + +Icon.displayName = 'WindowCursor' + +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 7167b71b44..61fbd4b21c 100644 --- a/web/app/components/base/icons/src/vender/workflow/index.ts +++ b/web/app/components/base/icons/src/vender/workflow/index.ts @@ -19,3 +19,4 @@ export { default as ParameterExtractor } from './ParameterExtractor' export { default as QuestionClassifier } from './QuestionClassifier' export { default as TemplatingTransform } from './TemplatingTransform' export { default as VariableX } from './VariableX' +export { default as WindowCursor } from './WindowCursor' diff --git a/web/app/components/tools/mcp/mcp-service-card.tsx b/web/app/components/tools/mcp/mcp-service-card.tsx index 2f86893873..c1e87d2f23 100644 --- a/web/app/components/tools/mcp/mcp-service-card.tsx +++ b/web/app/components/tools/mcp/mcp-service-card.tsx @@ -4,8 +4,11 @@ import { useTranslation } from 'react-i18next' import { RiLoopLeftLine, } from '@remixicon/react' +import { + Mcp, +} from '@/app/components/base/icons/src/vender/other' +import Button from '@/app/components/base/button' import Tooltip from '@/app/components/base/tooltip' -import AppBasic from '@/app/components/app-sidebar/basic' import { asyncRunSafe } from '@/utils' import { basePath } from '@/utils/var' import Switch from '@/app/components/base/switch' @@ -32,7 +35,6 @@ function MCPServiceCard({ const [genLoading, setGenLoading] = useState(false) const [showConfirmDelete, setShowConfirmDelete] = useState(false) - const basicName = t('appOverview.overview.apiInfo.title') const toggleDisabled = !isCurrentWorkspaceEditor const runningStatus = appInfo.enable_site // TODO const { app_base_url, access_token } = appInfo.site ?? {} @@ -54,15 +56,18 @@ function MCPServiceCard({ return (
-
+
- +
+
+ +
+
+
+ {t('tools.mcp.server.title')} +
+
+
@@ -75,7 +80,7 @@ function MCPServiceCard({
- {t('appOverview.overview.appInfo.accessibleAddress')} + {t('tools.mcp.server.url')}
@@ -93,7 +98,7 @@ function MCPServiceCard({ { onGenCode() @@ -118,6 +123,7 @@ function MCPServiceCard({
+
diff --git a/web/i18n/en-US/tools.ts b/web/i18n/en-US/tools.ts index fb6901d2e7..2635d4e06b 100644 --- a/web/i18n/en-US/tools.ts +++ b/web/i18n/en-US/tools.ts @@ -193,6 +193,7 @@ const translation = { server: { title: 'MCP Server', url: 'Server URL', + reGen: 'Do you want to regenerator server URL?', addDescription: 'Add description', edit: 'Edit description', modal: { diff --git a/web/i18n/zh-Hans/tools.ts b/web/i18n/zh-Hans/tools.ts index 52a6bf36f1..a082e33119 100644 --- a/web/i18n/zh-Hans/tools.ts +++ b/web/i18n/zh-Hans/tools.ts @@ -193,6 +193,7 @@ const translation = { server: { title: 'MCP 服务', url: '服务端点 URL', + reGen: '你想要重新生成服务端点 URL 吗?', addDescription: '添加描述', edit: '编辑描述', modal: { From a8bc02e39eebb22f15a8e9295c054b16b9098736 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Mon, 26 May 2025 14:54:54 +0800 Subject: [PATCH 37/58] mcp server modal --- .../components/tools/mcp/mcp-server-modal.tsx | 83 ++++++++++ .../tools/mcp/mcp-server-param-item.tsx | 35 +++++ .../components/tools/mcp/mcp-service-card.tsx | 146 ++++++++++-------- web/i18n/en-US/tools.ts | 1 + web/i18n/zh-Hans/tools.ts | 1 + 5 files changed, 199 insertions(+), 67 deletions(-) create mode 100644 web/app/components/tools/mcp/mcp-server-modal.tsx create mode 100644 web/app/components/tools/mcp/mcp-server-param-item.tsx diff --git a/web/app/components/tools/mcp/mcp-server-modal.tsx b/web/app/components/tools/mcp/mcp-server-modal.tsx new file mode 100644 index 0000000000..26fb8a17dd --- /dev/null +++ b/web/app/components/tools/mcp/mcp-server-modal.tsx @@ -0,0 +1,83 @@ +'use client' +import React from 'react' +import { useTranslation } from 'react-i18next' +import { RiCloseLine } from '@remixicon/react' +import Modal from '@/app/components/base/modal' +import Button from '@/app/components/base/button' +import Textarea from '@/app/components/base/textarea' +import Divider from '@/app/components/base/divider' +import MCPServerParamItem from '@/app/components/tools/mcp/mcp-server-param-item' +import cn from '@/utils/classnames' + +export type ModalProps = { + latestParams?: any + data?: any + show: boolean + onConfirm: () => void + onHide: () => void +} + +const MCPServerModal = ({ + // latestParams, + data, + show, + onConfirm, + onHide, +}: ModalProps) => { + const { t } = useTranslation() + + const [description, setDescription] = React.useState('') + + const submit = async () => { + await onConfirm() + onHide() + } + + return ( + +
+ +
+
+ {!data ? t('tools.mcp.server.modal.addTitle') : t('tools.mcp.server.modal.editTitle')} +
+
+
+
+
{t('tools.mcp.server.modal.description')}
+
*
+
+ +
+
+
+
{t('tools.mcp.server.modal.parameters')}
+ +
+
{t('tools.mcp.server.modal.parametersTip')}
+
+ ({})} + /> +
+
+
+
+ + +
+
+ ) +} + +export default MCPServerModal diff --git a/web/app/components/tools/mcp/mcp-server-param-item.tsx b/web/app/components/tools/mcp/mcp-server-param-item.tsx new file mode 100644 index 0000000000..c1465753ce --- /dev/null +++ b/web/app/components/tools/mcp/mcp-server-param-item.tsx @@ -0,0 +1,35 @@ +'use client' +import React from 'react' +import { useTranslation } from 'react-i18next' +import Textarea from '@/app/components/base/textarea' + +type Props = { + data?: any + onChange: (value: string) => void +} + +const MCPServerParamItem = ({ + data, + onChange, +}: Props) => { + const { t } = useTranslation() + + return ( +
+
+
{data.label}
+
·
+
{data.name}
+
{data.type}
+
+ +
+ ) +} + +export default MCPServerParamItem diff --git a/web/app/components/tools/mcp/mcp-service-card.tsx b/web/app/components/tools/mcp/mcp-service-card.tsx index c1e87d2f23..aaf8acc924 100644 --- a/web/app/components/tools/mcp/mcp-service-card.tsx +++ b/web/app/components/tools/mcp/mcp-service-card.tsx @@ -19,6 +19,7 @@ import type { AppDetailResponse } from '@/models/app' import { useAppContext } from '@/context/app-context' import type { AppSSO } from '@/types/app' import Indicator from '@/app/components/header/indicator' +import MCPServerModal from '@/app/components/tools/mcp/mcp-server-modal' import cn from '@/utils/classnames' export type IAppCardProps = { @@ -53,80 +54,91 @@ function MCPServiceCard({ // TODO } + const [showMCPServerModal, setShowMCPServerModal] = useState(false) + return ( -
-
-
-
-
-
- -
-
-
- {t('tools.mcp.server.title')} + <> +
+
+
+
+
+
+
-
-
-
- -
- {runningStatus - ? t('appOverview.overview.status.running') - : t('appOverview.overview.status.disable')} -
-
- -
-
-
- {t('tools.mcp.server.url')} -
-
-
-
- {appUrl} -
-
- - - {/* button copy link/ button regenerate */} - {showConfirmDelete && ( - { - onGenCode() - setShowConfirmDelete(false) - }} - onCancel={() => setShowConfirmDelete(false)} - /> - )} - {isCurrentWorkspaceManager && ( - -
setShowConfirmDelete(true)} - > - +
+
+ {t('tools.mcp.server.title')}
- - )} +
+
+
+ +
+ {runningStatus + ? t('appOverview.overview.status.running') + : t('appOverview.overview.status.disable')} +
+
+ +
+
+
+ {t('tools.mcp.server.url')} +
+
+
+
+ {appUrl} +
+
+ + + {/* button copy link/ button regenerate */} + {showConfirmDelete && ( + { + onGenCode() + setShowConfirmDelete(false) + }} + onCancel={() => setShowConfirmDelete(false)} + /> + )} + {isCurrentWorkspaceManager && ( + +
setShowConfirmDelete(true)} + > + +
+
+ )} +
-
-
- +
+ +
-
+ {showMCPServerModal && ( + setShowMCPServerModal(false)} + onHide={() => setShowMCPServerModal(false)} + /> + )} + ) } diff --git a/web/i18n/en-US/tools.ts b/web/i18n/en-US/tools.ts index 2635d4e06b..fd264f381b 100644 --- a/web/i18n/en-US/tools.ts +++ b/web/i18n/en-US/tools.ts @@ -199,6 +199,7 @@ const translation = { modal: { addTitle: 'Add description to enable MCP server', editTitle: 'Edit description', + description: 'Description', descriptionPlaceholder: 'Explain what this tool does and how it should be used by the LLM', parameters: 'Parameters', parametersTip: 'Add descriptions for each parameter to help the LLM understand their purpose and constraints.', diff --git a/web/i18n/zh-Hans/tools.ts b/web/i18n/zh-Hans/tools.ts index a082e33119..be8b67de4f 100644 --- a/web/i18n/zh-Hans/tools.ts +++ b/web/i18n/zh-Hans/tools.ts @@ -199,6 +199,7 @@ const translation = { modal: { addTitle: '添加描述以启用 MCP 服务', editTitle: '编辑 MCP 服务描述', + description: '描述', descriptionPlaceholder: '解释此工具的功能以及 LLM 应如何使用它', parameters: '参数', parametersTip: '为每个参数添加描述,以帮助 LLM 理解其目的和约束条件。', From ccea3212a29036046df87ff23722176c43064e2a Mon Sep 17 00:00:00 2001 From: JzoNg Date: Mon, 26 May 2025 16:13:39 +0800 Subject: [PATCH 38/58] api of MCP server detail --- web/app/components/tools/types.ts | 8 +++++ web/service/use-tools.ts | 55 +++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/web/app/components/tools/types.ts b/web/app/components/tools/types.ts index 7ab28e8847..cba7d92027 100644 --- a/web/app/components/tools/types.ts +++ b/web/app/components/tools/types.ts @@ -172,3 +172,11 @@ export type WorkflowToolProviderResponse = { } privacy_policy: string } + +export type MCPServerDetail = { + id: string + server_code: string + description: string + status: string + parameters?: Record +} diff --git a/web/service/use-tools.ts b/web/service/use-tools.ts index acec390465..b00420bd71 100644 --- a/web/service/use-tools.ts +++ b/web/service/use-tools.ts @@ -1,6 +1,7 @@ import { del, get, post, put } from './base' import type { Collection, + MCPServerDetail, Tool, } from '@/app/components/tools/types' import type { ToolWithProvider } from '@/app/components/workflow/types' @@ -175,6 +176,60 @@ export const useUpdateMCPTools = (providerID: string) => { }) } +export const useMCPServerDetail = (appID: string) => { + return useQuery({ + queryKey: [NAME_SPACE, 'MCPServerDetail', appID], + queryFn: () => get(`/apps/${appID}/server`), + }) +} + +export const useCreateMCPServer = ({ + onSuccess, +}: { + onSuccess?: () => void +}) => { + return useMutation({ + mutationKey: [NAME_SPACE, 'create-mcp-server'], + mutationFn: (payload: { + appID: string + description: string + parameters?: Record + }) => { + const { appID, ...rest } = payload + return post(`apps/${appID}/server`, { + body: { + ...rest, + }, + }) + }, + onSuccess, + }) +} + +export const useUpdateMCPServer = ({ + onSuccess, +}: { + onSuccess?: () => void +}) => { + return useMutation({ + mutationKey: [NAME_SPACE, 'update-mcp-server'], + mutationFn: (payload: { + appID: string + description?: string + status?: string + parameters?: Record + }) => { + const { appID, ...rest } = payload + return put(`apps/${appID}/server`, { + body: { + ...rest, + }, + }) + }, + onSuccess, + }) +} + export const useBuiltinProviderInfo = (providerName: string) => { return useQuery({ queryKey: [NAME_SPACE, 'builtin-provider-info', providerName], From a448b140e96f1ad1f2c411f06ca902d19e15c601 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Mon, 26 May 2025 17:07:49 +0800 Subject: [PATCH 39/58] latest params --- .../components/tools/mcp/mcp-server-param-item.tsx | 2 +- web/app/components/tools/mcp/mcp-service-card.tsx | 6 ++++++ .../components/workflow-header/features-trigger.tsx | 6 ++++-- web/service/use-workflow.ts | 12 +++++++++++- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/web/app/components/tools/mcp/mcp-server-param-item.tsx b/web/app/components/tools/mcp/mcp-server-param-item.tsx index c1465753ce..5ea268dba7 100644 --- a/web/app/components/tools/mcp/mcp-server-param-item.tsx +++ b/web/app/components/tools/mcp/mcp-server-param-item.tsx @@ -19,7 +19,7 @@ const MCPServerParamItem = ({
{data.label}
·
-
{data.name}
+
{data.variable}
{data.type}
-
-
-
{t('tools.mcp.server.modal.parameters')}
- + {latestParams.length > 0 && ( +
+
+
{t('tools.mcp.server.modal.parameters')}
+ +
+
{t('tools.mcp.server.modal.parametersTip')}
+
+ {latestParams.map(paramItem => ( + handleParamChange(paramItem.variable, value)} + /> + ))} +
-
{t('tools.mcp.server.modal.parametersTip')}
-
- ({})} - /> -
-
+ )}
- +
diff --git a/web/app/components/tools/mcp/mcp-service-card.tsx b/web/app/components/tools/mcp/mcp-service-card.tsx index 1d3a191425..96afc7cc73 100644 --- a/web/app/components/tools/mcp/mcp-service-card.tsx +++ b/web/app/components/tools/mcp/mcp-service-card.tsx @@ -1,5 +1,5 @@ 'use client' -import React, { useEffect, useState } from 'react' +import React, { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { RiLoopLeftLine, @@ -23,6 +23,7 @@ import { useAppWorkflow } from '@/service/use-workflow' import { useMCPServerDetail, } from '@/service/use-tools' +import { BlockEnum } from '@/app/components/workflow/types' import cn from '@/utils/classnames' export type IAppCardProps = { @@ -48,11 +49,17 @@ function MCPServiceCard({ const serverPublished = !!id const serverActivated = status === 'active' const serverURL = serverPublished ? `${globalThis.location.protocol}//${globalThis.location.host}/api/server/${server_code}/mcp` : '***********' - const toggleDisabled = !isCurrentWorkspaceEditor || appUnpublished const [activated, setActivated] = useState(serverActivated) + const latestParams = useMemo(() => { + if (!currentWorkflow?.graph) + return [] + const startNode = currentWorkflow?.graph.nodes.find(node => node.data.type === BlockEnum.Start) as any + return startNode?.data.variables as any[] || [] + }, [currentWorkflow]) + const onGenCode = async () => { if (onGenerateCode) { setGenLoading(true) @@ -80,10 +87,6 @@ function MCPServiceCard({ setActivated(false) } - const handleServerModalConfirm = () => { - setShowMCPServerModal(false) - } - useEffect(() => { setActivated(serverActivated) }, [serverActivated]) @@ -164,7 +167,7 @@ function MCPServiceCard({ variant='ghost' onClick={() => setShowMCPServerModal(true)} > - {serverPublished ? t('tools.mcp.server.editDescription') : t('tools.mcp.server.addDescription')} + {serverPublished ? t('tools.mcp.server.edit') : t('tools.mcp.server.addDescription')}
@@ -172,7 +175,9 @@ function MCPServiceCard({ {showMCPServerModal && ( )} diff --git a/web/service/use-tools.ts b/web/service/use-tools.ts index b00420bd71..9c5363e23e 100644 --- a/web/service/use-tools.ts +++ b/web/service/use-tools.ts @@ -183,11 +183,17 @@ export const useMCPServerDetail = (appID: string) => { }) } -export const useCreateMCPServer = ({ - onSuccess, -}: { - onSuccess?: () => void -}) => { +export const useInvalidateMCPServerDetail = () => { + const queryClient = useQueryClient() + return (appID: string) => { + queryClient.invalidateQueries( + { + queryKey: [NAME_SPACE, 'MCPServerDetail', appID], + }) + } +} + +export const useCreateMCPServer = () => { return useMutation({ mutationKey: [NAME_SPACE, 'create-mcp-server'], mutationFn: (payload: { @@ -202,19 +208,15 @@ export const useCreateMCPServer = ({ }, }) }, - onSuccess, }) } -export const useUpdateMCPServer = ({ - onSuccess, -}: { - onSuccess?: () => void -}) => { +export const useUpdateMCPServer = () => { return useMutation({ mutationKey: [NAME_SPACE, 'update-mcp-server'], mutationFn: (payload: { appID: string + id: string description?: string status?: string parameters?: Record @@ -226,7 +228,6 @@ export const useUpdateMCPServer = ({ }, }) }, - onSuccess, }) } From 938a180affb56edf76f37a33f337eeb2d85234ca Mon Sep 17 00:00:00 2001 From: jZonG Date: Tue, 27 May 2025 17:15:55 +0800 Subject: [PATCH 47/58] MCP server create & update --- .../[appId]/overview/cardView.tsx | 1 - .../components/tools/mcp/mcp-service-card.tsx | 41 ++++++++++++------- web/service/use-tools.ts | 9 ++++ 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx index 98e65235f7..05799bcac5 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx @@ -141,7 +141,6 @@ const CardView: FC = ({ appId, isInPanel, className }) => { {isInPanel && appDetail.mode === 'workflow' && ( )}
diff --git a/web/app/components/tools/mcp/mcp-service-card.tsx b/web/app/components/tools/mcp/mcp-service-card.tsx index 96afc7cc73..66709d699f 100644 --- a/web/app/components/tools/mcp/mcp-service-card.tsx +++ b/web/app/components/tools/mcp/mcp-service-card.tsx @@ -9,7 +9,6 @@ import { } from '@/app/components/base/icons/src/vender/other' import Button from '@/app/components/base/button' import Tooltip from '@/app/components/base/tooltip' -import { asyncRunSafe } from '@/utils' import Switch from '@/app/components/base/switch' import Divider from '@/app/components/base/divider' import CopyFeedback from '@/app/components/base/copy-feedback' @@ -21,23 +20,26 @@ import Indicator from '@/app/components/header/indicator' import MCPServerModal from '@/app/components/tools/mcp/mcp-server-modal' import { useAppWorkflow } from '@/service/use-workflow' import { + useInvalidateMCPServerDetail, useMCPServerDetail, + useRefreshMCPServerCode, + useUpdateMCPServer, } from '@/service/use-tools' import { BlockEnum } from '@/app/components/workflow/types' import cn from '@/utils/classnames' export type IAppCardProps = { appInfo: AppDetailResponse & Partial - onGenerateCode?: () => Promise } function MCPServiceCard({ appInfo, - onGenerateCode, }: IAppCardProps) { const { t } = useTranslation() + const { mutateAsync: updateMCPServer } = useUpdateMCPServer() + const { mutateAsync: refreshMCPServerCode, isPending: genLoading } = useRefreshMCPServerCode() + const invalidateMCPServerDetail = useInvalidateMCPServerDetail() const { isCurrentWorkspaceManager, isCurrentWorkspaceEditor } = useAppContext() - const [genLoading, setGenLoading] = useState(false) const [showConfirmDelete, setShowConfirmDelete] = useState(false) const [showMCPServerModal, setShowMCPServerModal] = useState(false) @@ -61,23 +63,34 @@ function MCPServiceCard({ }, [currentWorkflow]) const onGenCode = async () => { - if (onGenerateCode) { - setGenLoading(true) - await asyncRunSafe(onGenerateCode()) - setGenLoading(false) - } + await refreshMCPServerCode(detail?.id || '') + invalidateMCPServerDetail(appInfo.id) } const onChangeStatus = async (state: boolean) => { + setActivated(state) if (state) { - if (!serverPublished) { - setActivated(true) + if (!serverPublished) setShowMCPServerModal(true) - } - // TODO handle server activation + + await updateMCPServer({ + appID: appInfo.id, + id: id || '', + description: detail?.description || '', + parameters: detail?.parameters || {}, + status: 'active', + }) + invalidateMCPServerDetail(appInfo.id) } else { - // TODO handle server activation + await updateMCPServer({ + appID: appInfo.id, + id: id || '', + description: detail?.description || '', + parameters: detail?.parameters || {}, + status: 'inactive', + }) + invalidateMCPServerDetail(appInfo.id) } } diff --git a/web/service/use-tools.ts b/web/service/use-tools.ts index 9c5363e23e..917fe5ae99 100644 --- a/web/service/use-tools.ts +++ b/web/service/use-tools.ts @@ -231,6 +231,15 @@ export const useUpdateMCPServer = () => { }) } +export const useRefreshMCPServerCode = () => { + return useMutation({ + mutationKey: [NAME_SPACE, 'refresh-mcp-server-code'], + mutationFn: (appID: string) => { + return get(`apps/${appID}/server/refresh`) + }, + }) +} + export const useBuiltinProviderInfo = (providerName: string) => { return useQuery({ queryKey: [NAME_SPACE, 'builtin-provider-info', providerName], From 195a349cb5243b1b60c5fa36764801e9f2b544d8 Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 27 May 2025 18:21:59 +0800 Subject: [PATCH 48/58] fix: mcp tool label --- .../app/configuration/config/agent/agent-tools/index.tsx | 8 +++++++- .../plugins/plugin-detail-panel/tool-selector/index.tsx | 3 +++ .../plugin-detail-panel/tool-selector/tool-item.tsx | 6 +++++- web/app/components/workflow/block-selector/types.ts | 1 + 4 files changed, 16 insertions(+), 2 deletions(-) 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 fdb0bc3b49..6c7bb67ddb 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 @@ -126,6 +126,12 @@ const AgentTools: FC = () => { }) setModelConfig(newModelConfig) } + const getProviderShowName = (item: AgentTool) => { + const type = item.provider_type + if(type === CollectionType.builtIn) + return item.provider_name.split('/').pop() + return item.provider_name + } return ( <> @@ -187,7 +193,7 @@ const AgentTools: FC = () => { (item.isDeleted || item.notAuthor || !item.enabled) ? 'opacity-50' : '', )} > - {item.provider_type === CollectionType.builtIn ? item.provider_name.split('/').pop() : item.tool_label} + {getProviderShowName(item)} {item.tool_label} {!item.isDeleted && ( = ({ const paramValues = generateFormValue(tool.params, toolParametersToFormSchemas(tool.paramSchemas.filter(param => param.form === 'llm') as any), true) return { provider_name: tool.provider_id, + provider_show_name: tool.provider_name, type: tool.provider_type, tool_name: tool.tool_name, tool_label: tool.tool_label, @@ -252,7 +253,9 @@ const ToolSelector: FC = ({ { const { t } = useTranslation() - const providerNameText = providerName?.split('/').pop() + const providerNameText = isMCPTool ? providerShowName : providerName?.split('/').pop() const isTransparent = uninstalled || versionMismatch || isError const [isDeleting, setIsDeleting] = useState(false) diff --git a/web/app/components/workflow/block-selector/types.ts b/web/app/components/workflow/block-selector/types.ts index 50e3cc24a8..398a7e0c71 100644 --- a/web/app/components/workflow/block-selector/types.ts +++ b/web/app/components/workflow/block-selector/types.ts @@ -35,6 +35,7 @@ export type ToolDefaultValue = { export type ToolValue = { provider_name: string + provider_show_name?: string tool_name: string tool_label: string tool_description: string From 598c469be2390b709ecdb45735f37eb93dbf1651 Mon Sep 17 00:00:00 2001 From: jZonG Date: Wed, 28 May 2025 09:44:46 +0800 Subject: [PATCH 49/58] tool list data binding --- web/app/components/tools/mcp/detail/content.tsx | 4 ++-- web/service/use-tools.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/app/components/tools/mcp/detail/content.tsx b/web/app/components/tools/mcp/detail/content.tsx index af3fc7f75b..9396df3791 100644 --- a/web/app/components/tools/mcp/detail/content.tsx +++ b/web/app/components/tools/mcp/detail/content.tsx @@ -43,11 +43,11 @@ const MCPDetailContent: FC = ({ const { t } = useTranslation() const { isCurrentWorkspaceManager } = useAppContext() - const { data: toolList = [], isPending: isGettingTools } = useMCPTools(detail.is_team_authorization ? detail.id : '') - console.log('MCPDetailContent', detail, toolList) + const { data, isPending: isGettingTools } = useMCPTools(detail.is_team_authorization ? detail.id : '') const invalidateMCPTools = useInvalidateMCPTools() const { mutateAsync: updateTools, isPending: isUpdating } = useUpdateMCPTools(detail.id) const { mutateAsync: authorizeMcp, isPending: isAuthorizing } = useAuthorizeMCP() + const toolList = data?.tools || [] const handleUpdateTools = useCallback(async () => { if (!detail) diff --git a/web/service/use-tools.ts b/web/service/use-tools.ts index 917fe5ae99..ae83a3045f 100644 --- a/web/service/use-tools.ts +++ b/web/service/use-tools.ts @@ -157,7 +157,7 @@ export const useMCPTools = (providerID: string) => { return useQuery({ enabled: !!providerID, queryKey: [NAME_SPACE, 'get-MCP-provider-tool', providerID], - queryFn: () => get(`/workspaces/current/tool-provider/mcp/tools/${providerID}`), + queryFn: () => get<{ tools: Tool[] }>(`/workspaces/current/tool-provider/mcp/tools/${providerID}`), }) } export const useInvalidateMCPTools = () => { @@ -172,7 +172,7 @@ export const useInvalidateMCPTools = () => { export const useUpdateMCPTools = (providerID: string) => { return useMutation({ - mutationFn: () => get(`/workspaces/current/tool-provider/mcp/update/${providerID}`), + mutationFn: () => get<{ tools: Tool[] }>(`/workspaces/current/tool-provider/mcp/update/${providerID}`), }) } From 246948d892436b2447e17b5c0f4d6c17eb218aca Mon Sep 17 00:00:00 2001 From: jZonG Date: Wed, 28 May 2025 10:57:24 +0800 Subject: [PATCH 50/58] auto authorizing after created --- web/app/components/tools/mcp/create-card.tsx | 14 ++++++---- .../components/tools/mcp/detail/content.tsx | 4 +-- web/app/components/tools/mcp/index.tsx | 28 +++++++++++++++---- .../components/tools/mcp/provider-card.tsx | 6 ++-- web/service/use-tools.ts | 13 +++------ 5 files changed, 41 insertions(+), 24 deletions(-) diff --git a/web/app/components/tools/mcp/create-card.tsx b/web/app/components/tools/mcp/create-card.tsx index 2896372450..511a724831 100644 --- a/web/app/components/tools/mcp/create-card.tsx +++ b/web/app/components/tools/mcp/create-card.tsx @@ -12,9 +12,10 @@ import I18n from '@/context/i18n' import { getLanguage } from '@/i18n/language' import { useAppContext } from '@/context/app-context' import { useCreateMCP } from '@/service/use-tools' +import type { ToolWithProvider } from '@/app/components/workflow/types' type Props = { - handleCreate: () => void + handleCreate: (provider: ToolWithProvider) => void } const NewMCPCard = ({ handleCreate }: Props) => { @@ -23,9 +24,12 @@ const NewMCPCard = ({ handleCreate }: Props) => { const language = getLanguage(locale) const { isCurrentWorkspaceManager } = useAppContext() - const { mutate: createMCP } = useCreateMCP({ - onSuccess: handleCreate, - }) + const { mutateAsync: createMCP } = useCreateMCP() + + const create = async (info: any) => { + const provider = await createMCP(info) + handleCreate(provider) + } const linkUrl = useMemo(() => { // TODO help link @@ -60,7 +64,7 @@ const NewMCPCard = ({ handleCreate }: Props) => { {showModal && ( setShowModal(false)} /> )} diff --git a/web/app/components/tools/mcp/detail/content.tsx b/web/app/components/tools/mcp/detail/content.tsx index 9396df3791..5d2f5cb513 100644 --- a/web/app/components/tools/mcp/detail/content.tsx +++ b/web/app/components/tools/mcp/detail/content.tsx @@ -45,14 +45,14 @@ const MCPDetailContent: FC = ({ const { data, isPending: isGettingTools } = useMCPTools(detail.is_team_authorization ? detail.id : '') const invalidateMCPTools = useInvalidateMCPTools() - const { mutateAsync: updateTools, isPending: isUpdating } = useUpdateMCPTools(detail.id) + const { mutateAsync: updateTools, isPending: isUpdating } = useUpdateMCPTools() const { mutateAsync: authorizeMcp, isPending: isAuthorizing } = useAuthorizeMCP() const toolList = data?.tools || [] const handleUpdateTools = useCallback(async () => { if (!detail) return - await updateTools() + await updateTools(detail.id) invalidateMCPTools(detail.id) }, [detail, updateTools]) diff --git a/web/app/components/tools/mcp/index.tsx b/web/app/components/tools/mcp/index.tsx index 08a9f177be..6b57129b8a 100644 --- a/web/app/components/tools/mcp/index.tsx +++ b/web/app/components/tools/mcp/index.tsx @@ -3,7 +3,7 @@ import { useMemo, useState } from 'react' import NewMCPCard from './create-card' import MCPCard from './provider-card' import MCPDetailPanel from './detail/provider-detail' -import { useAllMCPTools, useInvalidateAllMCPTools } from '@/service/use-tools' +import { useAllMCPTools, useAuthorizeMCP, useInvalidateAllMCPTools, useInvalidateMCPTools, useUpdateMCPTools } from '@/service/use-tools' import type { ToolWithProvider } from '@/app/components/workflow/types' import cn from '@/utils/classnames' @@ -34,6 +34,9 @@ const MCPList = ({ }: Props) => { const { data: list = [] } = useAllMCPTools() const invalidateMCPList = useInvalidateAllMCPTools() + const { mutateAsync: authorizeMcp } = useAuthorizeMCP() + const { mutateAsync: updateTools } = useUpdateMCPTools() + const invalidateMCPTools = useInvalidateMCPTools() const filteredList = useMemo(() => { return list.filter((collection) => { @@ -43,7 +46,22 @@ const MCPList = ({ }) }, [list, searchText]) - const [currentProvider, setCurrentProvider] = useState() + const [currentProviderID, setCurrentProviderID] = useState() + + const currentProvider = useMemo(() => { + return list.find(provider => provider.id === currentProviderID) + }, [list, currentProviderID]) + + const handleCreate = async (provider: ToolWithProvider) => { + invalidateMCPList() + setCurrentProviderID(provider.id) + await authorizeMcp({ + provider_id: provider.id, + server_url: provider.server_url!, + }) + await updateTools(provider.id) + invalidateMCPTools(provider.id) + } return ( <> @@ -53,13 +71,13 @@ const MCPList = ({ !list.length && 'h-[calc(100vh_-_136px)] overflow-hidden', )} > - + {filteredList.map(provider => ( invalidateMCPList()} /> ))} @@ -68,7 +86,7 @@ const MCPList = ({ {currentProvider && ( setCurrentProvider(undefined)} + onHide={() => setCurrentProviderID(undefined)} onUpdate={() => invalidateMCPList()} /> )} diff --git a/web/app/components/tools/mcp/provider-card.tsx b/web/app/components/tools/mcp/provider-card.tsx index 06e0b6f02a..a28b0a1ca7 100644 --- a/web/app/components/tools/mcp/provider-card.tsx +++ b/web/app/components/tools/mcp/provider-card.tsx @@ -17,7 +17,7 @@ import cn from '@/utils/classnames' type Props = { currentProvider?: ToolWithProvider data: ToolWithProvider - handleSelect: (provider: ToolWithProvider) => void + handleSelect: (providerID: string) => void onUpdate: () => void } @@ -78,7 +78,7 @@ const MCPCard = ({ return (
handleSelect(data)} + onClick={() => handleSelect(data.id)} className={cn( 'group relative flex cursor-pointer flex-col rounded-xl border-[1.5px] border-transparent bg-components-card-bg shadow-xs hover:bg-components-card-bg-alt hover:shadow-md', currentProvider?.id === data.id && 'border-components-option-card-option-selected-border bg-components-card-bg-alt', @@ -107,7 +107,7 @@ const MCPCard = ({
{data.server_url}
- {data.is_team_authorization && } + {data.is_team_authorization && data.tools.length > 0 && } {(!data.is_team_authorization || !data.tools.length) && (
{t('tools.mcp.noConfigured')} diff --git a/web/service/use-tools.ts b/web/service/use-tools.ts index ae83a3045f..b0caeff044 100644 --- a/web/service/use-tools.ts +++ b/web/service/use-tools.ts @@ -75,11 +75,7 @@ export const useInvalidateAllMCPTools = () => { return useInvalid(useAllMCPToolsKey) } -export const useCreateMCP = ({ - onSuccess, -}: { - onSuccess?: () => void -}) => { +export const useCreateMCP = () => { return useMutation({ mutationKey: [NAME_SPACE, 'create-mcp'], mutationFn: (payload: { @@ -89,13 +85,12 @@ export const useCreateMCP = ({ icon: string icon_background?: string | null }) => { - return post('workspaces/current/tool-provider/mcp', { + return post('workspaces/current/tool-provider/mcp', { body: { ...payload, }, }) }, - onSuccess, }) } @@ -170,9 +165,9 @@ export const useInvalidateMCPTools = () => { } } -export const useUpdateMCPTools = (providerID: string) => { +export const useUpdateMCPTools = () => { return useMutation({ - mutationFn: () => get<{ tools: Tool[] }>(`/workspaces/current/tool-provider/mcp/update/${providerID}`), + mutationFn: (providerID: string) => get<{ tools: Tool[] }>(`/workspaces/current/tool-provider/mcp/update/${providerID}`), }) } From 2bcfcfabb7e65c0bb0fe0ab6eb5cac591b01dd91 Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 28 May 2025 14:30:33 +0800 Subject: [PATCH 51/58] chore: use short auto copywriting --- web/i18n/de-DE/plugin.ts | 2 +- web/i18n/en-US/plugin.ts | 4 ++-- web/i18n/es-ES/plugin.ts | 4 ++-- web/i18n/fr-FR/plugin.ts | 4 ++-- web/i18n/it-IT/plugin.ts | 4 ++-- web/i18n/pl-PL/plugin.ts | 4 ++-- web/i18n/pt-BR/plugin.ts | 4 ++-- web/i18n/ro-RO/plugin.ts | 4 ++-- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/web/i18n/de-DE/plugin.ts b/web/i18n/de-DE/plugin.ts index 64c59fd79f..25deb522f4 100644 --- a/web/i18n/de-DE/plugin.ts +++ b/web/i18n/de-DE/plugin.ts @@ -55,7 +55,7 @@ const translation = { unsupportedContent: 'Die installierte Plug-in-Version bietet diese Aktion nicht.', unsupportedTitle: 'Nicht unterstützte Aktion', descriptionPlaceholder: 'Kurze Beschreibung des Zwecks des Werkzeugs, z. B. um die Temperatur für einen bestimmten Ort zu ermitteln.', - auto: 'Automatisch', + auto: 'Auto', params: 'KONFIGURATION DER ARGUMENTATION', unsupportedContent2: 'Klicken Sie hier, um die Version zu wechseln.', placeholder: 'Wählen Sie ein Werkzeug aus...', diff --git a/web/i18n/en-US/plugin.ts b/web/i18n/en-US/plugin.ts index a0b36fbd65..a4e4d39d39 100644 --- a/web/i18n/en-US/plugin.ts +++ b/web/i18n/en-US/plugin.ts @@ -84,8 +84,8 @@ const translation = { settings: 'USER SETTINGS', params: 'REASONING CONFIG', paramsTip1: 'Controls LLM inference parameters.', - paramsTip2: 'When \'Automatic\' is off, the default value is used.', - auto: 'Automatic', + paramsTip2: 'When \'Auto\' is off, the default value is used.', + auto: 'Auto', empty: 'Click the \'+\' button to add tools. You can add multiple tools.', uninstalledTitle: 'Tool not installed', uninstalledContent: 'This plugin is installed from the local/GitHub repository. Please use after installation.', diff --git a/web/i18n/es-ES/plugin.ts b/web/i18n/es-ES/plugin.ts index 9453c20f97..26d96d44e5 100644 --- a/web/i18n/es-ES/plugin.ts +++ b/web/i18n/es-ES/plugin.ts @@ -51,11 +51,11 @@ const translation = { unsupportedContent2: 'Haga clic para cambiar de versión.', descriptionPlaceholder: 'Breve descripción del propósito de la herramienta, por ejemplo, obtener la temperatura para una ubicación específica.', empty: 'Haga clic en el botón \'+\' para agregar herramientas. Puede agregar varias herramientas.', - paramsTip2: 'Cuando \'Automático\' está desactivado, se utiliza el valor predeterminado.', + paramsTip2: 'Cuando \'Auto\' está desactivado, se utiliza el valor predeterminado.', uninstalledTitle: 'Herramienta no instalada', descriptionLabel: 'Descripción de la herramienta', unsupportedContent: 'La versión del plugin instalado no proporciona esta acción.', - auto: 'Automático', + auto: 'Auto', title: 'Agregar herramienta', placeholder: 'Seleccione una herramienta...', uninstalledContent: 'Este plugin se instala desde el repositorio local/GitHub. Úselo después de la instalación.', diff --git a/web/i18n/fr-FR/plugin.ts b/web/i18n/fr-FR/plugin.ts index 39fef6e91f..ecf4f9ff5e 100644 --- a/web/i18n/fr-FR/plugin.ts +++ b/web/i18n/fr-FR/plugin.ts @@ -53,14 +53,14 @@ const translation = { placeholder: 'Sélectionnez un outil...', params: 'CONFIGURATION DE RAISONNEMENT', unsupportedContent: 'La version du plugin installée ne fournit pas cette action.', - auto: 'Automatique', + auto: 'Auto', descriptionPlaceholder: 'Brève description de l’objectif de l’outil, par exemple, obtenir la température d’un endroit spécifique.', unsupportedContent2: 'Cliquez pour changer de version.', uninstalledTitle: 'Outil non installé', empty: 'Cliquez sur le bouton « + » pour ajouter des outils. Vous pouvez ajouter plusieurs outils.', toolLabel: 'Outil', settings: 'PARAMÈTRES UTILISATEUR', - paramsTip2: 'Lorsque « Automatique » est désactivé, la valeur par défaut est utilisée.', + paramsTip2: 'Lorsque « Auto » est désactivé, la valeur par défaut est utilisée.', paramsTip1: 'Contrôle les paramètres d’inférence LLM.', }, modelNum: '{{num}} MODÈLES INCLUS', diff --git a/web/i18n/it-IT/plugin.ts b/web/i18n/it-IT/plugin.ts index 2c57e5b7af..4faa584fea 100644 --- a/web/i18n/it-IT/plugin.ts +++ b/web/i18n/it-IT/plugin.ts @@ -60,8 +60,8 @@ const translation = { placeholder: 'Seleziona uno strumento...', unsupportedContent: 'La versione del plug-in installata non fornisce questa azione.', descriptionLabel: 'Descrizione dell\'utensile', - auto: 'Automatico', - paramsTip2: 'Quando \'Automatico\' è disattivato, viene utilizzato il valore predefinito.', + auto: 'Auto', + paramsTip2: 'Quando \'Auto\' è disattivato, viene utilizzato il valore predefinito.', }, modelNum: '{{num}} MODELLI INCLUSI', endpointModalTitle: 'Endpoint di configurazione', diff --git a/web/i18n/pl-PL/plugin.ts b/web/i18n/pl-PL/plugin.ts index e04068e59d..e61e049ad5 100644 --- a/web/i18n/pl-PL/plugin.ts +++ b/web/i18n/pl-PL/plugin.ts @@ -51,7 +51,7 @@ const translation = { paramsTip1: 'Steruje parametrami wnioskowania LLM.', unsupportedContent: 'Zainstalowana wersja wtyczki nie zapewnia tej akcji.', params: 'KONFIGURACJA ROZUMOWANIA', - auto: 'Automatyczne', + auto: 'Auto', empty: 'Kliknij przycisk "+", aby dodać narzędzia. Możesz dodać wiele narzędzi.', descriptionLabel: 'Opis narzędzia', title: 'Dodaj narzędzie', @@ -60,7 +60,7 @@ const translation = { uninstalledContent: 'Ta wtyczka jest instalowana z repozytorium lokalnego/GitHub. Proszę użyć po instalacji.', unsupportedTitle: 'Nieobsługiwana akcja', uninstalledTitle: 'Narzędzie nie jest zainstalowane', - paramsTip2: 'Gdy opcja "Automatycznie" jest wyłączona, używana jest wartość domyślna.', + paramsTip2: 'Gdy opcja "Auto" jest wyłączona, używana jest wartość domyślna.', toolLabel: 'Narzędzie', }, strategyNum: '{{liczba}} {{strategia}} ZAWARTE', diff --git a/web/i18n/pt-BR/plugin.ts b/web/i18n/pt-BR/plugin.ts index 3528407a1b..2596a375cd 100644 --- a/web/i18n/pt-BR/plugin.ts +++ b/web/i18n/pt-BR/plugin.ts @@ -47,14 +47,14 @@ const translation = { toolSelector: { uninstalledLink: 'Gerenciar em plug-ins', unsupportedContent2: 'Clique para mudar de versão.', - auto: 'Automático', + auto: 'Auto', title: 'Adicionar ferramenta', params: 'CONFIGURAÇÃO DE RACIOCÍNIO', toolLabel: 'Ferramenta', paramsTip1: 'Controla os parâmetros de inferência do LLM.', descriptionLabel: 'Descrição da ferramenta', uninstalledContent: 'Este plug-in é instalado a partir do repositório local/GitHub. Por favor, use após a instalação.', - paramsTip2: 'Quando \'Automático\' está desativado, o valor padrão é usado.', + paramsTip2: 'Quando \'Auto\' está desativado, o valor padrão é usado.', placeholder: 'Selecione uma ferramenta...', empty: 'Clique no botão \'+\' para adicionar ferramentas. Você pode adicionar várias ferramentas.', settings: 'CONFIGURAÇÕES DO USUÁRIO', diff --git a/web/i18n/ro-RO/plugin.ts b/web/i18n/ro-RO/plugin.ts index db21cbc40a..4720492937 100644 --- a/web/i18n/ro-RO/plugin.ts +++ b/web/i18n/ro-RO/plugin.ts @@ -46,7 +46,7 @@ const translation = { }, toolSelector: { unsupportedContent: 'Versiunea de plugin instalată nu oferă această acțiune.', - auto: 'Automat', + auto: 'Auto', empty: 'Faceți clic pe butonul "+" pentru a adăuga instrumente. Puteți adăuga mai multe instrumente.', uninstalledContent: 'Acest plugin este instalat din depozitul local/GitHub. Vă rugăm să utilizați după instalare.', descriptionLabel: 'Descrierea instrumentului', @@ -54,7 +54,7 @@ const translation = { uninstalledLink: 'Gestionați în pluginuri', paramsTip1: 'Controlează parametrii de inferență LLM.', params: 'CONFIGURAREA RAȚIONAMENTULUI', - paramsTip2: 'Când "Automat" este dezactivat, se folosește valoarea implicită.', + paramsTip2: 'Când "Auto" este dezactivat, se folosește valoarea implicită.', settings: 'SETĂRI UTILIZATOR', unsupportedTitle: 'Acțiune neacceptată', placeholder: 'Selectați un instrument...', From 8832f08fed28aa1795e42944c596fc64be3e8a1d Mon Sep 17 00:00:00 2001 From: jZonG Date: Wed, 28 May 2025 15:35:44 +0800 Subject: [PATCH 52/58] authorizing redirection --- web/app/components/tools/mcp/create-card.tsx | 4 +- .../components/tools/mcp/detail/content.tsx | 20 ++++---- web/app/components/tools/mcp/index.tsx | 47 ++++++++++++++++--- web/app/components/tools/provider-list.tsx | 7 ++- web/service/use-tools.ts | 15 +++++- 5 files changed, 72 insertions(+), 21 deletions(-) diff --git a/web/app/components/tools/mcp/create-card.tsx b/web/app/components/tools/mcp/create-card.tsx index 511a724831..afb45b0834 100644 --- a/web/app/components/tools/mcp/create-card.tsx +++ b/web/app/components/tools/mcp/create-card.tsx @@ -34,8 +34,8 @@ const NewMCPCard = ({ handleCreate }: Props) => { const linkUrl = useMemo(() => { // TODO help link if (language.startsWith('zh_')) - return 'https://docs.dify.ai/zh-hans/guides/tools#ru-he-chuang-jian-zi-ding-yi-gong-ju' - return 'https://docs.dify.ai/en/guides/tools#how-to-create-custom-tools' + return 'https://docs.dify.ai/zh-hans/guides/tools/integrate-tool/mcp' + return 'https://docs.dify.ai/en/guides/tools/integrate-tool/mcp' }, [language]) const [showModal, setShowModal] = useState(false) diff --git a/web/app/components/tools/mcp/detail/content.tsx b/web/app/components/tools/mcp/detail/content.tsx index 5d2f5cb513..05e2572170 100644 --- a/web/app/components/tools/mcp/detail/content.tsx +++ b/web/app/components/tools/mcp/detail/content.tsx @@ -1,5 +1,6 @@ 'use client' -import React, { useCallback, useState } from 'react' +import React, { useCallback } from 'react' +import { useRouter } from 'next/navigation' import type { FC } from 'react' import { useBoolean } from 'ahooks' import { useTranslation } from 'react-i18next' @@ -41,9 +42,10 @@ const MCPDetailContent: FC = ({ onHide, }) => { const { t } = useTranslation() + const router = useRouter() const { isCurrentWorkspaceManager } = useAppContext() - const { data, isPending: isGettingTools } = useMCPTools(detail.is_team_authorization ? detail.id : '') + const { data, isFetching: isGettingTools } = useMCPTools(detail.is_team_authorization ? detail.id : '') const invalidateMCPTools = useInvalidateMCPTools() const { mutateAsync: updateTools, isPending: isUpdating } = useUpdateMCPTools() const { mutateAsync: authorizeMcp, isPending: isAuthorizing } = useAuthorizeMCP() @@ -54,6 +56,7 @@ const MCPDetailContent: FC = ({ return await updateTools(detail.id) invalidateMCPTools(detail.id) + onUpdate() }, [detail, updateTools]) const { mutate: updateMCP } = useUpdateMCP({ @@ -85,11 +88,11 @@ const MCPDetailContent: FC = ({ provider_id: detail.id, server_url: detail.server_url!, }) - // TODO - if ((res as any)?.result === 'success') { - hideUpdateModal() - onUpdate() - } + if (res.result === 'success') + handleUpdateTools() + + else if (res.authorization_url) + router.push(res.authorization_url) }, [detail, updateMCP, hideUpdateModal, onUpdate]) const handleUpdate = useCallback(async (data: any) => { @@ -117,8 +120,6 @@ const MCPDetailContent: FC = ({ } }, [detail, showDeleting, hideDeleting, hideDeleteConfirm, onUpdate]) - const [loading, setLoading] = useState(false) - if (!detail) return null @@ -150,7 +151,6 @@ const MCPDetailContent: FC = ({
From e42f84f723b2d1ee2e599ce3bd27fc8a32d384a0 Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 28 May 2025 16:14:27 +0800 Subject: [PATCH 55/58] feat: add obj and array type support --- .../model-provider-page/declarations.ts | 2 + .../tool-selector/reasoning-config-form.tsx | 66 ++++++++- .../tool-selector/schema-modal.tsx | 127 ++++++++++++++++++ 3 files changed, 188 insertions(+), 7 deletions(-) create mode 100644 web/app/components/plugins/plugin-detail-panel/tool-selector/schema-modal.tsx diff --git a/web/app/components/header/account-setting/model-provider-page/declarations.ts b/web/app/components/header/account-setting/model-provider-page/declarations.ts index 12dd9b3b5b..4da734361b 100644 --- a/web/app/components/header/account-setting/model-provider-page/declarations.ts +++ b/web/app/components/header/account-setting/model-provider-page/declarations.ts @@ -19,6 +19,8 @@ export enum FormTypeEnum { toolSelector = 'tool-selector', multiToolSelector = 'array[tools]', appSelector = 'app-selector', + object = 'object', + array = 'array', } export type FormOption = { diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx index 0d137502f4..72df4d65ac 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx @@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next' import produce from 'immer' import { RiArrowRightUpLine, + RiBracesLine, } from '@remixicon/react' import Tooltip from '@/app/components/base/tooltip' import Switch from '@/app/components/base/switch' @@ -22,6 +23,8 @@ import type { ToolVarInputs } from '@/app/components/workflow/nodes/tool/types' import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' import { VarType } from '@/app/components/workflow/types' import cn from '@/utils/classnames' +import { useBoolean } from 'ahooks' +import SchemaModal from './schema-modal' type Props = { value: Record @@ -133,7 +136,12 @@ const ReasoningConfigForm: React.FC = ({ } }, [onChange, value]) - const renderField = (schema: any) => { + const [isShowSchema, { + setTrue: showSchema, + setFalse: hideSchema, + }] = useBoolean(false) + + const renderField = (schema: any, showSchema: () => void) => { const { variable, label, @@ -149,26 +157,56 @@ const ReasoningConfigForm: React.FC = ({ popupContent={
{tooltip[language] || tooltip.en_US}
} - triggerClassName='ml-1 w-4 h-4' + triggerClassName='ml-0.5 w-4 h-4' asChild={false} /> )) const varInput = value[variable].value const isNumber = type === FormTypeEnum.textNumber const isSelect = type === FormTypeEnum.select const isFile = type === FormTypeEnum.file || type === FormTypeEnum.files + const isObject = type === FormTypeEnum.object + const isArray = type === FormTypeEnum.array + const isShowSchemaTooltip = isObject || isArray const isAppSelector = type === FormTypeEnum.appSelector const isModelSelector = type === FormTypeEnum.modelSelector // const isToolSelector = type === FormTypeEnum.toolSelector - const isString = !isNumber && !isSelect && !isFile && !isAppSelector && !isModelSelector + const isString = !isNumber && !isSelect && !isFile && !isAppSelector && !isModelSelector && !isObject && !isArray + const valueType = (() => { + if (isNumber) return VarType.number + if (isSelect) return VarType.string + if (isFile) return VarType.file + if (isObject) return VarType.object + if (isArray) return VarType.array + + return VarType.string + })() + return (
-
+
{label[language] || label.en_US} {required && ( * )} {tooltipContent} + · + {valueType} + {!isShowSchemaTooltip && ( + + Click to view parameter schema +
} + asChild={false}> +
+ +
+ + )} +
handleAutomatic(variable, !auto)}> {t('plugin.detailPanel.toolSelector.auto')} @@ -220,7 +258,7 @@ const ReasoningConfigForm: React.FC = ({ schema={schema} /> )} - {isFile && ( + {(isFile || isObject || isArray) && ( = ({ value={varInput?.value || []} onChange={handleFileChange(variable)} defaultVarKindType={VarKindType.variable} - filterVar={(varPayload: Var) => varPayload.type === VarType.file || varPayload.type === VarType.arrayFile} + filterVar={(varPayload: Var) => { + if(isFile) + return varPayload.type === VarType.file || varPayload.type === VarType.arrayFile + if(isObject) + return varPayload.type === VarType.object + if(isArray) + return [VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject, VarType.arrayFile].includes(varPayload.type) + return true + }} /> )} {isAppSelector && ( @@ -267,7 +313,13 @@ const ReasoningConfigForm: React.FC = ({ } return (
- {schemas.map(schema => renderField(schema))} + {!isShowSchema && schemas.map(schema => renderField(schema, showSchema))} + {isShowSchema && ( + + )}
) } diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/schema-modal.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/schema-modal.tsx new file mode 100644 index 0000000000..048869ca58 --- /dev/null +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/schema-modal.tsx @@ -0,0 +1,127 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import Modal from '@/app/components/base/modal' +import VisualEditor from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor' +import type { SchemaRoot } from '@/app/components/workflow/nodes/llm/types' +import { Type } from '@/app/components/workflow/nodes/llm/types' +import { MittProvider, VisualEditorContextProvider } from '@/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/context' +import { useTranslation } from 'react-i18next' +import { RiCloseLine } from '@remixicon/react' + +const testSchema: SchemaRoot = { + type: Type.object, + properties: { + after: { + type: Type.string, + description: 'The ID of the existing block that the new block should be appended after. If not provided, content will be appended at the end of the page.', + }, + content_block: { + type: Type.object, + properties: { + block_property: { + type: Type.string, + description: 'The block property of the block to be added. Possible property are `paragraph`,`heading_1`,`heading_2`,`heading_3`,`callout`,`todo`,`toggle`,`quote`, `bulleted_list_item`, `numbered_list_item`, other properties possible are `file`,`image`,`video` (link required).', + }, + bold: { + type: Type.boolean, + description: 'Indicates if the text is bold.', + }, + code: { + type: Type.boolean, + description: 'Indicates if the text is formatted as code.', + }, + color: { + type: Type.string, + description: 'The color of the text background or text itself.', + }, + content: { + anyOf: [ + { + type: Type.string, + }, + { + enum: [ + 'null', + ], + nullable: true, + }, + ], + description: 'The textual content of the rich text object. Required for paragraph, heading_1, heading_2, heading_3, callout, todo, toggle, quote.', + }, + italic: { + type: Type.boolean, + description: 'Indicates if the text is italic.', + }, + link: { + type: Type.string, + description: 'The URL of the rich text object or the file to be uploaded or image/video link', + }, + strikethrough: { + type: Type.boolean, + description: 'Indicates if the text has strikethrough.', + }, + underline: { + type: Type.boolean, + description: 'Indicates if the text is underlined.', + }, + }, + additionalProperties: false, + description: 'Child content to append to a page.', + }, + parent_block_id: { + type: Type.string, + description: 'The ID of the page which the children will be added.', + }, + }, + required: [ + 'content_block', + 'parent_block_id', + ], + additionalProperties: false, +} +type Props = { + isShow: boolean + onClose: () => void +} + +const SchemaModal: FC = ({ + isShow, + onClose, +}) => { + const { t } = useTranslation() + return ( + +
+ {/* Header */} +
+
+ {t('workflow.nodes.llm.jsonSchema.title')} +
+
+ +
+
+ {/* Content */} +
+ + + { + console.log('Schema changed:', schema) + }} + > + + +
+
+
+ ) +} +export default React.memo(SchemaModal) From f2a8af068092250de6c62aaf62b784ed4840f060 Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 28 May 2025 16:27:43 +0800 Subject: [PATCH 56/58] fix: some copywriting to i18n --- .../tool-selector/reasoning-config-form.tsx | 6 +++--- .../plugin-detail-panel/tool-selector/schema-modal.tsx | 2 +- web/i18n/en-US/workflow.ts | 2 ++ web/i18n/zh-Hans/workflow.ts | 2 ++ 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx index 72df4d65ac..83050cb448 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form.tsx @@ -185,7 +185,7 @@ const ReasoningConfigForm: React.FC = ({
- {label[language] || label.en_US} + {label[language] || label.en_US} {required && ( * )} @@ -194,8 +194,8 @@ const ReasoningConfigForm: React.FC = ({ {valueType} {!isShowSchemaTooltip && ( - Click to view parameter schema + popupContent={
+ {t('workflow.nodes.agent.clickToViewParameterSchema')}
} asChild={false}>
= ({ {/* Header */}
- {t('workflow.nodes.llm.jsonSchema.title')} + {t('workflow.nodes.agent.parameterSchema')}
diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index 0bd2d85586..e34ef91fc2 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -874,6 +874,8 @@ const translation = { install: 'Install', cancel: 'Cancel', }, + clickToViewParameterSchema: 'Click to view parameter schema', + parameterSchema: 'Parameter Schema', }, }, tracing: { diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index 77e0fb6412..6a300a8027 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -875,6 +875,8 @@ const translation = { install: '安装', cancel: '取消', }, + clickToViewParameterSchema: '点击查看参数 schema', + parameterSchema: '参数 Schema', }, }, tracing: { From 9adda90227dd0abf64efb39c2f498b75ebcbd95b Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 28 May 2025 16:35:52 +0800 Subject: [PATCH 57/58] feat: param schema support readonly --- .../plugin-detail-panel/tool-selector/schema-modal.tsx | 4 +--- .../json-schema-config-modal/visual-editor/hooks.ts | 4 +++- .../json-schema-config-modal/visual-editor/index.tsx | 6 ++++-- .../json-schema-config-modal/visual-editor/schema-node.tsx | 6 +++++- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/schema-modal.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/schema-modal.tsx index 8e7c09306c..748b8d87eb 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/schema-modal.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/schema-modal.tsx @@ -113,9 +113,7 @@ const SchemaModal: FC = ({ { - console.log('Schema changed:', schema) - }} + readOnly > diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts index 470a322b13..eb3dff83d8 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts @@ -6,6 +6,7 @@ import type { EditData } from './edit-card' import { ArrayType, type Field, Type } from '../../../types' import Toast from '@/app/components/base/toast' import { findPropertyWithPath } from '../../../utils' +import _ from 'lodash' type ChangeEventParams = { path: string[], @@ -19,7 +20,8 @@ type AddEventParams = { } export const useSchemaNodeOperations = (props: VisualEditorProps) => { - const { schema: jsonSchema, onChange } = props + const { schema: jsonSchema, onChange: doOnChange } = props + const onChange = doOnChange || _.noop const backupSchema = useVisualEditorStore(state => state.backupSchema) const setBackupSchema = useVisualEditorStore(state => state.setBackupSchema) const isAddingNewField = useVisualEditorStore(state => state.isAddingNewField) diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/index.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/index.tsx index 1df42532a6..50924b5629 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/index.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/index.tsx @@ -5,11 +5,12 @@ import { useSchemaNodeOperations } from './hooks' export type VisualEditorProps = { schema: SchemaRoot - onChange: (schema: SchemaRoot) => void + readOnly?: boolean + onChange?: (schema: SchemaRoot) => void } const VisualEditor: FC = (props) => { - const { schema } = props + const { schema, readOnly } = props useSchemaNodeOperations(props) return ( @@ -20,6 +21,7 @@ const VisualEditor: FC = (props) => { required={false} path={[]} depth={0} + readOnly={readOnly} />
) diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/schema-node.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/schema-node.tsx index 70a6b861ad..96bbf999db 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/schema-node.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/schema-node.tsx @@ -19,6 +19,7 @@ type SchemaNodeProps = { path: string[] parentPath?: string[] depth: number + readOnly?: boolean } // Support 10 levels of indentation @@ -57,6 +58,7 @@ const SchemaNode: FC = ({ path, parentPath, depth, + readOnly, }) => { const [isExpanded, setIsExpanded] = useState(true) const hoveringProperty = useVisualEditorStore(state => state.hoveringProperty) @@ -77,11 +79,13 @@ const SchemaNode: FC = ({ } const handleMouseEnter = () => { + if(!readOnly) return if (advancedEditing || isAddingNewField) return setHoveringPropertyDebounced(path.join('.')) } const handleMouseLeave = () => { + if(!readOnly) return if (advancedEditing || isAddingNewField) return setHoveringPropertyDebounced(null) } @@ -183,7 +187,7 @@ const SchemaNode: FC = ({ )} { - depth === 0 && !isAddingNewField && ( + !readOnly && depth === 0 && !isAddingNewField && ( ) } From 18eb5ad33fdf827bb0b2f4f934fffbc701fcf9d2 Mon Sep 17 00:00:00 2001 From: jZonG Date: Wed, 28 May 2025 16:41:25 +0800 Subject: [PATCH 58/58] replace searchParams after redirection --- web/app/components/tools/mcp/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web/app/components/tools/mcp/index.tsx b/web/app/components/tools/mcp/index.tsx index 1b5d6a9fc5..e4ca24120a 100644 --- a/web/app/components/tools/mcp/index.tsx +++ b/web/app/components/tools/mcp/index.tsx @@ -1,6 +1,6 @@ 'use client' import { useEffect, useMemo, useState } from 'react' -import { useSearchParams } from 'next/navigation' +import { usePathname, useRouter, useSearchParams } from 'next/navigation' import NewMCPCard from './create-card' import MCPCard from './provider-card' import MCPDetailPanel from './detail/provider-detail' @@ -39,6 +39,8 @@ function renderDefaultCard() { const MCPList = ({ searchText, }: Props) => { + const router = useRouter() + const pathname = usePathname() const searchParams = useSearchParams() const authCode = searchParams.get('code') || '' const providerID = searchParams.get('state') || '' @@ -78,6 +80,7 @@ const MCPList = ({ const handleUpdateAuthorization = async (providerID: string, code: string) => { const targetProvider = list.find(provider => provider.id === providerID) + router.replace(pathname) if (!targetProvider) return await updateMCPAuthorizationToken({ provider_id: providerID,