From 4123c0a9609295b1d6697c8263c182d38b1b97be Mon Sep 17 00:00:00 2001 From: jZonG Date: Wed, 7 May 2025 19:53:45 +0800 Subject: [PATCH 001/126] 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 002/126] 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 003/126] 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 004/126] 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 005/126] 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 006/126] 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 007/126] 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 008/126] 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 009/126] 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 010/126] 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 011/126] 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 012/126] 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 013/126] 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 014/126] 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 015/126] 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 016/126] 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 017/126] 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 018/126] 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 019/126] 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 020/126] 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 021/126] 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 022/126] 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 023/126] 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 024/126] 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 025/126] 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 026/126] 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 027/126] 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 028/126] 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 029/126] 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 030/126] 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 031/126] 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 032/126] 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 033/126] 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 034/126] 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 037/126] 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 038/126] 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 039/126] 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 047/126] 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 048/126] 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 049/126] 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 050/126] 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 051/126] 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 052/126] 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 055/126] 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 056/126] 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 057/126] 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 058/126] 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, From 9350646c97bffd83a5d9dfdacd2615cd7cba25d2 Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 28 May 2025 16:57:15 +0800 Subject: [PATCH 059/126] fix: not show mcp tools icon in node --- .../nodes/agent/components/tool-icon.tsx | 54 ++++++++++++------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/web/app/components/workflow/nodes/agent/components/tool-icon.tsx b/web/app/components/workflow/nodes/agent/components/tool-icon.tsx index b94258855a..8616f34200 100644 --- a/web/app/components/workflow/nodes/agent/components/tool-icon.tsx +++ b/web/app/components/workflow/nodes/agent/components/tool-icon.tsx @@ -2,10 +2,11 @@ import Tooltip from '@/app/components/base/tooltip' import Indicator from '@/app/components/header/indicator' import classNames from '@/utils/classnames' import { memo, useMemo, useRef, useState } from 'react' -import { useAllBuiltInTools, useAllCustomTools, useAllWorkflowTools } from '@/service/use-tools' +import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools } from '@/service/use-tools' import { getIconFromMarketPlace } from '@/utils/get-icon' import { useTranslation } from 'react-i18next' import { Group } from '@/app/components/base/icons/src/vender/other' +import AppIcon from '@/app/components/base/app-icon' type Status = 'not-installed' | 'not-authorized' | undefined @@ -19,19 +20,21 @@ export const ToolIcon = memo(({ providerName }: ToolIconProps) => { const { data: buildInTools } = useAllBuiltInTools() const { data: customTools } = useAllCustomTools() const { data: workflowTools } = useAllWorkflowTools() - const isDataReady = !!buildInTools && !!customTools && !!workflowTools + const { data: mcpTools } = useAllMCPTools() + const isDataReady = !!buildInTools && !!customTools && !!workflowTools && !!mcpTools const currentProvider = useMemo(() => { - const mergedTools = [...(buildInTools || []), ...(customTools || []), ...(workflowTools || [])] + const mergedTools = [...(buildInTools || []), ...(customTools || []), ...(workflowTools || []), ...(mcpTools || [])] return mergedTools.find((toolWithProvider) => { - return toolWithProvider.name === providerName + return toolWithProvider.name === providerName || toolWithProvider.id === providerName }) - }, [buildInTools, customTools, providerName, workflowTools]) + }, [buildInTools, customTools, providerName, workflowTools, mcpTools]) + const providerNameParts = providerName.split('/') const author = providerNameParts[0] const name = providerNameParts[1] const icon = useMemo(() => { if (!isDataReady) return '' - if (currentProvider) return currentProvider.icon as string + if (currentProvider) return currentProvider.icon const iconFromMarketPlace = getIconFromMarketPlace(`${author}/${name}`) return iconFromMarketPlace }, [author, currentProvider, name, isDataReady]) @@ -62,19 +65,32 @@ export const ToolIcon = memo(({ providerName }: ToolIconProps) => { )} ref={containerRef} > - {(!iconFetchError && isDataReady) - - ? tool icon setIconFetchError(true)} - /> - : - } + {(() => { + if (iconFetchError || !icon) + return + if (typeof icon === 'string') { + return tool icon setIconFetchError(true)} + /> + } + if (typeof icon === 'object') { + return + } + return + })()} {indicator && }
From 29f9faf04943bc64d6adf6fd6b0431fa9ced11f3 Mon Sep 17 00:00:00 2001 From: jZonG Date: Wed, 28 May 2025 17:31:57 +0800 Subject: [PATCH 060/126] fix scroll --- web/app/components/tools/mcp/detail/content.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/app/components/tools/mcp/detail/content.tsx b/web/app/components/tools/mcp/detail/content.tsx index fd1457d40c..cb2263809c 100644 --- a/web/app/components/tools/mcp/detail/content.tsx +++ b/web/app/components/tools/mcp/detail/content.tsx @@ -179,7 +179,7 @@ const MCPDetailContent: FC = ({ )}
-
+
{((detail.is_team_authorization && isGettingTools) || isUpdating) && ( <>
@@ -217,7 +217,7 @@ const MCPDetailContent: FC = ({
-
+
{toolList.map(tool => ( Date: Thu, 29 May 2025 10:41:56 +0800 Subject: [PATCH 061/126] fix: search ui broken in explore page --- web/app/components/plugins/marketplace/search-box/index.tsx | 2 +- web/app/components/workflow/block-selector/index.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/web/app/components/plugins/marketplace/search-box/index.tsx b/web/app/components/plugins/marketplace/search-box/index.tsx index 0c09660195..5f19afbba6 100644 --- a/web/app/components/plugins/marketplace/search-box/index.tsx +++ b/web/app/components/plugins/marketplace/search-box/index.tsx @@ -35,7 +35,7 @@ const SearchBox = ({ className='z-[11] flex items-center' >
= ({ onTagsChange={setTags} size='small' placeholder={t('plugin.searchTools')!} + inputClassName='grow' /> )}
From 5ac413ff69ee79d4e6d9f8527d5e2e16915b1755 Mon Sep 17 00:00:00 2001 From: jZonG Date: Thu, 29 May 2025 16:40:25 +0800 Subject: [PATCH 062/126] fix: timestamp & updating loader --- web/app/components/tools/mcp/detail/content.tsx | 6 +++--- web/app/components/tools/mcp/provider-card.tsx | 2 +- 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 cb2263809c..6ffbe15184 100644 --- a/web/app/components/tools/mcp/detail/content.tsx +++ b/web/app/components/tools/mcp/detail/content.tsx @@ -194,7 +194,7 @@ const MCPDetailContent: FC = ({
)} - {detail.is_team_authorization && !isGettingTools && !toolList.length && ( + {!isUpdating && detail.is_team_authorization && !isGettingTools && !toolList.length && (
{t('tools.mcp.toolsEmpty')}
)} - {!isGettingTools && toolList.length > 0 && ( + {!isUpdating && !isGettingTools && toolList.length > 0 && ( <>
@@ -228,7 +228,7 @@ const MCPDetailContent: FC = ({ )} - {!detail.is_team_authorization && ( + {!isUpdating && !detail.is_team_authorization && (
{!isAuthorizing &&
{t('tools.mcp.authorizingRequired')}
} {isAuthorizing &&
{t('tools.mcp.authorizing')}
} diff --git a/web/app/components/tools/mcp/provider-card.tsx b/web/app/components/tools/mcp/provider-card.tsx index a28b0a1ca7..9ff579ef96 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.updated_at!)}`}
+
{`${t('tools.mcp.updateTime')} ${formatTimeFromNow(data.updated_at! * 1000)}`}
From d2a4e3eedc14964b041a124e73b1c01e8df602d6 Mon Sep 17 00:00:00 2001 From: jZonG Date: Thu, 29 May 2025 16:45:30 +0800 Subject: [PATCH 063/126] modify api --- web/app/components/tools/mcp/detail/content.tsx | 1 - web/app/components/tools/mcp/index.tsx | 2 -- web/service/use-tools.ts | 4 ++-- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/web/app/components/tools/mcp/detail/content.tsx b/web/app/components/tools/mcp/detail/content.tsx index 6ffbe15184..c3df795836 100644 --- a/web/app/components/tools/mcp/detail/content.tsx +++ b/web/app/components/tools/mcp/detail/content.tsx @@ -86,7 +86,6 @@ const MCPDetailContent: FC = ({ return const res = await authorizeMcp({ provider_id: detail.id, - server_url: detail.server_url!, }) if (res.result === 'success') handleUpdateTools() diff --git a/web/app/components/tools/mcp/index.tsx b/web/app/components/tools/mcp/index.tsx index e4ca24120a..c5d06d571b 100644 --- a/web/app/components/tools/mcp/index.tsx +++ b/web/app/components/tools/mcp/index.tsx @@ -70,7 +70,6 @@ const MCPList = ({ setCurrentProviderID(provider.id) await authorizeMcp({ provider_id: provider.id, - server_url: provider.server_url!, }) await refetch() // update authorization in list await updateTools(provider.id) @@ -84,7 +83,6 @@ const MCPList = ({ if (!targetProvider) return await updateMCPAuthorizationToken({ provider_id: providerID, - server_url: targetProvider.server_url!, authorization_code: code, }) await refetch() diff --git a/web/service/use-tools.ts b/web/service/use-tools.ts index 6c50112fa0..64a3ce7a1f 100644 --- a/web/service/use-tools.ts +++ b/web/service/use-tools.ts @@ -140,7 +140,7 @@ export const useDeleteMCP = ({ export const useAuthorizeMCP = () => { return useMutation({ mutationKey: [NAME_SPACE, 'authorize-mcp'], - mutationFn: (payload: { provider_id: string; server_url: string }) => { + mutationFn: (payload: { provider_id: string; }) => { return post<{ result?: string; authorization_url?: string }>('/workspaces/current/tool-provider/mcp/auth', { body: payload, }) @@ -151,7 +151,7 @@ export const useAuthorizeMCP = () => { export const useUpdateMCPAuthorizationToken = () => { return useMutation({ mutationKey: [NAME_SPACE, 'refresh-mcp-server-code'], - mutationFn: (payload: { provider_id: string; server_url: string; authorization_code: string }) => { + mutationFn: (payload: { provider_id: string; authorization_code: string }) => { return get('/workspaces/current/tool-provider/mcp/token', { params: { ...payload, From 953746bf7e03b2a08c7cd015acefba63f570e216 Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 29 May 2025 16:58:13 +0800 Subject: [PATCH 064/126] feat: use api schema --- .../tool-selector/reasoning-config-form.tsx | 17 ++++- .../tool-selector/schema-modal.tsx | 76 +------------------ 2 files changed, 16 insertions(+), 77 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 83050cb448..49b099662a 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 @@ -25,6 +25,7 @@ import { VarType } from '@/app/components/workflow/types' import cn from '@/utils/classnames' import { useBoolean } from 'ahooks' import SchemaModal from './schema-modal' +import type { SchemaRoot } from '@/app/components/workflow/nodes/llm/types' type Props = { value: Record @@ -141,7 +142,10 @@ const ReasoningConfigForm: React.FC = ({ setFalse: hideSchema, }] = useBoolean(false) - const renderField = (schema: any, showSchema: () => void) => { + const [schema, setSchema] = useState(null) + console.log(schema) + + const renderField = (schema: any, showSchema: (schema: SchemaRoot) => void) => { const { variable, label, @@ -150,6 +154,7 @@ const ReasoningConfigForm: React.FC = ({ type, scope, url, + input_schema, } = schema const auto = value[variable]?.auto const tooltipContent = (tooltip && ( @@ -192,7 +197,7 @@ const ReasoningConfigForm: React.FC = ({ {tooltipContent} · {valueType} - {!isShowSchemaTooltip && ( + {isShowSchemaTooltip && ( {t('workflow.nodes.agent.clickToViewParameterSchema')} @@ -200,7 +205,7 @@ const ReasoningConfigForm: React.FC = ({ asChild={false}>
showSchema(input_schema as SchemaRoot)} >
@@ -313,10 +318,14 @@ const ReasoningConfigForm: React.FC = ({ } return (
- {!isShowSchema && schemas.map(schema => renderField(schema, showSchema))} + {!isShowSchema && schemas.map(schema => renderField(schema, (s: SchemaRoot) => { + setSchema(s) + 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 index 748b8d87eb..de4e96a8b7 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 @@ -4,89 +4,19 @@ 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 + schema: SchemaRoot onClose: () => void } const SchemaModal: FC = ({ isShow, + schema, onClose, }) => { const { t } = useTranslation() @@ -112,7 +42,7 @@ const SchemaModal: FC = ({ From 049f904af84c2fe2b1f6f75962f45d32e5d2754e Mon Sep 17 00:00:00 2001 From: jZonG Date: Thu, 29 May 2025 17:22:19 +0800 Subject: [PATCH 065/126] fix: auto authorizing --- web/app/components/tools/mcp/index.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/web/app/components/tools/mcp/index.tsx b/web/app/components/tools/mcp/index.tsx index c5d06d571b..fa069ac7f0 100644 --- a/web/app/components/tools/mcp/index.tsx +++ b/web/app/components/tools/mcp/index.tsx @@ -68,13 +68,18 @@ const MCPList = ({ const handleCreate = async (provider: ToolWithProvider) => { await refetch() // update list setCurrentProviderID(provider.id) - await authorizeMcp({ + const res = await authorizeMcp({ provider_id: provider.id, }) - await refetch() // update authorization in list - await updateTools(provider.id) - invalidateMCPTools(provider.id) - await refetch() // update tool list in provider list + if (res.result === 'success') { + await refetch() // update authorization in list + await updateTools(provider.id) + invalidateMCPTools(provider.id) + await refetch() // update tool list in provider list + } + else if (res.authorization_url) { + router.push(res.authorization_url) + } } const handleUpdateAuthorization = async (providerID: string, code: string) => { From f1d964288dfb2129f509b30938959a56ef7d731a Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 30 May 2025 16:48:46 +0800 Subject: [PATCH 066/126] fix: schema title use prop name --- .../tool-selector/reasoning-config-form.tsx | 10 ++++++---- .../plugin-detail-panel/tool-selector/schema-modal.tsx | 3 +++ .../json-schema-config-modal/visual-editor/index.tsx | 3 ++- 3 files changed, 11 insertions(+), 5 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 49b099662a..017e0eb7aa 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 @@ -143,9 +143,9 @@ const ReasoningConfigForm: React.FC = ({ }] = useBoolean(false) const [schema, setSchema] = useState(null) - console.log(schema) + const [schemaRootName, setSchemaRootName] = useState('') - const renderField = (schema: any, showSchema: (schema: SchemaRoot) => void) => { + const renderField = (schema: any, showSchema: (schema: SchemaRoot, rootName: string) => void) => { const { variable, label, @@ -205,7 +205,7 @@ const ReasoningConfigForm: React.FC = ({ asChild={false}>
showSchema(input_schema as SchemaRoot)} + onClick={() => showSchema(input_schema as SchemaRoot, label[language] || label.en_US)} >
@@ -318,14 +318,16 @@ const ReasoningConfigForm: React.FC = ({ } return (
- {!isShowSchema && schemas.map(schema => renderField(schema, (s: SchemaRoot) => { + {!isShowSchema && schemas.map(schema => renderField(schema, (s: SchemaRoot, rootName: string) => { setSchema(s) + setSchemaRootName(rootName) 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 index de4e96a8b7..d9dd907816 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 @@ -11,12 +11,14 @@ import { RiCloseLine } from '@remixicon/react' type Props = { isShow: boolean schema: SchemaRoot + rootName: string onClose: () => void } const SchemaModal: FC = ({ isShow, schema, + rootName, onClose, }) => { const { t } = useTranslation() @@ -43,6 +45,7 @@ const SchemaModal: FC = ({ 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 50924b5629..28b9035edd 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,6 +5,7 @@ import { useSchemaNodeOperations } from './hooks' export type VisualEditorProps = { schema: SchemaRoot + rootName?: string readOnly?: boolean onChange?: (schema: SchemaRoot) => void } @@ -16,7 +17,7 @@ const VisualEditor: FC = (props) => { return (
Date: Tue, 3 Jun 2025 14:28:51 +0800 Subject: [PATCH 067/126] chore: can choose mcp --- web/app/components/workflow/block-selector/tabs.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/web/app/components/workflow/block-selector/tabs.tsx b/web/app/components/workflow/block-selector/tabs.tsx index 7e2b768afc..f32ab89692 100644 --- a/web/app/components/workflow/block-selector/tabs.tsx +++ b/web/app/components/workflow/block-selector/tabs.tsx @@ -83,7 +83,6 @@ const Tabs: FC = ({ customTools={customTools || []} workflowTools={workflowTools || []} mcpTools={mcpTools || []} - isHideMCPTools /> ) } From 7a76f38d90b14837f4923d3796a04a3e29c54f6d Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 5 Jun 2025 11:25:23 +0800 Subject: [PATCH 068/126] feat: hide mcp tab agentey not support --- .../multiple-tool-selector/index.tsx | 5 +- .../tool-selector/index.tsx | 3 + web/app/components/plugins/types.ts | 5 ++ .../workflow/block-selector/tabs.tsx | 1 + .../workflow/block-selector/tool-picker.tsx | 4 +- .../block-selector/tool/action-item.tsx | 1 + .../workflow/block-selector/types.ts | 3 + .../components/agent-strategy-selector.tsx | 2 + .../nodes/_base/components/agent-strategy.tsx | 9 ++- .../components/workflow/nodes/agent/panel.tsx | 5 +- .../components/workflow/nodes/agent/types.ts | 2 + .../workflow/nodes/agent/use-config.ts | 2 + web/app/components/workflow/types.ts | 2 + web/utils/plugin-version-feature.spec.ts | 26 +++++++ web/utils/plugin-version-feature.ts | 10 +++ web/utils/semver.spec.ts | 75 +++++++++++++++++++ web/utils/semver.ts | 4 + 17 files changed, 155 insertions(+), 4 deletions(-) create mode 100644 web/utils/plugin-version-feature.spec.ts create mode 100644 web/utils/plugin-version-feature.ts create mode 100644 web/utils/semver.spec.ts 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 e2b6b06fd6..114b47a431 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 @@ -26,6 +26,7 @@ type Props = { nodeOutputVars: NodeOutPutVar[], availableNodes: Node[], nodeId?: string + canChooseMCPTool?: boolean } const MultipleToolSelector = ({ @@ -40,6 +41,7 @@ const MultipleToolSelector = ({ nodeOutputVars, availableNodes, nodeId, + canChooseMCPTool, }: Props) => { const { t } = useTranslation() const enabledCount = value.filter(item => item.enabled).length @@ -155,7 +157,7 @@ const MultipleToolSelector = ({ } panelShowState={panelShowState} onPanelShowStateChange={setPanelShowState} - + canChooseMCPTool={canChooseMCPTool} /> {value.length === 0 && (
{t('plugin.detailPanel.toolSelector.empty')}
@@ -173,6 +175,7 @@ const MultipleToolSelector = ({ onSelectMultiple={handleAddMultiple} onDelete={() => handleDelete(index)} supportEnableSwitch + canChooseMCPTool={canChooseMCPTool} />
))} 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 5ec1797351..fba99f4e68 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 @@ -68,6 +68,7 @@ type Props = { nodeOutputVars: NodeOutPutVar[], availableNodes: Node[], nodeId?: string, + canChooseMCPTool?: boolean, } const ToolSelector: FC = ({ value, @@ -88,6 +89,7 @@ const ToolSelector: FC = ({ nodeOutputVars, availableNodes, nodeId = '', + canChooseMCPTool, }) => { const { t } = useTranslation() const [isShow, onShowChange] = useState(false) @@ -308,6 +310,7 @@ const ToolSelector: FC = ({ onSelectMultiple={handleSelectMultipleTool} scope={scope} selectedTools={selectedTools} + canChooseMCPTool={canChooseMCPTool} />
diff --git a/web/app/components/plugins/types.ts b/web/app/components/plugins/types.ts index f552d7c17a..4e97d71151 100644 --- a/web/app/components/plugins/types.ts +++ b/web/app/components/plugins/types.ts @@ -449,9 +449,14 @@ export type StrategyDeclaration = { strategies: StrategyDetail[] } +export type PluginMeta = { + version: string // the version of dify sdk +} + export type StrategyPluginDetail = { provider: string plugin_unique_identifier: string plugin_id: string declaration: StrategyDeclaration + meta: PluginMeta } diff --git a/web/app/components/workflow/block-selector/tabs.tsx b/web/app/components/workflow/block-selector/tabs.tsx index f32ab89692..d4e6a40163 100644 --- a/web/app/components/workflow/block-selector/tabs.tsx +++ b/web/app/components/workflow/block-selector/tabs.tsx @@ -83,6 +83,7 @@ const Tabs: FC = ({ customTools={customTools || []} workflowTools={workflowTools || []} mcpTools={mcpTools || []} + isHideMCPTools={false} /> ) } diff --git a/web/app/components/workflow/block-selector/tool-picker.tsx b/web/app/components/workflow/block-selector/tool-picker.tsx index bd24e204ce..95ac24f599 100644 --- a/web/app/components/workflow/block-selector/tool-picker.tsx +++ b/web/app/components/workflow/block-selector/tool-picker.tsx @@ -39,6 +39,7 @@ type Props = { supportAddCustomTool?: boolean scope?: string selectedTools?: ToolValue[] + canChooseMCPTool?: boolean } const ToolPicker: FC = ({ @@ -54,6 +55,7 @@ const ToolPicker: FC = ({ scope = 'all', selectedTools, panelClassName, + canChooseMCPTool, }) => { const { t } = useTranslation() const [searchText, setSearchText] = useState('') @@ -175,8 +177,8 @@ const ToolPicker: FC = ({ customTools={customToolList || []} workflowTools={workflowToolList || []} mcpTools={mcpTools || []} - selectedTools={selectedTools} + isHideMCPTools={!canChooseMCPTool} />
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 9a52e820f7..7c0ebd7465 100644 --- a/web/app/components/workflow/block-selector/tool/action-item.tsx +++ b/web/app/components/workflow/block-selector/tool/action-item.tsx @@ -69,6 +69,7 @@ const ToolItem: FC = ({ output_schema: payload.output_schema, paramSchemas: payload.parameters, params, + meta: provider.meta, }) }} > diff --git a/web/app/components/workflow/block-selector/types.ts b/web/app/components/workflow/block-selector/types.ts index 398a7e0c71..42ab833a5b 100644 --- a/web/app/components/workflow/block-selector/types.ts +++ b/web/app/components/workflow/block-selector/types.ts @@ -1,3 +1,5 @@ +import type { PluginMeta } from '../../plugins/types' + export enum TabsEnum { Blocks = 'blocks', Tools = 'tools', @@ -31,6 +33,7 @@ export type ToolDefaultValue = { params: Record paramSchemas: Record[] output_schema: Record + meta: PluginMeta } export type ToolValue = { diff --git a/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx b/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx index dd6a1c6a22..ca09cb2f3e 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx @@ -67,6 +67,7 @@ function formatStrategy(input: StrategyPluginDetail[], getIcon: (i: string) => s icon: getIcon(item.declaration.identity.icon), label: item.declaration.identity.label as any, type: CollectionType.all, + meta: item.meta, tools: item.declaration.strategies.map(strategy => ({ name: strategy.identity.name, author: strategy.identity.author, @@ -210,6 +211,7 @@ export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) => agent_strategy_label: tool!.tool_label, agent_output_schema: tool!.output_schema, plugin_unique_identifier: tool!.provider_id, + meta: tool!.meta, }) setOpen(false) }} diff --git a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx index 1e9612b7c7..b9d42558c1 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx @@ -22,6 +22,8 @@ import type { Node } from 'reactflow' import { useContext } from 'use-context-selector' import I18n from '@/context/i18n' import { LanguagesSupported } from '@/i18n/language' +import type { PluginMeta } from '@/app/components/plugins/types' +import { noop } from 'lodash' export type Strategy = { agent_strategy_provider_name: string @@ -29,6 +31,7 @@ export type Strategy = { agent_strategy_label: string agent_output_schema: Record plugin_unique_identifier: string + meta?: PluginMeta } export type AgentStrategyProps = { @@ -40,6 +43,7 @@ export type AgentStrategyProps = { nodeOutputVars?: NodeOutPutVar[], availableNodes?: Node[], nodeId?: string + canChooseMCPTool?: boolean } type CustomSchema = Omit & { type: Type } & Field @@ -50,7 +54,7 @@ type MultipleToolSelectorSchema = CustomSchema<'array[tools]'> type CustomField = ToolSelectorSchema | MultipleToolSelectorSchema export const AgentStrategy = memo((props: AgentStrategyProps) => { - const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange, nodeOutputVars, availableNodes, nodeId } = props + const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange, nodeOutputVars, availableNodes, nodeId, canChooseMCPTool } = props const { t } = useTranslation() const { locale } = useContext(I18n) const defaultModel = useDefaultModel(ModelTypeEnum.textGeneration) @@ -166,6 +170,8 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => { value={value} onSelect={item => onChange(item)} onDelete={() => onChange(null)} + canChooseMCPTool={canChooseMCPTool} + onSelectMultiple={noop} /> ) @@ -187,6 +193,7 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => { onChange={onChange} supportCollapse required={schema.required} + canChooseMCPTool={canChooseMCPTool} /> ) } diff --git a/web/app/components/workflow/nodes/agent/panel.tsx b/web/app/components/workflow/nodes/agent/panel.tsx index f92e92dbcb..a213ffc24b 100644 --- a/web/app/components/workflow/nodes/agent/panel.tsx +++ b/web/app/components/workflow/nodes/agent/panel.tsx @@ -53,6 +53,7 @@ const AgentPanel: FC> = (props) => { varInputs, outputSchema, handleMemoryChange, + canChooseMCPTool, } = useConfig(props.id, props.data) const { t } = useTranslation() const nodeInfo = useMemo(() => { @@ -79,7 +80,7 @@ const AgentPanel: FC> = (props) => { })() const resetEditor = useStore(s => s.setControlPromptEditorRerenderKey) - + console.log(canChooseMCPTool) return
> = (props) => { agent_strategy_label: inputs.agent_strategy_label!, agent_output_schema: inputs.output_schema, plugin_unique_identifier: inputs.plugin_unique_identifier!, + meta: inputs.meta, } : undefined} onStrategyChange={(strategy) => { setInputs({ @@ -102,6 +104,7 @@ const AgentPanel: FC> = (props) => { agent_strategy_label: strategy?.agent_strategy_label, output_schema: strategy!.agent_output_schema, plugin_unique_identifier: strategy!.plugin_unique_identifier, + meta: strategy?.meta, }) resetEditor(Date.now()) }} diff --git a/web/app/components/workflow/nodes/agent/types.ts b/web/app/components/workflow/nodes/agent/types.ts index ca8bb5e71d..e50586bd27 100644 --- a/web/app/components/workflow/nodes/agent/types.ts +++ b/web/app/components/workflow/nodes/agent/types.ts @@ -1,11 +1,13 @@ import type { CommonNodeType, Memory } from '@/app/components/workflow/types' import type { ToolVarInputs } from '../tool/types' +import type { PluginMeta } from '@/app/components/plugins/types' export type AgentNodeType = CommonNodeType & { agent_strategy_provider_name?: string agent_strategy_name?: string agent_strategy_label?: string agent_parameters?: ToolVarInputs + meta?: PluginMeta output_schema: Record plugin_unique_identifier?: string memory?: Memory diff --git a/web/app/components/workflow/nodes/agent/use-config.ts b/web/app/components/workflow/nodes/agent/use-config.ts index 8196caa3f5..1dc20fcbcd 100644 --- a/web/app/components/workflow/nodes/agent/use-config.ts +++ b/web/app/components/workflow/nodes/agent/use-config.ts @@ -14,6 +14,7 @@ import type { Memory, Var } from '../../types' import { VarType as VarKindType } from '../../types' import useAvailableVarList from '../_base/hooks/use-available-var-list' import produce from 'immer' +import { isSupportMCP } from '@/utils/plugin-version-feature' export type StrategyStatus = { plugin: { @@ -214,6 +215,7 @@ const useConfig = (id: string, payload: AgentNodeType) => { outputSchema, handleMemoryChange, isChatMode, + canChooseMCPTool: isSupportMCP(inputs.meta?.version), } } diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index 884bdfbd10..56cd9ffdf9 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -15,6 +15,7 @@ import type { } from '@/app/components/workflow/nodes/_base/components/error-handle/types' import type { WorkflowRetryConfig } from '@/app/components/workflow/nodes/_base/components/retry/types' import type { StructuredOutput } from '@/app/components/workflow/nodes/llm/types' +import type { PluginMeta } from '../plugins/types' export enum BlockEnum { Start = 'start', @@ -400,6 +401,7 @@ export type MoreInfo = { export type ToolWithProvider = Collection & { tools: Tool[] + meta: PluginMeta } export enum SupportUploadFileTypes { diff --git a/web/utils/plugin-version-feature.spec.ts b/web/utils/plugin-version-feature.spec.ts new file mode 100644 index 0000000000..12ca239aa9 --- /dev/null +++ b/web/utils/plugin-version-feature.spec.ts @@ -0,0 +1,26 @@ +import { isSupportMCP } from './plugin-version-feature' + +describe('plugin-version-feature', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + describe('isSupportMCP', () => { + it('should call isEqualOrLaterThanVersion with the correct parameters', () => { + expect(isSupportMCP('0.0.3')).toBe(true) + expect(isSupportMCP('1.0.0')).toBe(true) + }) + + it('should return true when version is equal to the supported MCP version', () => { + const mockVersion = '0.0.2' + const result = isSupportMCP(mockVersion) + expect(result).toBe(true) + }) + + it('should return false when version is less than the supported MCP version', () => { + const mockVersion = '0.0.1' + const result = isSupportMCP(mockVersion) + expect(result).toBe(false) + }) + }) +}) diff --git a/web/utils/plugin-version-feature.ts b/web/utils/plugin-version-feature.ts new file mode 100644 index 0000000000..51d366bf9c --- /dev/null +++ b/web/utils/plugin-version-feature.ts @@ -0,0 +1,10 @@ +import { isEqualOrLaterThanVersion } from './semver' + +const SUPPORT_MCP_VERSION = '0.0.2' + +export const isSupportMCP = (version?: string): boolean => { + if (!version) + return false + + return isEqualOrLaterThanVersion(version, SUPPORT_MCP_VERSION) +} diff --git a/web/utils/semver.spec.ts b/web/utils/semver.spec.ts new file mode 100644 index 0000000000..c2188a976c --- /dev/null +++ b/web/utils/semver.spec.ts @@ -0,0 +1,75 @@ +import { compareVersion, getLatestVersion, isEqualOrLaterThanVersion } from './semver' + +describe('semver utilities', () => { + describe('getLatestVersion', () => { + it('should return the latest version from a list of versions', () => { + expect(getLatestVersion(['1.0.0', '1.1.0', '1.0.1'])).toBe('1.1.0') + expect(getLatestVersion(['2.0.0', '1.9.9', '1.10.0'])).toBe('2.0.0') + expect(getLatestVersion(['1.0.0-alpha', '1.0.0-beta', '1.0.0'])).toBe('1.0.0') + }) + + it('should handle patch versions correctly', () => { + expect(getLatestVersion(['1.0.1', '1.0.2', '1.0.0'])).toBe('1.0.2') + expect(getLatestVersion(['1.0.10', '1.0.9', '1.0.11'])).toBe('1.0.11') + }) + + it('should handle mixed version formats', () => { + expect(getLatestVersion(['v1.0.0', '1.1.0', 'v1.2.0'])).toBe('v1.2.0') + expect(getLatestVersion(['1.0.0-rc.1', '1.0.0', '1.0.0-beta'])).toBe('1.0.0') + }) + + it('should return the only version if only one version is provided', () => { + expect(getLatestVersion(['1.0.0'])).toBe('1.0.0') + }) + }) + + describe('compareVersion', () => { + it('should return 1 when first version is greater', () => { + expect(compareVersion('1.1.0', '1.0.0')).toBe(1) + expect(compareVersion('2.0.0', '1.9.9')).toBe(1) + expect(compareVersion('1.0.1', '1.0.0')).toBe(1) + }) + + it('should return -1 when first version is less', () => { + expect(compareVersion('1.0.0', '1.1.0')).toBe(-1) + expect(compareVersion('1.9.9', '2.0.0')).toBe(-1) + expect(compareVersion('1.0.0', '1.0.1')).toBe(-1) + }) + + it('should return 0 when versions are equal', () => { + expect(compareVersion('1.0.0', '1.0.0')).toBe(0) + expect(compareVersion('2.1.3', '2.1.3')).toBe(0) + }) + + it('should handle pre-release versions correctly', () => { + expect(compareVersion('1.0.0-beta', '1.0.0-alpha')).toBe(1) + expect(compareVersion('1.0.0', '1.0.0-beta')).toBe(1) + expect(compareVersion('1.0.0-alpha', '1.0.0-beta')).toBe(-1) + }) + }) + + describe('isEqualOrLaterThanVersion', () => { + it('should return true when baseVersion is greater than targetVersion', () => { + expect(isEqualOrLaterThanVersion('1.1.0', '1.0.0')).toBe(true) + expect(isEqualOrLaterThanVersion('2.0.0', '1.9.9')).toBe(true) + expect(isEqualOrLaterThanVersion('1.0.1', '1.0.0')).toBe(true) + }) + + it('should return true when baseVersion is equal to targetVersion', () => { + expect(isEqualOrLaterThanVersion('1.0.0', '1.0.0')).toBe(true) + expect(isEqualOrLaterThanVersion('2.1.3', '2.1.3')).toBe(true) + }) + + it('should return false when baseVersion is less than targetVersion', () => { + expect(isEqualOrLaterThanVersion('1.0.0', '1.1.0')).toBe(false) + expect(isEqualOrLaterThanVersion('1.9.9', '2.0.0')).toBe(false) + expect(isEqualOrLaterThanVersion('1.0.0', '1.0.1')).toBe(false) + }) + + it('should handle pre-release versions correctly', () => { + expect(isEqualOrLaterThanVersion('1.0.0', '1.0.0-beta')).toBe(true) + expect(isEqualOrLaterThanVersion('1.0.0-beta', '1.0.0-alpha')).toBe(true) + expect(isEqualOrLaterThanVersion('1.0.0-alpha', '1.0.0')).toBe(false) + }) + }) +}) diff --git a/web/utils/semver.ts b/web/utils/semver.ts index f1b9eb8d7e..aea84153ec 100644 --- a/web/utils/semver.ts +++ b/web/utils/semver.ts @@ -7,3 +7,7 @@ export const getLatestVersion = (versionList: string[]) => { export const compareVersion = (v1: string, v2: string) => { return semver.compare(v1, v2) } + +export const isEqualOrLaterThanVersion = (baseVersion: string, targetVersion: string) => { + return semver.gte(baseVersion, targetVersion) +} From b3eca1b6648b3f83718c5b66cdd6b96c645266eb Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 5 Jun 2025 14:44:39 +0800 Subject: [PATCH 069/126] fix: can choose mcp tools --- .../account-setting/model-provider-page/model-modal/Form.tsx | 3 +++ .../workflow/nodes/_base/components/agent-strategy.tsx | 2 ++ web/app/components/workflow/nodes/agent/node.tsx | 2 +- web/app/components/workflow/nodes/agent/panel.tsx | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx b/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx index c5af4ed8a1..f1e3595d1e 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx @@ -54,6 +54,7 @@ type FormProps< nodeId?: string nodeOutputVars?: NodeOutPutVar[], availableNodes?: Node[], + canChooseMCPTool?: boolean } function Form< @@ -79,6 +80,7 @@ function Form< nodeId, nodeOutputVars, availableNodes, + canChooseMCPTool, }: FormProps) { const language = useLanguage() const [changeKey, setChangeKey] = useState('') @@ -377,6 +379,7 @@ function Form< value={value[variable] || []} onChange={item => handleFormChange(variable, item as any)} supportCollapse + canChooseMCPTool={canChooseMCPTool} /> {fieldMoreInfo?.(formSchema)} {validating && changeKey === variable && } diff --git a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx index b9d42558c1..e9e84000d7 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx @@ -63,6 +63,7 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => { const { setControlPromptEditorRerenderKey, } = workflowStore.getState() + const override: ComponentProps>['override'] = [ [FormTypeEnum.textNumber, FormTypeEnum.textInput], (schema, props) => { @@ -220,6 +221,7 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => { nodeId={nodeId} nodeOutputVars={nodeOutputVars || []} availableNodes={availableNodes || []} + canChooseMCPTool={canChooseMCPTool} />
: > = (props) => { {t('workflow.nodes.agent.toolbox')} }>
- {tools.map(tool => )} + {tools.map((tool, i) => )}
}
diff --git a/web/app/components/workflow/nodes/agent/panel.tsx b/web/app/components/workflow/nodes/agent/panel.tsx index a213ffc24b..4dc2d40a03 100644 --- a/web/app/components/workflow/nodes/agent/panel.tsx +++ b/web/app/components/workflow/nodes/agent/panel.tsx @@ -114,6 +114,7 @@ const AgentPanel: FC> = (props) => { nodeOutputVars={availableVars} availableNodes={availableNodesWithParent} nodeId={props.id} + canChooseMCPTool={canChooseMCPTool} />
From 933194b1f71eeb7edd9899adb11c2c345ee8ade4 Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 5 Jun 2025 15:08:57 +0800 Subject: [PATCH 070/126] feat: mcp tools not support warning --- .../tool-selector/index.tsx | 1 + .../tool-selector/tool-item.tsx | 11 ++++++++-- .../mcp-tool-not-support-tooltip.tsx | 22 +++++++++++++++++++ web/i18n/en-US/plugin.ts | 1 + web/i18n/zh-Hans/plugin.ts | 1 + 5 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 web/app/components/workflow/nodes/_base/components/mcp-tool-not-support-tooltip.tsx 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 fba99f4e68..ec35db953a 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 @@ -279,6 +279,7 @@ const ToolSelector: FC = ({

} + canChooseMCPTool={canChooseMCPTool} /> )} diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-item.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-item.tsx index 391b8a8528..7747870f13 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-item.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-item.tsx @@ -17,6 +17,7 @@ import { ToolTipContent } from '@/app/components/base/tooltip/content' import { InstallPluginButton } from '@/app/components/workflow/nodes/_base/components/install-plugin-button' import { SwitchPluginVersion } from '@/app/components/workflow/nodes/_base/components/switch-plugin-version' import cn from '@/utils/classnames' +import McpToolNotSupportTooltip from '@/app/components/workflow/nodes/_base/components/mcp-tool-not-support-tooltip' type Props = { icon?: any @@ -37,6 +38,7 @@ type Props = { onInstall?: () => void versionMismatch?: boolean open: boolean + canChooseMCPTool?: boolean, } const ToolItem = ({ @@ -58,11 +60,13 @@ const ToolItem = ({ isError, errorTip, versionMismatch, + canChooseMCPTool, }: Props) => { const { t } = useTranslation() const providerNameText = isMCPTool ? providerShowName : providerName?.split('/').pop() const isTransparent = uninstalled || versionMismatch || isError const [isDeleting, setIsDeleting] = useState(false) + const isShowCanNotChooseMCPTip = isMCPTool && !canChooseMCPTool return (
{toolLabel}
- {!noAuth && !isError && !uninstalled && !versionMismatch && ( + {!noAuth && !isError && !uninstalled && !versionMismatch && !isShowCanNotChooseMCPTip && ( @@ -107,7 +111,7 @@ const ToolItem = ({
- {!isError && !uninstalled && !noAuth && !versionMismatch && showSwitch && ( + {!isError && !uninstalled && !noAuth && !versionMismatch && !isShowCanNotChooseMCPTip && showSwitch && (
e.stopPropagation()}>
)} + {isShowCanNotChooseMCPTip && ( + + )} {!isError && !uninstalled && !versionMismatch && noAuth && (
+ } + > + + + ) +} +export default React.memo(McpToolNotSupportTooltip) diff --git a/web/i18n/en-US/plugin.ts b/web/i18n/en-US/plugin.ts index a4e4d39d39..d314a448dd 100644 --- a/web/i18n/en-US/plugin.ts +++ b/web/i18n/en-US/plugin.ts @@ -93,6 +93,7 @@ const translation = { unsupportedTitle: 'Unsupported Action', unsupportedContent: 'The installed plugin version does not provide this action.', unsupportedContent2: 'Click to switch version.', + unsupportedMCPTool: 'Currently selected agent strategy plugin version does not support MCP tools.', }, configureApp: 'Configure App', configureModel: 'Configure model', diff --git a/web/i18n/zh-Hans/plugin.ts b/web/i18n/zh-Hans/plugin.ts index e088557dfb..a7be431b3e 100644 --- a/web/i18n/zh-Hans/plugin.ts +++ b/web/i18n/zh-Hans/plugin.ts @@ -93,6 +93,7 @@ const translation = { unsupportedTitle: '不支持的 Action', unsupportedContent: '已安装的插件版本不提供这个 action。', unsupportedContent2: '点击切换版本', + unsupportedMCPTool: '当前选定的 Agent 策略插件版本不支持 MCP 工具。', }, configureApp: '应用设置', configureModel: '模型设置', From fa3a616bf6aacefb1dabc1907e7cedeef2abc376 Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 5 Jun 2025 15:51:34 +0800 Subject: [PATCH 071/126] feat: can not choose mcp tool in picker --- .../components/workflow/block-selector/all-tools.tsx | 7 ++++--- web/app/components/workflow/block-selector/tabs.tsx | 2 +- .../workflow/block-selector/tool-picker.tsx | 2 +- .../workflow/block-selector/tool/action-item.tsx | 4 +++- .../block-selector/tool/tool-list-flat-view/list.tsx | 3 +++ .../block-selector/tool/tool-list-tree-view/item.tsx | 3 +++ .../block-selector/tool/tool-list-tree-view/list.tsx | 3 +++ .../components/workflow/block-selector/tool/tool.tsx | 12 +++++++++--- web/app/components/workflow/block-selector/tools.tsx | 4 ++++ web/app/components/workflow/block-selector/types.ts | 2 +- .../_base/components/agent-strategy-selector.tsx | 9 +++++++-- .../nodes/_base/components/agent-strategy.tsx | 4 ++-- 12 files changed, 41 insertions(+), 14 deletions(-) diff --git a/web/app/components/workflow/block-selector/all-tools.tsx b/web/app/components/workflow/block-selector/all-tools.tsx index 910cf684c4..7a33f47cb1 100644 --- a/web/app/components/workflow/block-selector/all-tools.tsx +++ b/web/app/components/workflow/block-selector/all-tools.tsx @@ -35,7 +35,7 @@ type AllToolsProps = { canNotSelectMultiple?: boolean onSelectMultiple?: (type: BlockEnum, tools: ToolDefaultValue[]) => void selectedTools?: ToolValue[] - isHideMCPTools?: boolean + canChooseMCPTool?: boolean } const DEFAULT_TAGS: AllToolsProps['tags'] = [] @@ -53,10 +53,10 @@ const AllTools = ({ customTools, mcpTools = [], selectedTools, - isHideMCPTools, + canChooseMCPTool, }: AllToolsProps) => { const language = useGetLanguage() - const tabs = useToolTabs(isHideMCPTools) + const tabs = useToolTabs() const [activeTab, setActiveTab] = useState(ToolTypeEnum.All) const [activeView, setActiveView] = useState(ViewType.flat) const hasFilter = searchText || tags.length > 0 @@ -148,6 +148,7 @@ const AllTools = ({ viewType={isSupportGroupView ? activeView : ViewType.flat} hasSearchText={!!searchText} selectedTools={selectedTools} + canChooseMCPTool={canChooseMCPTool} /> {/* Plugins from marketplace */} {enable_marketplace && = ({ customTools={customTools || []} workflowTools={workflowTools || []} mcpTools={mcpTools || []} - isHideMCPTools={false} + canChooseMCPTool /> ) } diff --git a/web/app/components/workflow/block-selector/tool-picker.tsx b/web/app/components/workflow/block-selector/tool-picker.tsx index 95ac24f599..69800231b5 100644 --- a/web/app/components/workflow/block-selector/tool-picker.tsx +++ b/web/app/components/workflow/block-selector/tool-picker.tsx @@ -178,7 +178,7 @@ const ToolPicker: FC = ({ workflowTools={workflowToolList || []} mcpTools={mcpTools || []} selectedTools={selectedTools} - isHideMCPTools={!canChooseMCPTool} + canChooseMCPTool={canChooseMCPTool} />
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 7c0ebd7465..e5e33614b0 100644 --- a/web/app/components/workflow/block-selector/tool/action-item.tsx +++ b/web/app/components/workflow/block-selector/tool/action-item.tsx @@ -15,6 +15,7 @@ type Props = { provider: ToolWithProvider payload: Tool disabled?: boolean + isAdded?: boolean onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void } @@ -23,6 +24,7 @@ const ToolItem: FC = ({ payload, onSelect, disabled, + isAdded, }) => { const { t } = useTranslation() @@ -76,7 +78,7 @@ const ToolItem: FC = ({
{payload.label[language]}
- {disabled && ( + {isAdded && (
{t('tools.addToolModal.added')}
)}
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 abb28dead0..ed68339bc0 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 @@ -18,6 +18,7 @@ type Props = { letters: string[] toolRefs: any selectedTools?: ToolValue[] + canChooseMCPTool?: boolean } const ToolViewFlatView: FC = ({ @@ -30,6 +31,7 @@ const ToolViewFlatView: FC = ({ onSelectMultiple, toolRefs, selectedTools, + canChooseMCPTool, }) => { const firstLetterToolIds = useMemo(() => { const res: Record = {} @@ -60,6 +62,7 @@ const ToolViewFlatView: FC = ({ canNotSelectMultiple={canNotSelectMultiple} onSelectMultiple={onSelectMultiple} selectedTools={selectedTools} + canChooseMCPTool={canChooseMCPTool} />
))} 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 acec666822..b3f7aab4df 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 @@ -15,6 +15,7 @@ type Props = { canNotSelectMultiple?: boolean onSelectMultiple?: (type: BlockEnum, tools: ToolDefaultValue[]) => void selectedTools?: ToolValue[] + canChooseMCPTool?: boolean } const Item: FC = ({ @@ -25,6 +26,7 @@ const Item: FC = ({ canNotSelectMultiple, onSelectMultiple, selectedTools, + canChooseMCPTool, }) => { return (
@@ -43,6 +45,7 @@ const Item: FC = ({ canNotSelectMultiple={canNotSelectMultiple} onSelectMultiple={onSelectMultiple} selectedTools={selectedTools} + canChooseMCPTool={canChooseMCPTool} /> ))}
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 a82df0570f..d85d1ea682 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 @@ -15,6 +15,7 @@ type Props = { canNotSelectMultiple?: boolean onSelectMultiple?: (type: BlockEnum, tools: ToolDefaultValue[]) => void selectedTools?: ToolValue[] + canChooseMCPTool?: boolean } const ToolListTreeView: FC = ({ @@ -24,6 +25,7 @@ const ToolListTreeView: FC = ({ canNotSelectMultiple, onSelectMultiple, selectedTools, + canChooseMCPTool, }) => { const { t } = useTranslation() const getI18nGroupName = useCallback((name: string) => { @@ -53,6 +55,7 @@ const ToolListTreeView: FC = ({ canNotSelectMultiple={canNotSelectMultiple} onSelectMultiple={onSelectMultiple} selectedTools={selectedTools} + canChooseMCPTool={canChooseMCPTool} /> ))}
diff --git a/web/app/components/workflow/block-selector/tool/tool.tsx b/web/app/components/workflow/block-selector/tool/tool.tsx index 415400ec04..05a34096ee 100644 --- a/web/app/components/workflow/block-selector/tool/tool.tsx +++ b/web/app/components/workflow/block-selector/tool/tool.tsx @@ -14,6 +14,7 @@ import ActonItem from './action-item' import BlockIcon from '../../block-icon' import { useTranslation } from 'react-i18next' import { useHover } from 'ahooks' +import McpToolNotSupportTooltip from '../../nodes/_base/components/mcp-tool-not-support-tooltip' type Props = { className?: string @@ -25,6 +26,7 @@ type Props = { canNotSelectMultiple?: boolean onSelectMultiple?: (type: BlockEnum, tools: ToolDefaultValue[]) => void selectedTools?: ToolValue[] + canChooseMCPTool?: boolean } const Tool: FC = ({ @@ -37,6 +39,7 @@ const Tool: FC = ({ canNotSelectMultiple, onSelectMultiple, selectedTools, + canChooseMCPTool, }) => { const { t } = useTranslation() const language = useGetLanguage() @@ -47,6 +50,7 @@ const Tool: FC = ({ const [isFold, setFold] = React.useState(true) const ref = useRef(null) const isHovering = useHover(ref) + const isShowCanNotChooseMCPTip = !canChooseMCPTool && payload.type === CollectionType.mcp 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) @@ -173,7 +177,7 @@ const Tool: FC = ({ }) }} > -
+
= ({
- {!canNotSelectMultiple && (notShowProvider ? notShowProviderSelectInfo : selectedInfo)} + {!isShowCanNotChooseMCPTip && !canNotSelectMultiple && (notShowProvider ? notShowProviderSelectInfo : selectedInfo)} + {isShowCanNotChooseMCPTip && } {hasAction && ( )} @@ -202,7 +207,8 @@ const Tool: FC = ({ provider={payload} payload={action} onSelect={onSelect} - disabled={getIsDisabled(action)} + disabled={getIsDisabled(action) || isShowCanNotChooseMCPTip} + isAdded={getIsDisabled(action)} /> )) )} diff --git a/web/app/components/workflow/block-selector/tools.tsx b/web/app/components/workflow/block-selector/tools.tsx index dbe8c3a81a..cc4cbd2a5d 100644 --- a/web/app/components/workflow/block-selector/tools.tsx +++ b/web/app/components/workflow/block-selector/tools.tsx @@ -25,6 +25,7 @@ type ToolsProps = { className?: string indexBarClassName?: string selectedTools?: ToolValue[] + canChooseMCPTool?: boolean } const Blocks = ({ showWorkflowEmpty, @@ -37,6 +38,7 @@ const Blocks = ({ className, indexBarClassName, selectedTools, + canChooseMCPTool, }: ToolsProps) => { const { t } = useTranslation() const language = useGetLanguage() @@ -114,6 +116,7 @@ const Blocks = ({ canNotSelectMultiple={canNotSelectMultiple} onSelectMultiple={onSelectMultiple} selectedTools={selectedTools} + canChooseMCPTool={canChooseMCPTool} /> ) : ( ) )} diff --git a/web/app/components/workflow/block-selector/types.ts b/web/app/components/workflow/block-selector/types.ts index 42ab833a5b..bff23897ba 100644 --- a/web/app/components/workflow/block-selector/types.ts +++ b/web/app/components/workflow/block-selector/types.ts @@ -33,7 +33,7 @@ export type ToolDefaultValue = { params: Record paramSchemas: Record[] output_schema: Record - meta: PluginMeta + meta?: PluginMeta } export type ToolValue = { diff --git a/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx b/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx index ca09cb2f3e..2e8b8166b9 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx @@ -89,10 +89,11 @@ function formatStrategy(input: StrategyPluginDetail[], getIcon: (i: string) => s export type AgentStrategySelectorProps = { value?: Strategy, onChange: (value?: Strategy) => void, + canChooseMCPTool: boolean, } export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) => { - const { value, onChange } = props + const { value, onChange, canChooseMCPTool } = props const [open, setOpen] = useState(false) const [viewType, setViewType] = useState(ViewType.flat) const [query, setQuery] = useState('') @@ -216,7 +217,11 @@ export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) => setOpen(false) }} className='h-full max-h-full max-w-none overflow-y-auto' - indexBarClassName='top-0 xl:top-36' showWorkflowEmpty={false} hasSearchText={false} /> + indexBarClassName='top-0 xl:top-36' + showWorkflowEmpty={false} + hasSearchText={false} + canChooseMCPTool={canChooseMCPTool} + /> = Omit & { type: Type } & Field @@ -201,7 +201,7 @@ export const AgentStrategy = memo((props: AgentStrategyProps) => { } } return
- + { strategy ?
From caf7b200249ca6fca499fabcd2bfe4b5e1bf29bb Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 5 Jun 2025 16:04:51 +0800 Subject: [PATCH 072/126] feat: not abled in tool list --- .../plugin-detail-panel/multiple-tool-selector/index.tsx | 9 ++++++++- .../plugin-detail-panel/tool-selector/tool-item.tsx | 5 +++-- 2 files changed, 11 insertions(+), 3 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 114b47a431..e8d32f1908 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 @@ -13,6 +13,7 @@ import type { Node } from 'reactflow' import type { NodeOutPutVar } from '@/app/components/workflow/types' import cn from '@/utils/classnames' import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/general' +import { useAllMCPTools } from '@/service/use-tools' type Props = { disabled?: boolean @@ -44,7 +45,13 @@ const MultipleToolSelector = ({ canChooseMCPTool, }: Props) => { const { t } = useTranslation() - const enabledCount = value.filter(item => item.enabled).length + const { data: mcpTools } = useAllMCPTools() + const enabledCount = value.filter((item) => { + const isMCPTool = mcpTools?.find(tool => tool.id === item.provider_name) + if(isMCPTool) + return item.enabled && canChooseMCPTool + return item.enabled + }).length // collapse control const [collapse, setCollapse] = React.useState(false) const handleCollapse = () => { diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-item.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-item.tsx index 7747870f13..5cc9b7a3a8 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-item.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-item.tsx @@ -75,7 +75,7 @@ const ToolItem = ({ isDeleting && 'border-state-destructive-border shadow-xs hover:bg-state-destructive-hover', )}> {icon && ( -
+
{typeof icon === 'string' &&
} {typeof icon !== 'string' && }
@@ -83,13 +83,14 @@ const ToolItem = ({ {!icon && (
)} -
+
{providerNameText}
{toolLabel}
From d0ed31b00819143d8e5462eb1c3fae1ce0ae3e53 Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 5 Jun 2025 16:12:31 +0800 Subject: [PATCH 073/126] fix: agent app show mcp not support warning --- .../app/configuration/config/agent/agent-tools/index.tsx | 1 + 1 file changed, 1 insertion(+) 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 6c7bb67ddb..1b6004ff59 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 @@ -165,6 +165,7 @@ const AgentTools: FC = () => { onSelect={handleSelectTool} onSelectMultiple={handleSelectMultipleTool} selectedTools={tools as unknown as ToolValue[]} + canChooseMCPTool /> )} From ed2d971cf96ed1ec512b23a9a707d6f4aa71d7b4 Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 5 Jun 2025 16:19:21 +0800 Subject: [PATCH 074/126] fix: agent selector can choose multi --- .../workflow/nodes/_base/components/agent-strategy-selector.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx b/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx index 2e8b8166b9..61ad2e3b92 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx @@ -220,6 +220,7 @@ export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) => indexBarClassName='top-0 xl:top-36' showWorkflowEmpty={false} hasSearchText={false} + canNotSelectMultiple canChooseMCPTool={canChooseMCPTool} /> Date: Fri, 6 Jun 2025 16:42:13 +0800 Subject: [PATCH 075/126] data formatting --- .../components/tools/utils/to-form-schema.ts | 29 ++++++++++++++++++- .../components/workflow/nodes/tool/node.tsx | 13 +++------ .../workflow/nodes/tool/use-config.ts | 18 +++++++----- .../workflow/utils/workflow-init.ts | 18 ++++++++++++ 4 files changed, 60 insertions(+), 18 deletions(-) diff --git a/web/app/components/tools/utils/to-form-schema.ts b/web/app/components/tools/utils/to-form-schema.ts index 179f59021e..7841b660b5 100644 --- a/web/app/components/tools/utils/to-form-schema.ts +++ b/web/app/components/tools/utils/to-form-schema.ts @@ -54,7 +54,7 @@ export const toolCredentialToFormSchemas = (parameters: ToolCredential[]) => { return formSchemas } -export const addDefaultValue = (value: Record, formSchemas: { variable: string; default?: any }[]) => { +export const addDefaultValue = (value: Record, formSchemas: { variable: string; type: string; default?: any }[]) => { const newValues = { ...value } formSchemas.forEach((formSchema) => { const itemValue = value[formSchema.variable] @@ -94,3 +94,30 @@ export const getStructureValue = (value: Record) => { }) return newValue } + +export const getConfiguredValue = (value: Record, formSchemas: { variable: string; type: string; default?: any }[]) => { + const newValues = { ...value } + formSchemas.forEach((formSchema) => { + const itemValue = value[formSchema.variable] + if ((formSchema.default !== undefined) && (value === undefined || itemValue === null || itemValue === '' || itemValue === undefined)) { + const value = formSchema.default + newValues[formSchema.variable] = { + type: 'constant', + value: formSchema.default, + } + if (formSchema.type === 'boolean') { + if (typeof value === 'string') + newValues[formSchema.variable].value = value === 'true' ? 1 : 0 + + if (typeof value === 'boolean') + newValues[formSchema.variable].value = value ? 1 : 0 + } + + if (formSchema.type === 'number-input') { + if (typeof value === 'string' && value !== '') + newValues[formSchema.variable].value = Number.parseFloat(value) + } + } + }) + return newValues +} diff --git a/web/app/components/workflow/nodes/tool/node.tsx b/web/app/components/workflow/nodes/tool/node.tsx index f3cb4d9fae..e15ddcaaaa 100644 --- a/web/app/components/workflow/nodes/tool/node.tsx +++ b/web/app/components/workflow/nodes/tool/node.tsx @@ -21,14 +21,14 @@ const Node: FC> = ({
{key}
- {typeof tool_configurations[key] === 'string' && ( + {typeof tool_configurations[key].value === 'string' && (
- {paramSchemas?.find(i => i.name === key)?.type === FormTypeEnum.secretInput ? '********' : tool_configurations[key]} + {paramSchemas?.find(i => i.name === key)?.type === FormTypeEnum.secretInput ? '********' : tool_configurations[key].value}
)} - {typeof tool_configurations[key] === 'number' && ( + {typeof tool_configurations[key].value === 'number' && (
- {tool_configurations[key]} + {tool_configurations[key].value}
)} {typeof tool_configurations[key] !== 'string' && tool_configurations[key]?.type === FormTypeEnum.modelSelector && ( @@ -36,11 +36,6 @@ const Node: FC> = ({ {tool_configurations[key].model}
)} - {/* {typeof tool_configurations[key] !== 'string' && tool_configurations[key]?.type === FormTypeEnum.appSelector && ( -
- {tool_configurations[key].app_id} -
- )} */}
))} diff --git a/web/app/components/workflow/nodes/tool/use-config.ts b/web/app/components/workflow/nodes/tool/use-config.ts index 49a5e3b4d1..41bf29912d 100644 --- a/web/app/components/workflow/nodes/tool/use-config.ts +++ b/web/app/components/workflow/nodes/tool/use-config.ts @@ -8,7 +8,10 @@ import { useLanguage } from '@/app/components/header/account-setting/model-provi import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' import { CollectionType } from '@/app/components/tools/types' import { updateBuiltInToolCredential } from '@/service/tools' -import { addDefaultValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' +import { + getConfiguredValue, + toolParametersToFormSchemas, +} from '@/app/components/tools/utils/to-form-schema' import Toast from '@/app/components/base/toast' import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form' import { VarType as VarVarType } from '@/app/components/workflow/types' @@ -28,8 +31,8 @@ const useConfig = (id: string, payload: ToolNodeType) => { const language = useLanguage() const { inputs, setInputs: doSetInputs } = useNodeCrud(id, payload) /* - * tool_configurations: tool setting, not dynamic setting - * tool_parameters: tool dynamic setting(by user) + * tool_configurations: tool setting, not dynamic setting (form type = form) + * tool_parameters: tool dynamic setting(form type = llm) * output_schema: tool dynamic output */ const { provider_id, provider_type, tool_name, tool_configurations, output_schema } = inputs @@ -112,12 +115,11 @@ const useConfig = (id: string, payload: ToolNodeType) => { doSetInputs(newInputs) }, [doSetInputs, formSchemas, hasShouldTransferTypeSettingInput]) const [notSetDefaultValue, setNotSetDefaultValue] = useState(false) - const toolSettingValue = (() => { + const toolSettingValue = useMemo(() => { if (notSetDefaultValue) return tool_configurations - - return addDefaultValue(tool_configurations, toolSettingSchema) - })() + return getConfiguredValue(tool_configurations, toolSettingSchema) + }, [notSetDefaultValue, toolSettingSchema, tool_configurations]) const setToolSettingValue = useCallback((value: Record) => { setNotSetDefaultValue(true) setInputs({ @@ -131,7 +133,7 @@ const useConfig = (id: string, payload: ToolNodeType) => { return const inputsWithDefaultValue = produce(inputs, (draft) => { if (!draft.tool_configurations || Object.keys(draft.tool_configurations).length === 0) - draft.tool_configurations = addDefaultValue(tool_configurations, toolSettingSchema) + draft.tool_configurations = getConfiguredValue(tool_configurations, toolSettingSchema) if (!draft.tool_parameters) draft.tool_parameters = {} diff --git a/web/app/components/workflow/utils/workflow-init.ts b/web/app/components/workflow/utils/workflow-init.ts index 93a61230ba..dd952ad98f 100644 --- a/web/app/components/workflow/utils/workflow-init.ts +++ b/web/app/components/workflow/utils/workflow-init.ts @@ -28,6 +28,7 @@ import type { IfElseNodeType } from '../nodes/if-else/types' import { branchNameCorrect } from '../nodes/if-else/utils' import type { IterationNodeType } from '../nodes/iteration/types' import type { LoopNodeType } from '../nodes/loop/types' +import type { ToolNodeType } from '../nodes/tool/types' import { getIterationStartNode, getLoopStartNode, @@ -276,6 +277,7 @@ export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => { if (node.data.type === BlockEnum.ParameterExtractor) (node as any).data.model.provider = correctModelProvider((node as any).data.model.provider) + if (node.data.type === BlockEnum.HttpRequest && !node.data.retry_config) { node.data.retry_config = { retry_enabled: true, @@ -284,6 +286,22 @@ export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => { } } + if (node.data.type === BlockEnum.Tool) { + const toolConfigurations = (node as Node).data.tool_configurations + if (toolConfigurations && Object.keys(toolConfigurations).length > 0) { + const newValues = { ...toolConfigurations } + Object.keys(toolConfigurations).forEach((key) => { + if (typeof toolConfigurations[key] !== 'object') { + newValues[key] = { + type: 'constant', + value: toolConfigurations[key], + } + } + }); + (node as Node).data.tool_configurations = newValues + } + } + return node }) } From ab8146035db0cafc189f4f9b952848a1b9e251fa Mon Sep 17 00:00:00 2001 From: JzoNg Date: Sun, 8 Jun 2025 18:37:18 +0800 Subject: [PATCH 076/126] mixed-variable-input --- .../components/base/prompt-editor/index.tsx | 25 ++++++++-- .../plugins/custom-text/node.tsx | 1 - .../prompt-editor/plugins/placeholder.tsx | 2 +- .../mixed-variable-text-input/index.tsx | 39 +++++++++++++++ .../mixed-variable-text-input/placeholder.tsx | 49 +++++++++++++++++++ 5 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx create mode 100644 web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/placeholder.tsx diff --git a/web/app/components/base/prompt-editor/index.tsx b/web/app/components/base/prompt-editor/index.tsx index 94a65e4b62..a87a51cd50 100644 --- a/web/app/components/base/prompt-editor/index.tsx +++ b/web/app/components/base/prompt-editor/index.tsx @@ -64,8 +64,9 @@ import cn from '@/utils/classnames' export type PromptEditorProps = { instanceId?: string compact?: boolean + wrapperClassName?: string className?: string - placeholder?: string + placeholder?: string | JSX.Element placeholderClassName?: string style?: React.CSSProperties value?: string @@ -85,6 +86,7 @@ export type PromptEditorProps = { const PromptEditor: FC = ({ instanceId, compact, + wrapperClassName, className, placeholder, placeholderClassName, @@ -147,10 +149,25 @@ const PromptEditor: FC = ({ return ( -
+
} - placeholder={} + contentEditable={ + + } + placeholder={ + + } ErrorBoundary={LexicalErrorBoundary} /> { const { t } = useTranslation() diff --git a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx new file mode 100644 index 0000000000..4bb562ba3a --- /dev/null +++ b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx @@ -0,0 +1,39 @@ +import { + memo, +} from 'react' +import PromptEditor from '@/app/components/base/prompt-editor' +import cn from '@/utils/classnames' +import Placeholder from './placeholder' + +type MixedVariableTextInputProps = { + editable?: boolean + value?: string + onChange?: (text: string) => void +} +const MixedVariableTextInput = ({ + editable = true, + value = '', + onChange, +}: MixedVariableTextInputProps) => { + return ( + } + onChange={onChange} + /> + ) +} + +export default memo(MixedVariableTextInput) diff --git a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/placeholder.tsx b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/placeholder.tsx new file mode 100644 index 0000000000..e84ffbeb28 --- /dev/null +++ b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/placeholder.tsx @@ -0,0 +1,49 @@ +import { useCallback } from 'react' +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' +import { FOCUS_COMMAND } from 'lexical' +import { $insertNodes } from 'lexical' +import { CustomTextNode } from '@/app/components/base/prompt-editor/plugins/custom-text/node' +import Badge from '@/app/components/base/badge' + +const Placeholder = () => { + const [editor] = useLexicalComposerContext() + + const handleInsert = useCallback((text: string) => { + editor.update(() => { + const textNode = new CustomTextNode(text) + $insertNodes([textNode]) + }) + editor.dispatchCommand(FOCUS_COMMAND, undefined as any) + }, [editor]) + + return ( +
{ + e.stopPropagation() + handleInsert('') + }} + > +
+ Type or press +
/
+
{ + e.stopPropagation() + handleInsert('/') + })} + > + insert variable +
+
+ +
+ ) +} + +export default Placeholder From cadd226e84a762e159a54067bcda549751bc88f0 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Sun, 8 Jun 2025 20:48:00 +0800 Subject: [PATCH 077/126] MCP server publish --- .../app/(appDetailLayout)/[appId]/overview/cardView.tsx | 2 +- web/app/components/tools/mcp/mcp-service-card.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx index 05799bcac5..349d31cf9f 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx @@ -138,7 +138,7 @@ const CardView: FC = ({ appId, isInPanel, className }) => { isInPanel={isInPanel} onChangeStatus={onChangeApiStatus} /> - {isInPanel && appDetail.mode === 'workflow' && ( + {isInPanel && ( diff --git a/web/app/components/tools/mcp/mcp-service-card.tsx b/web/app/components/tools/mcp/mcp-service-card.tsx index 66709d699f..b8492c3dc3 100644 --- a/web/app/components/tools/mcp/mcp-service-card.tsx +++ b/web/app/components/tools/mcp/mcp-service-card.tsx @@ -50,7 +50,7 @@ function MCPServiceCard({ const appUnpublished = !currentWorkflow?.graph const serverPublished = !!id const serverActivated = status === 'active' - const serverURL = serverPublished ? `${globalThis.location.protocol}//${globalThis.location.host}/api/server/${server_code}/mcp` : '***********' + const serverURL = serverPublished ? `${appInfo.api_base_url.replace('/v1', '')}/mcp/server/${server_code}/mcp` : '***********' const toggleDisabled = !isCurrentWorkspaceEditor || appUnpublished const [activated, setActivated] = useState(serverActivated) From 900c9e589d3effbc9b8ad99c09106d9684bd2f2d Mon Sep 17 00:00:00 2001 From: JzoNg Date: Mon, 9 Jun 2025 20:27:08 +0800 Subject: [PATCH 078/126] number type --- .../_base/components/form-input-item.tsx | 133 ++++++++++++++++++ .../components/form-input-type-switch.tsx | 47 +++++++ .../nodes/tool/components/tool-form/index.tsx | 47 +++++++ .../nodes/tool/components/tool-form/item.tsx | 83 +++++++++++ .../components/workflow/nodes/tool/panel.tsx | 64 ++++++--- web/i18n/en-US/workflow.ts | 5 + web/i18n/zh-Hans/workflow.ts | 5 + 7 files changed, 367 insertions(+), 17 deletions(-) create mode 100644 web/app/components/workflow/nodes/_base/components/form-input-item.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/form-input-type-switch.tsx create mode 100644 web/app/components/workflow/nodes/tool/components/tool-form/index.tsx create mode 100644 web/app/components/workflow/nodes/tool/components/tool-form/item.tsx diff --git a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx new file mode 100644 index 0000000000..32114a7f2c --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx @@ -0,0 +1,133 @@ +'use client' +import type { FC } from 'react' +import type { ToolVarInputs } from '@/app/components/workflow/nodes/tool/types' +import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' +import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' +import { VarType } from '@/app/components/workflow/types' + +import type { ValueSelector } from '@/app/components/workflow/types' +import FormInputTypeSwitch from './form-input-type-switch' +import Input from '@/app/components/base/input' +import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' +// import cn from '@/utils/classnames' + +type Props = { + readOnly: boolean + nodeId: string + schema: CredentialFormSchema + value: ToolVarInputs + onChange: (value: any) => void + onOpen?: (index: number) => void + hideTypeSwitch?: boolean +} + +const FormInputItem: FC = ({ + readOnly, + nodeId, + schema, + value, + onChange, + hideTypeSwitch, +}) => { + const { + placeholder, + variable, + type, + default: defaultValue, + // scope, + } = schema as any + const language = useLanguage() + const varInput = value[variable] + const isString = type === FormTypeEnum.textInput || type === FormTypeEnum.secretInput + const isNumber = type === FormTypeEnum.textNumber + const isBoolean = type === FormTypeEnum.boolean + const isSelect = type === FormTypeEnum.select + const isFile = type === FormTypeEnum.file || type === FormTypeEnum.files + const isAppSelector = type === FormTypeEnum.appSelector + const isModelSelector = type === FormTypeEnum.modelSelector + const isObject = type === FormTypeEnum.object + const isArray = type === FormTypeEnum.array + + const showTypeSwitch = isNumber || isObject || isArray + + const handleTypeChange = (newType: string) => { + if (newType === VarKindType.variable) { + onChange({ + ...value, + [variable]: { + ...varInput, + type: VarKindType.variable, + value: '', + }, + }) + } + else { + onChange({ + ...value, + [variable]: { + ...varInput, + type: VarKindType.constant, + value: defaultValue, + }, + }) + } + } + + const handleValueChange = (newValue: any) => { + onChange({ + ...value, + [variable]: { + ...varInput, + type: varInput.type, + value: newValue, + }, + }) + } + + const handleVariableSelectorChange = (newValue: ValueSelector | string, variable: string) => { + onChange({ + ...value, + [variable]: { + ...varInput, + type: VarKindType.variable, + value: newValue || '', + }, + }) + } + + return ( +
+ {showTypeSwitch && !hideTypeSwitch && ( + + )} + {isNumber && varInput.type === VarKindType.constant && ( + handleValueChange(e.target.value)} + placeholder={placeholder?.[language] || placeholder?.en_US} + /> + )} + {isNumber && varInput.type === VarKindType.variable && ( + handleVariableSelectorChange(value, variable)} + filterVar={varPayload => varPayload.type === VarType.number} + schema={schema} + valueTypePlaceHolder={VarType.number} + /> + )} + {!isNumber && ( +
+ )} +
+ ) +} +export default FormInputItem diff --git a/web/app/components/workflow/nodes/_base/components/form-input-type-switch.tsx b/web/app/components/workflow/nodes/_base/components/form-input-type-switch.tsx new file mode 100644 index 0000000000..30df5839bd --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/form-input-type-switch.tsx @@ -0,0 +1,47 @@ +'use client' +import type { FC } from 'react' +import { useTranslation } from 'react-i18next' +import { + RiEditLine, +} from '@remixicon/react' +import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' +import Tooltip from '@/app/components/base/tooltip' +import { VarType } from '@/app/components/workflow/nodes/tool/types' +import cn from '@/utils/classnames' + +type Props = { + value: string + onChange: (value: string) => void +} + +const FormInputTypeSwitch: FC = ({ + value, + onChange, +}) => { + const { t } = useTranslation() + return ( +
+ +
onChange(VarType.variable)} + > + +
+
+ +
onChange(VarType.constant)} + > + +
+
+
+ ) +} +export default FormInputTypeSwitch diff --git a/web/app/components/workflow/nodes/tool/components/tool-form/index.tsx b/web/app/components/workflow/nodes/tool/components/tool-form/index.tsx new file mode 100644 index 0000000000..10157861c7 --- /dev/null +++ b/web/app/components/workflow/nodes/tool/components/tool-form/index.tsx @@ -0,0 +1,47 @@ +'use client' +import type { FC } from 'react' +import React, { useCallback } from 'react' +import type { ToolVarInputs } from '../../types' +import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { noop } from 'lodash-es' +import ToolFormItem from './item' + +type Props = { + readOnly: boolean + nodeId: string + schema: CredentialFormSchema[] + value: ToolVarInputs + onChange: (value: ToolVarInputs) => void + onOpen?: (index: number) => void +} + +const ToolForm: FC = ({ + readOnly, + nodeId, + schema, + value, + onChange, + onOpen = noop, +}) => { + const handleOpen = useCallback((index: number) => { + return () => onOpen(index) + }, [onOpen]) + return ( +
+ { + schema.map((schema, index) => ( + + )) + } +
+ ) +} +export default ToolForm diff --git a/web/app/components/workflow/nodes/tool/components/tool-form/item.tsx b/web/app/components/workflow/nodes/tool/components/tool-form/item.tsx new file mode 100644 index 0000000000..0e0ef65acb --- /dev/null +++ b/web/app/components/workflow/nodes/tool/components/tool-form/item.tsx @@ -0,0 +1,83 @@ +'use client' +import type { FC } from 'react' +import { + RiBracesLine, +} from '@remixicon/react' +import type { ToolVarInputs } from '../../types' +import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' +import Button from '@/app/components/base/button' +import Tooltip from '@/app/components/base/tooltip' +import FormInputItem from '@/app/components/workflow/nodes/_base/components/form-input-item' + +type Props = { + readOnly: boolean + nodeId: string + schema: CredentialFormSchema + value: ToolVarInputs + onChange: (value: ToolVarInputs) => void + onOpen?: (index: number) => void + showDescription?: boolean +} + +const ToolFormItem: FC = ({ + readOnly, + nodeId, + schema, + value, + onChange, + showDescription, +}) => { + const language = useLanguage() + const { label, type, required, tooltip } = schema + const showSchemaButton = type === FormTypeEnum.object || type === FormTypeEnum.array + + return ( +
+
+
+
{label[language] || label.en_US}
+ {required && ( +
*
+ )} + {!showDescription && tooltip && ( + + {tooltip[language] || tooltip.en_US} +
} + triggerClassName='ml-1 w-4 h-4' + asChild={false} + /> + )} + {showSchemaButton && ( + <> +
·
+ + + )} +
+ {showDescription && tooltip && ( +
{tooltip[language] || tooltip.en_US}
+ )} +
+ +
+ ) +} +export default ToolFormItem diff --git a/web/app/components/workflow/nodes/tool/panel.tsx b/web/app/components/workflow/nodes/tool/panel.tsx index 85966443d5..c532fd79dd 100644 --- a/web/app/components/workflow/nodes/tool/panel.tsx +++ b/web/app/components/workflow/nodes/tool/panel.tsx @@ -5,10 +5,11 @@ import Split from '../_base/components/split' import type { ToolNodeType } from './types' import useConfig from './use-config' import InputVarList from './components/input-var-list' +import ToolForm from './components/tool-form' import Button from '@/app/components/base/button' import Field from '@/app/components/workflow/nodes/_base/components/field' import type { NodePanelProps } from '@/app/components/workflow/types' -import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form' +// import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form' import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials' import Loading from '@/app/components/base/loading' import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form' @@ -63,6 +64,8 @@ const Panel: FC> = ({ return formatToTracingNodeList([runResult], t)[0] }, [runResult, t]) + const [collapsed, setCollapsed] = React.useState(false) + if (isLoading) { return
@@ -84,10 +87,11 @@ const Panel: FC> = ({
)} - {!isShowAuthBtn && <> -
+ {!isShowAuthBtn && ( +
{toolInputVarSchema.length > 0 && ( > = ({ isSupportConstantValue onOpen={handleOnVarOpen} /> + {/* */} )} @@ -107,21 +119,39 @@ const Panel: FC> = ({ )} -
+ {toolSettingSchema.length > 0 && ( + <> + + {/* */} + + + + + )}
- } + )} {showSetAuth && ( Date: Mon, 9 Jun 2025 21:01:29 +0800 Subject: [PATCH 079/126] file & files & select & model & app --- .../_base/components/form-input-item.tsx | 105 +++++++++++++++--- 1 file changed, 91 insertions(+), 14 deletions(-) diff --git a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx index 32114a7f2c..62716d7915 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx @@ -10,6 +10,9 @@ import { VarType } from '@/app/components/workflow/types' import type { ValueSelector } from '@/app/components/workflow/types' import FormInputTypeSwitch from './form-input-type-switch' import Input from '@/app/components/base/input' +import { SimpleSelect } from '@/app/components/base/select' +import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector' +import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector' import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' // import cn from '@/utils/classnames' @@ -36,22 +39,65 @@ const FormInputItem: FC = ({ variable, type, default: defaultValue, - // scope, + options, + scope, } = schema as any const language = useLanguage() const varInput = value[variable] const isString = type === FormTypeEnum.textInput || type === FormTypeEnum.secretInput const isNumber = type === FormTypeEnum.textNumber - const isBoolean = type === FormTypeEnum.boolean - const isSelect = type === FormTypeEnum.select - const isFile = type === FormTypeEnum.file || type === FormTypeEnum.files - const isAppSelector = type === FormTypeEnum.appSelector - const isModelSelector = type === FormTypeEnum.modelSelector const isObject = type === FormTypeEnum.object const isArray = type === FormTypeEnum.array + const isBoolean = type === FormTypeEnum.boolean + const isSelect = type === FormTypeEnum.select + const isAppSelector = type === FormTypeEnum.appSelector + const isModelSelector = type === FormTypeEnum.modelSelector + const isFile = type === FormTypeEnum.file || type === FormTypeEnum.files const showTypeSwitch = isNumber || isObject || isArray + const targetVarType = () => { + if (isString) + return VarType.string + else if (isNumber) + return VarType.number + else if (isFile) + return VarType.arrayFile + else if (isBoolean) + return VarType.boolean + else if (isObject) + return VarType.object + else if (isArray) + return VarType.arrayObject + else + return VarType.string + } + + const getFilterVar = () => { + if (isNumber) + return (varPayload: any) => varPayload.type === VarType.number + else if (isString) + return (varPayload: any) => [VarType.string, VarType.number, VarType.secret].includes(varPayload.type) + else if (isFile) + return (varPayload: any) => [VarType.file, VarType.arrayFile].includes(varPayload.type) + else if (isBoolean) + return (varPayload: any) => varPayload.type === VarType.boolean + else if (isObject) + return (varPayload: any) => varPayload.type === VarType.object + else if (isArray) + return (varPayload: any) => varPayload.type === VarType.arrayObject + return undefined + } + + const getVarKindType = () => { + if (isFile) + return VarKindType.variable + if (isSelect || isAppSelector || isModelSelector || isBoolean) + return VarKindType.constant + if (isString) + return VarKindType.mixed + } + const handleTypeChange = (newType: string) => { if (newType === VarKindType.variable) { onChange({ @@ -80,7 +126,7 @@ const FormInputItem: FC = ({ ...value, [variable]: { ...varInput, - type: varInput.type, + type: getVarKindType(), value: newValue, }, }) @@ -91,7 +137,7 @@ const FormInputItem: FC = ({ ...value, [variable]: { ...varInput, - type: VarKindType.variable, + type: getVarKindType(), value: newValue || '', }, }) @@ -111,7 +157,41 @@ const FormInputItem: FC = ({ placeholder={placeholder?.[language] || placeholder?.en_US} /> )} - {isNumber && varInput.type === VarKindType.variable && ( + {isSelect && ( + { + if (option.show_on.length) + return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value) + + return true + }).map((option: { value: any; label: { [x: string]: any; en_US: any } }) => ({ value: option.value, name: option.label[language] || option.label.en_US }))} + onSelect={item => handleValueChange(item.value as string)} + placeholder={placeholder?.[language] || placeholder?.en_US} + /> + )} + {isAppSelector && ( + + )} + {isModelSelector && ( + + )} + {varInput.type === VarKindType.variable && ( = ({ nodeId={nodeId} value={varInput?.value || []} onChange={value => handleVariableSelectorChange(value, variable)} - filterVar={varPayload => varPayload.type === VarType.number} + filterVar={getFilterVar()} schema={schema} - valueTypePlaceHolder={VarType.number} + valueTypePlaceHolder={targetVarType()} /> )} - {!isNumber && ( -
- )}
) } From e9d196261bdb6f6defe09df7319d76254c1ebd66 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Mon, 9 Jun 2025 21:35:29 +0800 Subject: [PATCH 080/126] boolean --- .../components/tools/utils/to-form-schema.ts | 7 ++-- .../_base/components/form-input-boolean.tsx | 35 +++++++++++++++++++ .../_base/components/form-input-item.tsx | 7 ++++ 3 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 web/app/components/workflow/nodes/_base/components/form-input-boolean.tsx diff --git a/web/app/components/tools/utils/to-form-schema.ts b/web/app/components/tools/utils/to-form-schema.ts index 7841b660b5..0ea451759d 100644 --- a/web/app/components/tools/utils/to-form-schema.ts +++ b/web/app/components/tools/utils/to-form-schema.ts @@ -105,12 +105,15 @@ export const getConfiguredValue = (value: Record, formSchemas: { va type: 'constant', value: formSchema.default, } + if (formSchema.type === 'text-input' || formSchema.type === 'secret-input') + newValues[formSchema.variable].type = 'mixed' + if (formSchema.type === 'boolean') { if (typeof value === 'string') - newValues[formSchema.variable].value = value === 'true' ? 1 : 0 + newValues[formSchema.variable].value = value === 'true' if (typeof value === 'boolean') - newValues[formSchema.variable].value = value ? 1 : 0 + newValues[formSchema.variable].value = value } if (formSchema.type === 'number-input') { diff --git a/web/app/components/workflow/nodes/_base/components/form-input-boolean.tsx b/web/app/components/workflow/nodes/_base/components/form-input-boolean.tsx new file mode 100644 index 0000000000..07c3a087b9 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/form-input-boolean.tsx @@ -0,0 +1,35 @@ +'use client' +import type { FC } from 'react' +import cn from '@/utils/classnames' + +type Props = { + value: boolean + onChange: (value: boolean) => void +} + +const FormInputBoolean: FC = ({ + value, + onChange, +}) => { + return ( +
+
onChange(true)} + >True
+
onChange(false)} + >False
+
+ ) +} +export default FormInputBoolean diff --git a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx index 62716d7915..c7d5def988 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx @@ -11,6 +11,7 @@ import type { ValueSelector } from '@/app/components/workflow/types' import FormInputTypeSwitch from './form-input-type-switch' import Input from '@/app/components/base/input' import { SimpleSelect } from '@/app/components/base/select' +import FormInputBoolean from './form-input-boolean' import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector' import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector' import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' @@ -157,6 +158,12 @@ const FormInputItem: FC = ({ placeholder={placeholder?.[language] || placeholder?.en_US} /> )} + {isBoolean && ( + + )} {isSelect && ( Date: Mon, 9 Jun 2025 22:00:24 +0800 Subject: [PATCH 081/126] string --- .../_base/components/form-input-item.tsx | 60 +++++++++++++++---- .../nodes/tool/components/tool-form/index.tsx | 7 --- .../nodes/tool/components/tool-form/item.tsx | 4 +- .../components/workflow/nodes/tool/panel.tsx | 32 +--------- 4 files changed, 51 insertions(+), 52 deletions(-) diff --git a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx index c7d5def988..a50c7c86ad 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx @@ -1,5 +1,7 @@ 'use client' import type { FC } from 'react' +import React, { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' import type { ToolVarInputs } from '@/app/components/workflow/nodes/tool/types' import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' @@ -7,15 +9,17 @@ import { FormTypeEnum } from '@/app/components/header/account-setting/model-prov import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' import { VarType } from '@/app/components/workflow/types' -import type { ValueSelector } from '@/app/components/workflow/types' +import type { ValueSelector, Var } from '@/app/components/workflow/types' import FormInputTypeSwitch from './form-input-type-switch' +import MixedInput from '@/app/components/workflow/nodes/_base/components/input-support-select-var' +import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' import Input from '@/app/components/base/input' import { SimpleSelect } from '@/app/components/base/select' import FormInputBoolean from './form-input-boolean' import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector' import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector' import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' -// import cn from '@/utils/classnames' +import cn from '@/utils/classnames' type Props = { readOnly: boolean @@ -23,7 +27,6 @@ type Props = { schema: CredentialFormSchema value: ToolVarInputs onChange: (value: any) => void - onOpen?: (index: number) => void hideTypeSwitch?: boolean } @@ -35,6 +38,9 @@ const FormInputItem: FC = ({ onChange, hideTypeSwitch, }) => { + const { t } = useTranslation() + const language = useLanguage() + const { placeholder, variable, @@ -43,7 +49,6 @@ const FormInputItem: FC = ({ options, scope, } = schema as any - const language = useLanguage() const varInput = value[variable] const isString = type === FormTypeEnum.textInput || type === FormTypeEnum.secretInput const isNumber = type === FormTypeEnum.textNumber @@ -54,9 +59,15 @@ const FormInputItem: FC = ({ const isAppSelector = type === FormTypeEnum.appSelector const isModelSelector = type === FormTypeEnum.modelSelector const isFile = type === FormTypeEnum.file || type === FormTypeEnum.files - const showTypeSwitch = isNumber || isObject || isArray + const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, { + onlyLeafNodeVar: false, + filterVar: (varPayload: Var) => { + return [VarType.string, VarType.number, VarType.secret].includes(varPayload.type) + }, + }) + const targetVarType = () => { if (isString) return VarType.string @@ -144,23 +155,48 @@ const FormInputItem: FC = ({ }) } + const [inputsIsFocus, setInputsIsFocus] = useState>({}) + const handleInputFocus = useCallback((variable: string) => { + return (value: boolean) => { + setInputsIsFocus((prev) => { + return { + ...prev, + [variable]: value, + } + }) + } + }, []) + return (
{showTypeSwitch && !hideTypeSwitch && ( - + )} - {isNumber && varInput.type === VarKindType.constant && ( + {isString && ( + + )} + {isNumber && varInput?.type === VarKindType.constant && ( handleValueChange(e.target.value)} placeholder={placeholder?.[language] || placeholder?.en_US} /> )} {isBoolean && ( )} @@ -183,7 +219,7 @@ const FormInputItem: FC = ({ )} @@ -192,13 +228,13 @@ const FormInputItem: FC = ({ popupClassName='!w-[387px]' isAdvancedMode isInWorkflow - value={varInput.value as any} + value={varInput?.value as any} setModel={handleValueChange} readonly={readOnly} scope={scope} /> )} - {varInput.type === VarKindType.variable && ( + {varInput?.type === VarKindType.variable && ( = ({ schema, value, onChange, - onOpen = noop, }) => { - const handleOpen = useCallback((index: number) => { - return () => onOpen(index) - }, [onOpen]) return (
{ @@ -37,7 +31,6 @@ const ToolForm: FC = ({ schema={schema} value={value} onChange={onChange} - onOpen={handleOpen(index)} /> )) } diff --git a/web/app/components/workflow/nodes/tool/components/tool-form/item.tsx b/web/app/components/workflow/nodes/tool/components/tool-form/item.tsx index 0e0ef65acb..c91463461f 100644 --- a/web/app/components/workflow/nodes/tool/components/tool-form/item.tsx +++ b/web/app/components/workflow/nodes/tool/components/tool-form/item.tsx @@ -17,8 +17,6 @@ type Props = { schema: CredentialFormSchema value: ToolVarInputs onChange: (value: ToolVarInputs) => void - onOpen?: (index: number) => void - showDescription?: boolean } const ToolFormItem: FC = ({ @@ -27,11 +25,11 @@ const ToolFormItem: FC = ({ schema, value, onChange, - showDescription, }) => { const language = useLanguage() const { label, type, required, tooltip } = schema const showSchemaButton = type === FormTypeEnum.object || type === FormTypeEnum.array + const showDescription = type === FormTypeEnum.textInput || type === FormTypeEnum.secretInput return (
diff --git a/web/app/components/workflow/nodes/tool/panel.tsx b/web/app/components/workflow/nodes/tool/panel.tsx index c532fd79dd..321a4cd28f 100644 --- a/web/app/components/workflow/nodes/tool/panel.tsx +++ b/web/app/components/workflow/nodes/tool/panel.tsx @@ -4,12 +4,10 @@ import { useTranslation } from 'react-i18next' import Split from '../_base/components/split' import type { ToolNodeType } from './types' import useConfig from './use-config' -import InputVarList from './components/input-var-list' import ToolForm from './components/tool-form' import Button from '@/app/components/base/button' import Field from '@/app/components/workflow/nodes/_base/components/field' import type { NodePanelProps } from '@/app/components/workflow/types' -// import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form' import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials' import Loading from '@/app/components/base/loading' import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form' @@ -34,8 +32,6 @@ const Panel: FC> = ({ inputs, toolInputVarSchema, setInputVar, - handleOnVarOpen, - filterVar, toolSettingSchema, toolSettingValue, setToolSettingValue, @@ -94,29 +90,18 @@ const Panel: FC> = ({ className='px-4' title={t(`${i18nPrefix}.inputVars`)} > - - {/* */} )} {toolInputVarSchema.length > 0 && toolSettingSchema.length > 0 && ( - + )} {toolSettingSchema.length > 0 && ( @@ -126,19 +111,6 @@ const Panel: FC> = ({ collapsed={collapsed} onCollapse={setCollapsed} > - {/* */} Date: Tue, 10 Jun 2025 10:13:46 +0800 Subject: [PATCH 082/126] data formatting --- web/app/components/workflow/nodes/tool/use-config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/web/app/components/workflow/nodes/tool/use-config.ts b/web/app/components/workflow/nodes/tool/use-config.ts index 41bf29912d..38c4454ec0 100644 --- a/web/app/components/workflow/nodes/tool/use-config.ts +++ b/web/app/components/workflow/nodes/tool/use-config.ts @@ -137,6 +137,7 @@ const useConfig = (id: string, payload: ToolNodeType) => { if (!draft.tool_parameters) draft.tool_parameters = {} + // TODO: boolean & model & app formatting BOTH configuration & parameters }) setInputs(inputsWithDefaultValue) // eslint-disable-next-line react-hooks/exhaustive-deps From 83c9ebbacc42aad9cc88bdebc8226697cff1c6da Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 10 Jun 2025 13:57:05 +0800 Subject: [PATCH 083/126] chore: json schma ui --- .../workflow/nodes/tool/components/tool-form/item.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web/app/components/workflow/nodes/tool/components/tool-form/item.tsx b/web/app/components/workflow/nodes/tool/components/tool-form/item.tsx index c91463461f..3aee79bb6e 100644 --- a/web/app/components/workflow/nodes/tool/components/tool-form/item.tsx +++ b/web/app/components/workflow/nodes/tool/components/tool-form/item.tsx @@ -50,15 +50,16 @@ const ToolFormItem: FC = ({ )} {showSchemaButton && ( <> -
·
+
·
From 1c44fb77afc4e59a48fa90532922b11d89ae7b64 Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 10 Jun 2025 14:13:38 +0800 Subject: [PATCH 084/126] feat: can show schema tooltip --- .../model-provider-page/declarations.ts | 4 ++++ .../components/workflow/nodes/agent/panel.tsx | 1 - .../nodes/tool/components/tool-form/item.tsx | 22 ++++++++++++++----- 3 files changed, 21 insertions(+), 6 deletions(-) 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 4da734361b..eec40797bd 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 @@ -1,3 +1,5 @@ +import type { SchemaRoot } from '@/app/components/workflow/nodes/llm/types' + export type FormValue = Record export type TypeWithI18N = { @@ -109,6 +111,7 @@ export type FormShowOnObject = { } export type CredentialFormSchemaBase = { + name: string variable: string label: TypeWithI18N type: FormTypeEnum @@ -118,6 +121,7 @@ export type CredentialFormSchemaBase = { show_on: FormShowOnObject[] url?: string scope?: string + input_schema?: SchemaRoot } export type CredentialFormSchemaTextInput = CredentialFormSchemaBase & { diff --git a/web/app/components/workflow/nodes/agent/panel.tsx b/web/app/components/workflow/nodes/agent/panel.tsx index 4dc2d40a03..a2f5afb276 100644 --- a/web/app/components/workflow/nodes/agent/panel.tsx +++ b/web/app/components/workflow/nodes/agent/panel.tsx @@ -80,7 +80,6 @@ const AgentPanel: FC> = (props) => { })() const resetEditor = useStore(s => s.setControlPromptEditorRerenderKey) - console.log(canChooseMCPTool) return
= ({ onChange, }) => { const language = useLanguage() - const { label, type, required, tooltip } = schema + const { name, label, type, required, tooltip, input_schema } = schema const showSchemaButton = type === FormTypeEnum.object || type === FormTypeEnum.array const showDescription = type === FormTypeEnum.textInput || type === FormTypeEnum.secretInput - + const [isShowSchema, { + setTrue: showSchema, + setFalse: hideSchema, + }] = useBoolean(false) return (
@@ -54,9 +59,7 @@ const ToolFormItem: FC = ({
) } From b73e64b97520b7011080b0fe38a1c8a51924e06a Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 10 Jun 2025 14:58:03 +0800 Subject: [PATCH 085/126] feat: can input json editor --- .../nodes/_base/components/editor/base.tsx | 2 +- .../components/editor/code-editor/index.tsx | 2 +- .../_base/components/form-input-item.tsx | 29 +++++++++++++++---- .../components/form-input-type-switch.tsx | 6 ++-- .../workflow/nodes/tool/use-config.ts | 5 +++- 5 files changed, 33 insertions(+), 11 deletions(-) diff --git a/web/app/components/workflow/nodes/_base/components/editor/base.tsx b/web/app/components/workflow/nodes/_base/components/editor/base.tsx index 38968b2e0d..c2b95cacf9 100644 --- a/web/app/components/workflow/nodes/_base/components/editor/base.tsx +++ b/web/app/components/workflow/nodes/_base/components/editor/base.tsx @@ -75,7 +75,7 @@ const Base: FC = ({ return ( -
+
{title}
{ diff --git a/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx b/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx index a185f16e2e..a7c0f1fc6d 100644 --- a/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx @@ -22,7 +22,7 @@ export type Props = { value?: string | object placeholder?: React.JSX.Element | string onChange?: (value: string) => void - title?: React.JSX.Element + title?: string | React.JSX.Element language: CodeLanguage headerRight?: React.JSX.Element readOnly?: boolean diff --git a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx index a50c7c86ad..c4c3221d09 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx @@ -20,6 +20,8 @@ import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-select import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector' import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' import cn from '@/utils/classnames' +import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' +import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' type Props = { readOnly: boolean @@ -54,12 +56,15 @@ const FormInputItem: FC = ({ const isNumber = type === FormTypeEnum.textNumber const isObject = type === FormTypeEnum.object const isArray = type === FormTypeEnum.array + const isShowJSONEditor = isObject || isArray const isBoolean = type === FormTypeEnum.boolean const isSelect = type === FormTypeEnum.select const isAppSelector = type === FormTypeEnum.appSelector const isModelSelector = type === FormTypeEnum.modelSelector const isFile = type === FormTypeEnum.file || type === FormTypeEnum.files const showTypeSwitch = isNumber || isObject || isArray + const isVariable = varInput?.type === VarKindType.variable + const isConstant = varInput?.type === VarKindType.constant const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, { onlyLeafNodeVar: false, @@ -168,9 +173,9 @@ const FormInputItem: FC = ({ }, []) return ( -
+
{showTypeSwitch && !hideTypeSwitch && ( - + )} {isString && ( = ({ placeholderClassName='!leading-[21px]' /> )} - {isNumber && varInput?.type === VarKindType.constant && ( + {isNumber && isConstant && ( = ({ placeholder={placeholder?.[language] || placeholder?.en_US} /> )} + {isShowJSONEditor && isConstant && ( +
+ {placeholder?.[language] || placeholder?.en_US}
} + /> +
+ )} {isAppSelector && ( = ({ onSelect={handleValueChange} /> )} - {isModelSelector && ( + {isModelSelector && isConstant && ( = ({ scope={scope} /> )} - {varInput?.type === VarKindType.variable && ( + {isVariable && ( void + value: VarType + onChange: (value: VarType) => void } const FormInputTypeSwitch: FC = ({ @@ -20,7 +20,7 @@ const FormInputTypeSwitch: FC = ({ }) => { const { t } = useTranslation() return ( -
+
diff --git a/web/app/components/workflow/nodes/tool/use-config.ts b/web/app/components/workflow/nodes/tool/use-config.ts index 38c4454ec0..08e00edf87 100644 --- a/web/app/components/workflow/nodes/tool/use-config.ts +++ b/web/app/components/workflow/nodes/tool/use-config.ts @@ -178,8 +178,11 @@ const useConfig = (id: string, payload: ToolNodeType) => { const res = produce(inputVarValues, (draft) => { Object.keys(inputs.tool_parameters).forEach((key: string) => { const { type, value } = inputs.tool_parameters[key] - if (type === VarType.constant && (value === undefined || value === null)) + if (type === VarType.constant && (value === undefined || value === null)) { + if(!draft.tool_parameters || !draft.tool_parameters[key]) + return draft.tool_parameters[key].value = value + } }) }) return res From 2efdacd28be3127ed45d6232c50838152b0e6845 Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 10 Jun 2025 15:06:17 +0800 Subject: [PATCH 086/126] fix: no var type value hide --- .../workflow/nodes/_base/components/form-input-item.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx index c4c3221d09..98dfc261e5 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx @@ -64,7 +64,7 @@ const FormInputItem: FC = ({ const isFile = type === FormTypeEnum.file || type === FormTypeEnum.files const showTypeSwitch = isNumber || isObject || isArray const isVariable = varInput?.type === VarKindType.variable - const isConstant = varInput?.type === VarKindType.constant + const isConstant = varInput?.type === VarKindType.constant || !varInput?.type const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, { onlyLeafNodeVar: false, From ec27b2ba85ab66e93906b0d5c1bbead1cb5983a1 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Tue, 10 Jun 2025 13:37:59 +0800 Subject: [PATCH 087/126] form type display --- .../_base/components/form-input-item.tsx | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx index 98dfc261e5..21c3ee9c67 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx @@ -57,14 +57,14 @@ const FormInputItem: FC = ({ const isObject = type === FormTypeEnum.object const isArray = type === FormTypeEnum.array const isShowJSONEditor = isObject || isArray + const isFile = type === FormTypeEnum.file || type === FormTypeEnum.files const isBoolean = type === FormTypeEnum.boolean const isSelect = type === FormTypeEnum.select const isAppSelector = type === FormTypeEnum.appSelector const isModelSelector = type === FormTypeEnum.modelSelector - const isFile = type === FormTypeEnum.file || type === FormTypeEnum.files const showTypeSwitch = isNumber || isObject || isArray - const isVariable = varInput?.type === VarKindType.variable - const isConstant = varInput?.type === VarKindType.constant || !varInput?.type + const isConstant = varInput?.type === VarKindType.constant + const showVariableSelector = isString || isFile || varInput?.type === VarKindType.variable const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, { onlyLeafNodeVar: false, @@ -78,10 +78,18 @@ const FormInputItem: FC = ({ return VarType.string else if (isNumber) return VarType.number - else if (isFile) + else if (type === FormTypeEnum.files) return VarType.arrayFile - else if (isBoolean) - return VarType.boolean + else if (type === FormTypeEnum.file) + return VarType.file + // else if (isSelect) + // return VarType.select + // else if (isAppSelector) + // return VarType.appSelector + // else if (isModelSelector) + // return VarType.modelSelector + // else if (isBoolean) + // return VarType.boolean else if (isObject) return VarType.object else if (isArray) @@ -253,7 +261,7 @@ const FormInputItem: FC = ({ scope={scope} /> )} - {isVariable && ( + {showVariableSelector && ( Date: Tue, 10 Jun 2025 15:02:36 +0800 Subject: [PATCH 088/126] fix tool parameters --- .../nodes/_base/components/form-input-item.tsx | 4 ++-- .../workflow/nodes/tool/use-config.ts | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx index 21c3ee9c67..6fdfd37849 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx @@ -64,7 +64,7 @@ const FormInputItem: FC = ({ const isModelSelector = type === FormTypeEnum.modelSelector const showTypeSwitch = isNumber || isObject || isArray const isConstant = varInput?.type === VarKindType.constant - const showVariableSelector = isString || isFile || varInput?.type === VarKindType.variable + const showVariableSelector = isFile || varInput?.type === VarKindType.variable const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, { onlyLeafNodeVar: false, @@ -110,7 +110,7 @@ const FormInputItem: FC = ({ else if (isObject) return (varPayload: any) => varPayload.type === VarType.object else if (isArray) - return (varPayload: any) => varPayload.type === VarType.arrayObject + return (varPayload: any) => [VarType.array, VarType.arrayString, VarType.arrayNumber, VarType.arrayObject].includes(varPayload.type) return undefined } diff --git a/web/app/components/workflow/nodes/tool/use-config.ts b/web/app/components/workflow/nodes/tool/use-config.ts index 08e00edf87..8b95237f64 100644 --- a/web/app/components/workflow/nodes/tool/use-config.ts +++ b/web/app/components/workflow/nodes/tool/use-config.ts @@ -35,7 +35,7 @@ const useConfig = (id: string, payload: ToolNodeType) => { * tool_parameters: tool dynamic setting(form type = llm) * output_schema: tool dynamic output */ - const { provider_id, provider_type, tool_name, tool_configurations, output_schema } = inputs + const { provider_id, provider_type, tool_name, tool_configurations, output_schema, tool_parameters } = inputs const isBuiltIn = provider_type === CollectionType.builtIn const buildInTools = useStore(s => s.buildInTools) const customTools = useStore(s => s.customTools) @@ -132,12 +132,19 @@ const useConfig = (id: string, payload: ToolNodeType) => { if (!currTool) return const inputsWithDefaultValue = produce(inputs, (draft) => { - if (!draft.tool_configurations || Object.keys(draft.tool_configurations).length === 0) + if (!draft.tool_configurations || Object.keys(draft.tool_configurations).length === 0) { draft.tool_configurations = getConfiguredValue(tool_configurations, toolSettingSchema) + } + else { + // TODO + } - if (!draft.tool_parameters) - draft.tool_parameters = {} - // TODO: boolean & model & app formatting BOTH configuration & parameters + if (!draft.tool_parameters || Object.keys(draft.tool_configurations).length === 0) { + draft.tool_parameters = getConfiguredValue(tool_parameters, toolInputVarSchema) + } + else { + // TODO: boolean & model & app formatting BOTH configuration & parameters + } }) setInputs(inputsWithDefaultValue) // eslint-disable-next-line react-hooks/exhaustive-deps From 32c1f9b263600fc24e8718a3b42d6f7d71faa222 Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 10 Jun 2025 15:14:15 +0800 Subject: [PATCH 089/126] fix: expand style error --- .../workflow/nodes/_base/components/form-input-item.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx index 6fdfd37849..0b45bfc7ef 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx @@ -234,6 +234,7 @@ const FormInputItem: FC = ({ title='JSON' value={varInput?.value as any} isExpand + isInNode height={100} language={CodeLanguage.json} onChange={handleValueChange} From 45941778c9d5ec10e56c74e01154732be5a33c48 Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 10 Jun 2025 15:16:24 +0800 Subject: [PATCH 090/126] fix: is contant detect --- .../workflow/nodes/_base/components/form-input-item.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx index 0b45bfc7ef..986093c19c 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx @@ -63,7 +63,7 @@ const FormInputItem: FC = ({ const isAppSelector = type === FormTypeEnum.appSelector const isModelSelector = type === FormTypeEnum.modelSelector const showTypeSwitch = isNumber || isObject || isArray - const isConstant = varInput?.type === VarKindType.constant + const isConstant = varInput?.type === VarKindType.constant || !varInput?.type const showVariableSelector = isFile || varInput?.type === VarKindType.variable const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, { From e0d7facdddad64befe5c9b58127d97def073e774 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Wed, 11 Jun 2025 15:54:01 +0800 Subject: [PATCH 091/126] data formatting of tool data --- .../components/tools/utils/to-form-schema.ts | 8 ++- .../_base/components/form-input-item.tsx | 14 ++++- .../components/workflow/nodes/tool/default.ts | 1 + .../components/workflow/nodes/tool/types.ts | 1 + .../workflow/nodes/tool/use-config.ts | 54 ++++++------------- .../workflow/utils/workflow-init.ts | 4 +- 6 files changed, 41 insertions(+), 41 deletions(-) diff --git a/web/app/components/tools/utils/to-form-schema.ts b/web/app/components/tools/utils/to-form-schema.ts index 0ea451759d..d476e687c1 100644 --- a/web/app/components/tools/utils/to-form-schema.ts +++ b/web/app/components/tools/utils/to-form-schema.ts @@ -110,16 +110,22 @@ export const getConfiguredValue = (value: Record, formSchemas: { va if (formSchema.type === 'boolean') { if (typeof value === 'string') - newValues[formSchema.variable].value = value === 'true' + newValues[formSchema.variable].value = value === 'true' || value === '1' if (typeof value === 'boolean') newValues[formSchema.variable].value = value + + if (typeof value === 'number') + newValues[formSchema.variable].value = value === 1 } if (formSchema.type === 'number-input') { if (typeof value === 'string' && value !== '') newValues[formSchema.variable].value = Number.parseFloat(value) } + + if (formSchema.type === 'app-selector' || formSchema.type === 'model-selector') + newValues[formSchema.variable] = value } }) return newValues diff --git a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx index 986093c19c..d5444872af 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx @@ -157,6 +157,16 @@ const FormInputItem: FC = ({ }) } + const handleAppOrModelSelect = (newValue: any) => { + onChange({ + ...value, + [variable]: { + ...varInput, + ...newValue, + }, + }) + } + const handleVariableSelectorChange = (newValue: ValueSelector | string, variable: string) => { onChange({ ...value, @@ -248,7 +258,7 @@ const FormInputItem: FC = ({ disabled={readOnly} scope={scope || 'all'} value={varInput?.value as any} - onSelect={handleValueChange} + onSelect={handleAppOrModelSelect} /> )} {isModelSelector && isConstant && ( @@ -257,7 +267,7 @@ const FormInputItem: FC = ({ isAdvancedMode isInWorkflow value={varInput?.value as any} - setModel={handleValueChange} + setModel={handleAppOrModelSelect} readonly={readOnly} scope={scope} /> diff --git a/web/app/components/workflow/nodes/tool/default.ts b/web/app/components/workflow/nodes/tool/default.ts index f245929684..a73274b713 100644 --- a/web/app/components/workflow/nodes/tool/default.ts +++ b/web/app/components/workflow/nodes/tool/default.ts @@ -10,6 +10,7 @@ const nodeDefault: NodeDefault = { defaultValue: { tool_parameters: {}, tool_configurations: {}, + version: '2', }, getAvailablePrevNodes(isChatMode: boolean) { const nodes = isChatMode diff --git a/web/app/components/workflow/nodes/tool/types.ts b/web/app/components/workflow/nodes/tool/types.ts index 4b78c53ab2..4584645a1e 100644 --- a/web/app/components/workflow/nodes/tool/types.ts +++ b/web/app/components/workflow/nodes/tool/types.ts @@ -22,4 +22,5 @@ export type ToolNodeType = CommonNodeType & { tool_configurations: Record output_schema: Record paramSchemas?: Record[] + version?: string } diff --git a/web/app/components/workflow/nodes/tool/use-config.ts b/web/app/components/workflow/nodes/tool/use-config.ts index 8b95237f64..b04193fe10 100644 --- a/web/app/components/workflow/nodes/tool/use-config.ts +++ b/web/app/components/workflow/nodes/tool/use-config.ts @@ -14,8 +14,7 @@ import { } from '@/app/components/tools/utils/to-form-schema' import Toast from '@/app/components/base/toast' import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form' -import { VarType as VarVarType } from '@/app/components/workflow/types' -import type { InputVar, ValueSelector, Var } from '@/app/components/workflow/types' +import type { InputVar, ValueSelector } from '@/app/components/workflow/types' import useOneStepRun from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run' import { useFetchToolsData, @@ -42,7 +41,7 @@ const useConfig = (id: string, payload: ToolNodeType) => { const workflowTools = useStore(s => s.workflowTools) const mcpTools = useStore(s => s.mcpTools) - const currentTools = (() => { + const currentTools = useMemo(() => { switch (provider_type) { case CollectionType.builtIn: return buildInTools @@ -55,7 +54,7 @@ const useConfig = (id: string, payload: ToolNodeType) => { default: return [] } - })() + }, [buildInTools, customTools, mcpTools, provider_type, workflowTools]) const currCollection = currentTools.find(item => canFindTool(item.id, provider_id)) // Auth @@ -99,10 +98,10 @@ const useConfig = (id: string, payload: ToolNodeType) => { const value = newConfig[key] if (schema?.type === 'boolean') { if (typeof value === 'string') - newConfig[key] = Number.parseInt(value, 10) + newConfig[key] = value === 'true' || value === '1' - if (typeof value === 'boolean') - newConfig[key] = value ? 1 : 0 + if (typeof value === 'number') + newConfig[key] = value === 1 } if (schema?.type === 'number-input') { @@ -128,24 +127,20 @@ const useConfig = (id: string, payload: ToolNodeType) => { }) }, [inputs, setInputs]) + const formattingParameters = () => { + const inputsWithDefaultValue = produce(inputs, (draft) => { + if (!draft.tool_configurations || Object.keys(draft.tool_configurations).length === 0) + draft.tool_configurations = getConfiguredValue(tool_configurations, toolSettingSchema) + if (!draft.tool_parameters || Object.keys(draft.tool_parameters).length === 0) + draft.tool_parameters = getConfiguredValue(tool_parameters, toolInputVarSchema) + }) + return inputsWithDefaultValue + } + useEffect(() => { if (!currTool) return - const inputsWithDefaultValue = produce(inputs, (draft) => { - if (!draft.tool_configurations || Object.keys(draft.tool_configurations).length === 0) { - draft.tool_configurations = getConfiguredValue(tool_configurations, toolSettingSchema) - } - else { - // TODO - } - - if (!draft.tool_parameters || Object.keys(draft.tool_configurations).length === 0) { - draft.tool_parameters = getConfiguredValue(tool_parameters, toolInputVarSchema) - } - else { - // TODO: boolean & model & app formatting BOTH configuration & parameters - } - }) + const inputsWithDefaultValue = formattingParameters() setInputs(inputsWithDefaultValue) // eslint-disable-next-line react-hooks/exhaustive-deps }, [currTool]) @@ -158,19 +153,6 @@ const useConfig = (id: string, payload: ToolNodeType) => { }) }, [inputs, setInputs]) - const [currVarIndex, setCurrVarIndex] = useState(-1) - const currVarType = toolInputVarSchema[currVarIndex]?._type - const handleOnVarOpen = useCallback((index: number) => { - setCurrVarIndex(index) - }, []) - - const filterVar = useCallback((varPayload: Var) => { - if (currVarType) - return varPayload.type === currVarType - - return varPayload.type !== VarVarType.arrayFile - }, [currVarType]) - const isLoading = currTool && (isBuiltIn ? !currCollection : false) // single run @@ -314,8 +296,6 @@ const useConfig = (id: string, payload: ToolNodeType) => { setToolSettingValue, toolInputVarSchema, setInputVar, - handleOnVarOpen, - filterVar, currCollection, isShowAuthBtn, showSetAuth, diff --git a/web/app/components/workflow/utils/workflow-init.ts b/web/app/components/workflow/utils/workflow-init.ts index dd952ad98f..8610bbe9d1 100644 --- a/web/app/components/workflow/utils/workflow-init.ts +++ b/web/app/components/workflow/utils/workflow-init.ts @@ -286,7 +286,9 @@ export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => { } } - if (node.data.type === BlockEnum.Tool) { + if (node.data.type === BlockEnum.Tool && !(node as Node).data.version) { + (node as Node).data.version = '2' + const toolConfigurations = (node as Node).data.tool_configurations if (toolConfigurations && Object.keys(toolConfigurations).length > 0) { const newValues = { ...toolConfigurations } From 8af635459a0ed8175bc0739cb20e7d59d56ba843 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Wed, 11 Jun 2025 17:07:26 +0800 Subject: [PATCH 092/126] single run of tool --- .../_base/components/form-input-item.tsx | 2 +- .../components/workflow/nodes/tool/default.ts | 2 ++ .../workflow/nodes/tool/use-config.ts | 19 +++++++++++++++---- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx index d5444872af..5e934b439b 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx @@ -172,7 +172,7 @@ const FormInputItem: FC = ({ ...value, [variable]: { ...varInput, - type: getVarKindType(), + type: VarKindType.variable, value: newValue || '', }, }) diff --git a/web/app/components/workflow/nodes/tool/default.ts b/web/app/components/workflow/nodes/tool/default.ts index a73274b713..1fdb9eed2d 100644 --- a/web/app/components/workflow/nodes/tool/default.ts +++ b/web/app/components/workflow/nodes/tool/default.ts @@ -56,6 +56,8 @@ const nodeDefault: NodeDefault = { const value = payload.tool_configurations[field.variable] if (!errorMessages && (value === undefined || value === null || value === '')) errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: field.label[language] }) + if (!errorMessages && typeof value === 'object' && !!value.type && (value.value === undefined || value.value === null || value.value === '' || (Array.isArray(value.value) && value.value.length === 0))) + errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: field.label[language] }) }) } diff --git a/web/app/components/workflow/nodes/tool/use-config.ts b/web/app/components/workflow/nodes/tool/use-config.ts index b04193fe10..8722573182 100644 --- a/web/app/components/workflow/nodes/tool/use-config.ts +++ b/web/app/components/workflow/nodes/tool/use-config.ts @@ -213,7 +213,11 @@ const useConfig = (id: string, payload: ToolNodeType) => { .filter(key => inputs.tool_parameters[key].type !== VarType.constant) .map(k => inputs.tool_parameters[k]) - const varInputs = getInputVars(hadVarParams.map((p) => { + const hadVarSettings = Object.keys(inputs.tool_configurations) + .filter(key => typeof inputs.tool_configurations[key] === 'object' && inputs.tool_configurations[key].type && inputs.tool_configurations[key].type !== VarType.constant) + .map(k => inputs.tool_configurations[k]) + + const varInputs = getInputVars([...hadVarParams, ...hadVarSettings].map((p) => { if (p.type === VarType.variable) { // handle the old wrong value not crash the page if (!(p.value as any).join) @@ -237,16 +241,23 @@ const useConfig = (id: string, payload: ToolNodeType) => { const handleRun = (submitData: Record) => { const varTypeInputKeys = Object.keys(inputs.tool_parameters) .filter(key => inputs.tool_parameters[key].type === VarType.variable) - const shouldAdd = varTypeInputKeys.length > 0 + const varTypeSettingKeys = Object.keys(inputs.tool_configurations) + .filter(key => typeof inputs.tool_configurations[key] === 'object' && inputs.tool_configurations[key].type === VarType.variable) + const mergedInputKeys = [...varTypeInputKeys, ...varTypeSettingKeys] + const shouldAdd = mergedInputKeys.length > 0 if (!shouldAdd) { doHandleRun(submitData) return } const addMissedVarData = { ...submitData } + const mergedValueMap = { + ...inputs.tool_parameters, + ...inputs.tool_configurations, + } Object.keys(submitData).forEach((key) => { const value = submitData[key] - varTypeInputKeys.forEach((inputKey) => { - const inputValue = inputs.tool_parameters[inputKey].value as ValueSelector + mergedInputKeys.forEach((inputKey) => { + const inputValue = mergedValueMap[inputKey].value as ValueSelector if (`#${inputValue.join('.')}#` === key) addMissedVarData[inputKey] = value }) From e3fcee124aae5b283bbcda5b634fe99320363ac1 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Wed, 11 Jun 2025 22:47:31 +0800 Subject: [PATCH 093/126] agent node --- .../tool-selector/index.tsx | 31 +- .../tool-selector/reasoning-config-form.tsx | 280 ++++++++++-------- .../components/tools/utils/to-form-schema.ts | 77 +++-- .../_base/components/form-input-item.tsx | 2 +- .../workflow/nodes/agent/default.ts | 18 +- .../components/workflow/nodes/agent/types.ts | 1 + .../workflow/utils/workflow-init.ts | 8 + 7 files changed, 232 insertions(+), 185 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 ec35db953a..fb047e195c 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 @@ -5,7 +5,6 @@ import { useTranslation } from 'react-i18next' import Link from 'next/link' import { RiArrowLeftLine, - RiArrowRightUpLine, } from '@remixicon/react' import { PortalToFollowElem, @@ -15,6 +14,7 @@ import { import ToolTrigger from '@/app/components/plugins/plugin-detail-panel/tool-selector/tool-trigger' import ToolItem from '@/app/components/plugins/plugin-detail-panel/tool-selector/tool-item' import ToolPicker from '@/app/components/workflow/block-selector/tool-picker' +import ToolForm from '@/app/components/workflow/nodes/tool/components/tool-form' import Button from '@/app/components/base/button' import Indicator from '@/app/components/header/indicator' import ToolCredentialForm from '@/app/components/plugins/plugin-detail-panel/tool-selector/tool-credentials-form' @@ -23,8 +23,7 @@ import Textarea from '@/app/components/base/textarea' import Divider from '@/app/components/base/divider' import TabSlider from '@/app/components/base/tab-slider-plain' import ReasoningConfigForm from '@/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form' -import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form' -import { generateFormValue, getPlainValue, getStructureValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' +import { generateFormValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' import { useAppContext } from '@/context/app-context' import { @@ -173,11 +172,9 @@ const ToolSelector: FC = ({ const paramsFormSchemas = useMemo(() => toolParametersToFormSchemas(currentToolParams), [currentToolParams]) const handleSettingsFormChange = (v: Record) => { - const newValue = getStructureValue(v) - const toolValue = { ...value, - settings: newValue, + settings: v, } onSelect(toolValue as any) } @@ -400,24 +397,12 @@ const ToolSelector: FC = ({ {/* user settings form */} {(currType === 'settings' || userSettingsOnly) && (
- item.url - ? ( - {t('tools.howToGet')} - - ) - : null} />
)} 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 017e0eb7aa..0dae8f64e1 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 @@ -7,17 +7,22 @@ import { } from '@remixicon/react' import Tooltip from '@/app/components/base/tooltip' import Switch from '@/app/components/base/switch' -import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var' +import MixedInput from '@/app/components/workflow/nodes/_base/components/input-support-select-var' +import Input from '@/app/components/base/input' +import FormInputTypeSwitch from '@/app/components/workflow/nodes/_base/components/form-input-type-switch' +import FormInputBoolean from '@/app/components/workflow/nodes/_base/components/form-input-boolean' +import { SimpleSelect } from '@/app/components/base/select' +import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector' import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector' +import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { Node } from 'reactflow' import type { NodeOutPutVar, ValueSelector, - Var, } from '@/app/components/workflow/types' import type { ToolVarInputs } from '@/app/components/workflow/nodes/tool/types' import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' @@ -46,14 +51,13 @@ const ReasoningConfigForm: React.FC = ({ }) => { const { t } = useTranslation() const language = useLanguage() - const handleAutomatic = (key: string, val: any) => { - onChange({ - ...value, - [key]: { - value: val ? null : value[key]?.value, - auto: val ? 1 : 0, - }, - }) + const getVarKindType = (type: FormTypeEnum) => { + if (type === FormTypeEnum.file || type === FormTypeEnum.files) + return VarKindType.variable + if (type === FormTypeEnum.select || type === FormTypeEnum.boolean || type === FormTypeEnum.textNumber) + return VarKindType.constant + if (type === FormTypeEnum.textInput || type === FormTypeEnum.secretInput) + return VarKindType.mixed } const [inputsIsFocus, setInputsIsFocus] = useState>({}) @@ -67,52 +71,38 @@ const ReasoningConfigForm: React.FC = ({ }) } }, []) - const handleNotMixedTypeChange = useCallback((variable: string) => { - return (varValue: ValueSelector | string, varKindType: VarKindType) => { - const newValue = produce(value, (draft: ToolVarInputs) => { - const target = draft[variable].value - if (target) { - target.type = varKindType - target.value = varValue - } - else { - draft[variable].value = { - type: varKindType, - value: varValue, - } - } - }) - onChange(newValue) - } - }, [value, onChange]) - const handleMixedTypeChange = useCallback((variable: string) => { - return (itemValue: string) => { - const newValue = produce(value, (draft: ToolVarInputs) => { - const target = draft[variable].value - if (target) { - target.value = itemValue - } - else { - draft[variable].value = { - type: VarKindType.mixed, - value: itemValue, - } - } - }) - onChange(newValue) - } - }, [value, onChange]) - const handleFileChange = useCallback((variable: string) => { - return (varValue: ValueSelector | string) => { - const newValue = produce(value, (draft: ToolVarInputs) => { + + const handleAutomatic = (key: string, val: any, type: FormTypeEnum) => { + onChange({ + ...value, + [key]: { + value: val ? null : { type: getVarKindType(type), value: null }, + auto: val ? 1 : 0, + }, + }) + } + const handleTypeChange = useCallback((variable: string, defaultValue: any) => { + return (newType: VarKindType) => { + const res = produce(value, (draft: ToolVarInputs) => { draft[variable].value = { - type: VarKindType.variable, - value: varValue, + type: newType, + value: newType === VarKindType.variable ? '' : defaultValue, } }) - onChange(newValue) + onChange(res) } - }, [value, onChange]) + }, [onChange, value]) + const handleValueChange = useCallback((variable: string, varType: FormTypeEnum) => { + return (newValue: any) => { + const res = produce(value, (draft: ToolVarInputs) => { + draft[variable].value = { + type: getVarKindType(varType), + value: newValue, + } + }) + onChange(res) + } + }, [onChange, value]) const handleAppChange = useCallback((variable: string) => { return (app: { app_id: string @@ -136,6 +126,17 @@ const ReasoningConfigForm: React.FC = ({ onChange(newValue) } }, [onChange, value]) + const handleVariableSelectorChange = useCallback((variable: string) => { + return (newValue: ValueSelector | string) => { + const res = produce(value, (draft: ToolVarInputs) => { + draft[variable].value = { + type: VarKindType.variable, + value: newValue, + } + }) + onChange(res) + } + }, [onChange, value]) const [isShowSchema, { setTrue: showSchema, @@ -147,6 +148,7 @@ const ReasoningConfigForm: React.FC = ({ const renderField = (schema: any, showSchema: (schema: SchemaRoot, rootName: string) => void) => { const { + default: defaultValue, variable, label, required, @@ -155,6 +157,8 @@ const ReasoningConfigForm: React.FC = ({ scope, url, input_schema, + placeholder, + options, } = schema const auto = value[variable]?.auto const tooltipContent = (tooltip && ( @@ -166,28 +170,55 @@ const ReasoningConfigForm: React.FC = ({ asChild={false} /> )) const varInput = value[variable].value + const isString = type === FormTypeEnum.textInput || type === FormTypeEnum.secretInput 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 isShowJSONEditor = isObject || isArray + const isFile = type === FormTypeEnum.file || type === FormTypeEnum.files + const isBoolean = type === FormTypeEnum.boolean + const isSelect = type === FormTypeEnum.select const isAppSelector = type === FormTypeEnum.appSelector const isModelSelector = type === FormTypeEnum.modelSelector - // const isToolSelector = type === FormTypeEnum.toolSelector - 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 - })() + const showTypeSwitch = isNumber || isObject || isArray + const isConstant = varInput?.type === VarKindType.constant || !varInput?.type + const showVariableSelector = isFile || varInput?.type === VarKindType.variable + const targetVarType = () => { + if (isString) + return VarType.string + else if (isNumber) + return VarType.number + else if (type === FormTypeEnum.files) + return VarType.arrayFile + else if (type === FormTypeEnum.file) + return VarType.file + else if (isBoolean) + return VarType.boolean + else if (isObject) + return VarType.object + else if (isArray) + return VarType.arrayObject + else + return VarType.string + } + const getFilterVar = () => { + if (isNumber) + return (varPayload: any) => varPayload.type === VarType.number + else if (isString) + return (varPayload: any) => [VarType.string, VarType.number, VarType.secret].includes(varPayload.type) + else if (isFile) + return (varPayload: any) => [VarType.file, VarType.arrayFile].includes(varPayload.type) + else if (isBoolean) + return (varPayload: any) => varPayload.type === VarType.boolean + else if (isObject) + return (varPayload: any) => varPayload.type === VarType.object + else if (isArray) + return (varPayload: any) => [VarType.array, VarType.arrayString, VarType.arrayNumber, VarType.arrayObject].includes(varPayload.type) + return undefined + } return ( -
+
{label[language] || label.en_US} @@ -196,8 +227,8 @@ const ReasoningConfigForm: React.FC = ({ )} {tooltipContent} · - {valueType} - {isShowSchemaTooltip && ( + {targetVarType()} + {isShowJSONEditor && ( {t('workflow.nodes.agent.clickToViewParameterSchema')} @@ -213,22 +244,25 @@ const ReasoningConfigForm: React.FC = ({ )}
-
handleAutomatic(variable, !auto)}> +
handleAutomatic(variable, !auto, type)}> {t('plugin.detailPanel.toolSelector.auto')} handleAutomatic(variable, val)} + onChange={val => handleAutomatic(variable, val, type)} />
{auto === 0 && ( - <> +
+ {showTypeSwitch && ( + + )} {isString && ( - = ({ placeholderClassName='!leading-[21px]' /> )} - {/* {isString && ( - varPayload.type === VarType.number || varPayload.type === VarType.secret || varPayload.type === VarType.string} - /> - )} */} - {(isNumber || isSelect) && ( - varPayload.type === schema._type : undefined} - availableVars={isSelect ? nodeOutputVars : undefined} - schema={schema} + onChange={handleValueChange(variable, type)} + placeholder={placeholder?.[language] || placeholder?.en_US} /> )} - {(isFile || isObject || isArray) && ( - { - 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 - }} + {isBoolean && ( + )} + {isSelect && ( + { + if (option.show_on.length) + return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value) + + return true + }).map((option: { value: any; label: { [x: string]: any; en_US: any } }) => ({ value: option.value, name: option.label[language] || option.label.en_US }))} + onSelect={item => handleValueChange(variable, type)(item.value as string)} + placeholder={placeholder?.[language] || placeholder?.en_US} + /> + )} + {isShowJSONEditor && isConstant && ( +
+ {placeholder?.[language] || placeholder?.en_US}
} + /> +
+ )} {isAppSelector && ( = ({ scope={scope} /> )} - + {showVariableSelector && ( + + )} +
)} {url && ( , formSchemas: { varia return newValues } -export const generateFormValue = (value: Record, formSchemas: { variable: string; default?: any }[], isReasoning = false) => { +const correctInitialData = (type: string, target: any, defaultValue: any) => { + if (type === 'text-input' || type === 'secret-input') + target.type = 'mixed' + + if (type === 'boolean') { + if (typeof defaultValue === 'string') + target.value = defaultValue === 'true' || defaultValue === '1' + + if (typeof defaultValue === 'boolean') + target.value = defaultValue + + if (typeof defaultValue === 'number') + target.value = defaultValue === 1 + } + + if (type === 'number-input') { + if (typeof defaultValue === 'string' && defaultValue !== '') + target.value = Number.parseFloat(defaultValue) + } + + if (type === 'app-selector' || type === 'model-selector') + target.value = defaultValue + + return target +} + +export const generateFormValue = (value: Record, formSchemas: { variable: string; default?: any; type: string }[], isReasoning = false) => { const newValues = {} as any formSchemas.forEach((formSchema) => { const itemValue = value[formSchema.variable] if ((formSchema.default !== undefined) && (value === undefined || itemValue === null || itemValue === '' || itemValue === undefined)) { + const value = formSchema.default newValues[formSchema.variable] = { - ...(isReasoning ? { value: null, auto: 1 } : { value: formSchema.default }), + value: { + type: 'constant', + value: formSchema.default, + }, + ...(isReasoning ? { auto: 1, value: null } : {}), } + if (!isReasoning) + newValues[formSchema.variable].value = correctInitialData(formSchema.type, newValues[formSchema.variable].value, value) } }) return newValues } -export const getPlainValue = (value: Record) => { - const plainValue = { ...value } - Object.keys(plainValue).forEach((key) => { - plainValue[key] = value[key].value - }) - return plainValue -} - -export const getStructureValue = (value: Record) => { - const newValue = { ...value } as any - Object.keys(newValue).forEach((key) => { - newValue[key] = { - value: value[key], - } - }) - return newValue -} - export const getConfiguredValue = (value: Record, formSchemas: { variable: string; type: string; default?: any }[]) => { const newValues = { ...value } formSchemas.forEach((formSchema) => { @@ -105,27 +120,7 @@ export const getConfiguredValue = (value: Record, formSchemas: { va type: 'constant', value: formSchema.default, } - if (formSchema.type === 'text-input' || formSchema.type === 'secret-input') - newValues[formSchema.variable].type = 'mixed' - - if (formSchema.type === 'boolean') { - if (typeof value === 'string') - newValues[formSchema.variable].value = value === 'true' || value === '1' - - if (typeof value === 'boolean') - newValues[formSchema.variable].value = value - - if (typeof value === 'number') - newValues[formSchema.variable].value = value === 1 - } - - if (formSchema.type === 'number-input') { - if (typeof value === 'string' && value !== '') - newValues[formSchema.variable].value = Number.parseFloat(value) - } - - if (formSchema.type === 'app-selector' || formSchema.type === 'model-selector') - newValues[formSchema.variable] = value + newValues[formSchema.variable] = correctInitialData(formSchema.type, newValues[formSchema.variable], value) } }) return newValues diff --git a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx index 5e934b439b..c0468a5ecc 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx @@ -117,7 +117,7 @@ const FormInputItem: FC = ({ const getVarKindType = () => { if (isFile) return VarKindType.variable - if (isSelect || isAppSelector || isModelSelector || isBoolean) + if (isSelect || isBoolean || isNumber) return VarKindType.constant if (isString) return VarKindType.mixed diff --git a/web/app/components/workflow/nodes/agent/default.ts b/web/app/components/workflow/nodes/agent/default.ts index 4e7b447de8..55cb7de377 100644 --- a/web/app/components/workflow/nodes/agent/default.ts +++ b/web/app/components/workflow/nodes/agent/default.ts @@ -7,6 +7,7 @@ import { renderI18nObject } from '@/i18n' const nodeDefault: NodeDefault = { defaultValue: { + version: '2', }, getAvailablePrevNodes(isChatMode) { return isChatMode @@ -60,15 +61,28 @@ const nodeDefault: NodeDefault = { const schemas = toolValue.schemas || [] const userSettings = toolValue.settings const reasoningConfig = toolValue.parameters + const version = payload.version schemas.forEach((schema: any) => { if (schema?.required) { - if (schema.form === 'form' && !userSettings[schema.name]?.value) { + if (schema.form === 'form' && !version && !userSettings[schema.name]?.value) { return { isValid: false, errorMessage: t('workflow.errorMsg.toolParameterRequired', { field: renderI18nObject(param.label, language), param: renderI18nObject(schema.label, language) }), } } - if (schema.form === 'llm' && reasoningConfig[schema.name].auto === 0 && !userSettings[schema.name]?.value) { + if (schema.form === 'form' && version && !userSettings[schema.name]?.value.value) { + return { + isValid: false, + errorMessage: t('workflow.errorMsg.toolParameterRequired', { field: renderI18nObject(param.label, language), param: renderI18nObject(schema.label, language) }), + } + } + if (schema.form === 'llm' && !version && reasoningConfig[schema.name].auto === 0 && !reasoningConfig[schema.name]?.value) { + return { + isValid: false, + errorMessage: t('workflow.errorMsg.toolParameterRequired', { field: renderI18nObject(param.label, language), param: renderI18nObject(schema.label, language) }), + } + } + if (schema.form === 'llm' && version && reasoningConfig[schema.name].auto === 0 && !reasoningConfig[schema.name]?.value.value) { return { isValid: false, errorMessage: t('workflow.errorMsg.toolParameterRequired', { field: renderI18nObject(param.label, language), param: renderI18nObject(schema.label, language) }), diff --git a/web/app/components/workflow/nodes/agent/types.ts b/web/app/components/workflow/nodes/agent/types.ts index e50586bd27..5a13a4a4f3 100644 --- a/web/app/components/workflow/nodes/agent/types.ts +++ b/web/app/components/workflow/nodes/agent/types.ts @@ -11,6 +11,7 @@ export type AgentNodeType = CommonNodeType & { output_schema: Record plugin_unique_identifier?: string memory?: Memory + version?: string } export enum AgentFeature { diff --git a/web/app/components/workflow/utils/workflow-init.ts b/web/app/components/workflow/utils/workflow-init.ts index 8610bbe9d1..cd4ab1a095 100644 --- a/web/app/components/workflow/utils/workflow-init.ts +++ b/web/app/components/workflow/utils/workflow-init.ts @@ -27,6 +27,7 @@ import type { QuestionClassifierNodeType } from '../nodes/question-classifier/ty import type { IfElseNodeType } from '../nodes/if-else/types' import { branchNameCorrect } from '../nodes/if-else/utils' import type { IterationNodeType } from '../nodes/iteration/types' +import type { AgentNodeType } from '../nodes/agent/types' import type { LoopNodeType } from '../nodes/loop/types' import type { ToolNodeType } from '../nodes/tool/types' import { @@ -304,6 +305,13 @@ export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => { } } + if (node.data.type === BlockEnum.Agent && !(node as Node).data.version) { + // TODO: formatting legacy agent node data + // (node as Node).data.version = '2' + // const toolData = (node as Node).data.agent_parameters?.tool + // const multipleTools = (node as Node).data.agent_parameters?.multiple_tools + } + return node }) } From f4ed447f396e57e4c3f59a67dc5b52ffdf474bc0 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Wed, 11 Jun 2025 22:53:04 +0800 Subject: [PATCH 094/126] fix z-index --- .../plugin-detail-panel/tool-selector/reasoning-config-form.tsx | 1 + 1 file changed, 1 insertion(+) 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 0dae8f64e1..c77ea74c88 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 @@ -334,6 +334,7 @@ const ReasoningConfigForm: React.FC = ({ )} {showVariableSelector && ( Date: Thu, 12 Jun 2025 09:40:53 +0800 Subject: [PATCH 095/126] fix z-index --- .../workflow/nodes/_base/components/form-input-item.tsx | 7 ++++--- .../workflow/nodes/tool/components/tool-form/index.tsx | 3 +++ .../workflow/nodes/tool/components/tool-form/item.tsx | 3 +++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx index c0468a5ecc..d528e95285 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx @@ -29,7 +29,7 @@ type Props = { schema: CredentialFormSchema value: ToolVarInputs onChange: (value: any) => void - hideTypeSwitch?: boolean + inPanel?: boolean } const FormInputItem: FC = ({ @@ -38,7 +38,7 @@ const FormInputItem: FC = ({ schema, value, onChange, - hideTypeSwitch, + inPanel, }) => { const { t } = useTranslation() const language = useLanguage() @@ -192,7 +192,7 @@ const FormInputItem: FC = ({ return (
- {showTypeSwitch && !hideTypeSwitch && ( + {showTypeSwitch && ( )} {isString && ( @@ -274,6 +274,7 @@ const FormInputItem: FC = ({ )} {showVariableSelector && ( void onOpen?: (index: number) => void + inPanel?: boolean } const ToolForm: FC = ({ @@ -19,6 +20,7 @@ const ToolForm: FC = ({ schema, value, onChange, + inPanel, }) => { return (
@@ -31,6 +33,7 @@ const ToolForm: FC = ({ schema={schema} value={value} onChange={onChange} + inPanel={inPanel} /> )) } diff --git a/web/app/components/workflow/nodes/tool/components/tool-form/item.tsx b/web/app/components/workflow/nodes/tool/components/tool-form/item.tsx index 49f8111306..2ef3c93ede 100644 --- a/web/app/components/workflow/nodes/tool/components/tool-form/item.tsx +++ b/web/app/components/workflow/nodes/tool/components/tool-form/item.tsx @@ -19,6 +19,7 @@ type Props = { schema: CredentialFormSchema value: ToolVarInputs onChange: (value: ToolVarInputs) => void + inPanel?: boolean } const ToolFormItem: FC = ({ @@ -27,6 +28,7 @@ const ToolFormItem: FC = ({ schema, value, onChange, + inPanel, }) => { const language = useLanguage() const { name, label, type, required, tooltip, input_schema } = schema @@ -78,6 +80,7 @@ const ToolFormItem: FC = ({ schema={schema} value={value} onChange={onChange} + inPanel={inPanel} /> {isShowSchema && ( From b2b74e66ad184d67f4dfdded3bcd0e347affed3b Mon Sep 17 00:00:00 2001 From: JzoNg Date: Thu, 12 Jun 2025 09:43:57 +0800 Subject: [PATCH 096/126] fix z-index --- .../plugins/plugin-detail-panel/tool-selector/index.tsx | 1 + 1 file changed, 1 insertion(+) 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 fb047e195c..7a378b178a 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 @@ -398,6 +398,7 @@ const ToolSelector: FC = ({ {(currType === 'settings' || userSettingsOnly) && (
Date: Thu, 12 Jun 2025 10:15:40 +0800 Subject: [PATCH 097/126] fix z-index --- .../_base/components/variable/var-reference-picker.tsx | 1 + .../_base/components/variable/var-reference-popup.tsx | 3 +++ .../nodes/_base/components/variable/var-reference-vars.tsx | 7 ++++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx index 789da34f9d..9007aa80df 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx @@ -437,6 +437,7 @@ const VarReferencePicker: FC = ({ onChange={handleVarReferenceChange} itemWidth={isAddBtnTrigger ? 260 : (minWidth || triggerWidth)} isSupportFileVar={isSupportFileVar} + zIndex={zIndex} /> )} diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-popup.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-popup.tsx index e35977ae31..a8b52f8a4e 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-popup.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-popup.tsx @@ -15,6 +15,7 @@ type Props = { onChange: (value: ValueSelector, varDetail: Var) => void itemWidth?: number isSupportFileVar?: boolean + zIndex?: number } const VarReferencePopup: FC = ({ vars, @@ -22,6 +23,7 @@ const VarReferencePopup: FC = ({ onChange, itemWidth, isSupportFileVar = true, + zIndex, }) => { const { t } = useTranslation() const { locale } = useContext(I18n) @@ -57,6 +59,7 @@ const VarReferencePopup: FC = ({ onChange={onChange} itemWidth={itemWidth} isSupportFileVar={isSupportFileVar} + zIndex={zIndex} /> }
diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx index 023916ec5b..7cc2171029 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx @@ -46,6 +46,7 @@ type ItemProps = { isSupportFileVar?: boolean isException?: boolean isLoopVar?: boolean + zIndex?: number } const objVarTypes = [VarType.object, VarType.file] @@ -60,6 +61,7 @@ const Item: FC = ({ isSupportFileVar, isException, isLoopVar, + zIndex, }) => { const isStructureOutput = itemData.type === VarType.object && (itemData.children as StructuredOutput)?.schema?.properties const isFile = itemData.type === VarType.file && !isStructureOutput @@ -171,7 +173,7 @@ const Item: FC = ({
{(isStructureOutput || isObj) && ( void onBlur?: () => void + zIndex?: number } const VarReferenceVars: FC = ({ hideSearch, @@ -271,6 +274,7 @@ const VarReferenceVars: FC = ({ maxHeightClass, onClose, onBlur, + zIndex, }) => { const { t } = useTranslation() const [searchText, setSearchText] = useState('') @@ -355,6 +359,7 @@ const VarReferenceVars: FC = ({ isSupportFileVar={isSupportFileVar} isException={v.isException} isLoopVar={item.isLoop} + zIndex={zIndex} /> ))}
)) From e438d08a39cd6e83e86a059ae3a67b12d4adbc9a Mon Sep 17 00:00:00 2001 From: JzoNg Date: Thu, 12 Jun 2025 14:37:53 +0800 Subject: [PATCH 098/126] agent node legacy data fomatting --- .../tool-selector/index.tsx | 7 +- .../components/tools/utils/to-form-schema.ts | 69 +++++++++++++++++++ .../_base/components/form-input-item.tsx | 2 +- .../workflow/nodes/agent/use-config.ts | 43 +++++++++++- .../workflow/utils/workflow-init.ts | 8 --- 5 files changed, 115 insertions(+), 14 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 7a378b178a..9fe69766ac 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 @@ -23,7 +23,7 @@ import Textarea from '@/app/components/base/textarea' import Divider from '@/app/components/base/divider' import TabSlider from '@/app/components/base/tab-slider-plain' import ReasoningConfigForm from '@/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form' -import { generateFormValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' +import { generateFormValue, getPlainValue, getStructureValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' import { useAppContext } from '@/context/app-context' import { @@ -172,9 +172,10 @@ const ToolSelector: FC = ({ const paramsFormSchemas = useMemo(() => toolParametersToFormSchemas(currentToolParams), [currentToolParams]) const handleSettingsFormChange = (v: Record) => { + const newValue = getStructureValue(v) const toolValue = { ...value, - settings: v, + settings: newValue, } onSelect(toolValue as any) } @@ -402,7 +403,7 @@ const ToolSelector: FC = ({ readOnly={false} nodeId={nodeId} schema={settingsFormSchemas as any} - value={value?.settings || {}} + value={getPlainValue(value?.settings || {})} onChange={handleSettingsFormChange} />
diff --git a/web/app/components/tools/utils/to-form-schema.ts b/web/app/components/tools/utils/to-form-schema.ts index 68c1ef6e2b..ee7f3379ad 100644 --- a/web/app/components/tools/utils/to-form-schema.ts +++ b/web/app/components/tools/utils/to-form-schema.ts @@ -1,4 +1,7 @@ import type { ToolCredential, ToolParameter } from '../types' +import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' + export const toType = (type: string) => { switch (type) { case 'string': @@ -110,6 +113,26 @@ export const generateFormValue = (value: Record, formSchemas: { var return newValues } +export const getPlainValue = (value: Record) => { + const plainValue = { ...value } + Object.keys(plainValue).forEach((key) => { + plainValue[key] = { + ...value[key].value, + } + }) + return plainValue +} + +export const getStructureValue = (value: Record) => { + const newValue = { ...value } as any + Object.keys(newValue).forEach((key) => { + newValue[key] = { + value: value[key], + } + }) + return newValue +} + export const getConfiguredValue = (value: Record, formSchemas: { variable: string; type: string; default?: any }[]) => { const newValues = { ...value } formSchemas.forEach((formSchema) => { @@ -125,3 +148,49 @@ export const getConfiguredValue = (value: Record, formSchemas: { va }) return newValues } + +const getVarKindType = (type: FormTypeEnum) => { + if (type === FormTypeEnum.file || type === FormTypeEnum.files) + return VarKindType.variable + if (type === FormTypeEnum.select || type === FormTypeEnum.boolean || type === FormTypeEnum.textNumber) + return VarKindType.constant + if (type === FormTypeEnum.textInput || type === FormTypeEnum.secretInput) + return VarKindType.mixed + } + +export const generateAgentToolValue = (value: Record, formSchemas: { variable: string; default?: any; type: string }[], isReasoning = false) => { + const newValues = {} as any + if (!isReasoning) { + formSchemas.forEach((formSchema) => { + const itemValue = value[formSchema.variable] + newValues[formSchema.variable] = { + value: { + type: 'constant', + value: itemValue.value, + }, + } + newValues[formSchema.variable].value = correctInitialData(formSchema.type, newValues[formSchema.variable].value, itemValue.value) + }) + } + else { + formSchemas.forEach((formSchema) => { + const itemValue = value[formSchema.variable] + if (itemValue.auto === 1) { + newValues[formSchema.variable] = { + auto: 1, + value: null, + } + } + else { + newValues[formSchema.variable] = { + auto: 0, + value: itemValue.value || { + type: getVarKindType(formSchema.type as FormTypeEnum), + value: null, + }, + } + } + }) + } + return newValues +} diff --git a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx index d528e95285..0c98620ada 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx @@ -152,7 +152,7 @@ const FormInputItem: FC = ({ [variable]: { ...varInput, type: getVarKindType(), - value: newValue, + value: isNumber ? Number.parseFloat(newValue) : newValue, }, }) } diff --git a/web/app/components/workflow/nodes/agent/use-config.ts b/web/app/components/workflow/nodes/agent/use-config.ts index 1dc20fcbcd..0a3be6c76c 100644 --- a/web/app/components/workflow/nodes/agent/use-config.ts +++ b/web/app/components/workflow/nodes/agent/use-config.ts @@ -7,7 +7,7 @@ import { useIsChatMode, useNodesReadOnly, } from '@/app/components/workflow/hooks' -import { useCallback, useMemo } from 'react' +import { useCallback, useEffect, useMemo } from 'react' import { type ToolVarInputs, VarType } from '../tool/types' import { useCheckInstalled, useFetchPluginsInMarketPlaceByIds } from '@/service/use-plugins' import type { Memory, Var } from '../../types' @@ -15,6 +15,8 @@ import { VarType as VarKindType } from '../../types' import useAvailableVarList from '../_base/hooks/use-available-var-list' import produce from 'immer' import { isSupportMCP } from '@/utils/plugin-version-feature' +import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { generateAgentToolValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' export type StrategyStatus = { plugin: { @@ -87,11 +89,12 @@ const useConfig = (id: string, payload: AgentNodeType) => { }) const formData = useMemo(() => { const paramNameList = (currentStrategy?.parameters || []).map(item => item.name) - return Object.fromEntries( + const res = Object.fromEntries( Object.entries(inputs.agent_parameters || {}).filter(([name]) => paramNameList.includes(name)).map(([key, value]) => { return [key, value.value] }), ) + return res }, [inputs.agent_parameters, currentStrategy?.parameters]) const onFormChange = (value: Record) => { const res: ToolVarInputs = {} @@ -107,6 +110,42 @@ const useConfig = (id: string, payload: AgentNodeType) => { }) } + const formattingToolData = (data: any) => { + const settingValues = generateAgentToolValue(data.settings, toolParametersToFormSchemas(data.schemas.filter((param: { form: string }) => param.form !== 'llm') as any)) + const paramValues = generateAgentToolValue(data.parameters, toolParametersToFormSchemas(data.schemas.filter((param: { form: string }) => param.form === 'llm') as any), true) + const res = produce(data, (draft: any) => { + draft.settings = settingValues + draft.parameters = paramValues + }) + return res + } + + const formattingLegacyData = () => { + if (inputs.version) + return inputs + const newData = produce(inputs, (draft) => { + const schemas = currentStrategy?.parameters || [] + Object.keys(draft.agent_parameters || {}).forEach((key) => { + const targetSchema = schemas.find(schema => schema.name === key) + if (targetSchema?.type === FormTypeEnum.toolSelector) + draft.agent_parameters![key].value = formattingToolData(draft.agent_parameters![key].value) + if (targetSchema?.type === FormTypeEnum.multiToolSelector) + draft.agent_parameters![key].value = draft.agent_parameters![key].value.map((tool: any) => formattingToolData(tool)) + }) + draft.version = '2' + }) + return newData + } + + // formatting legacy data + useEffect(() => { + if (!currentStrategy) + return + const newData = formattingLegacyData() + setInputs(newData) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentStrategy]) + // vars const filterMemoryPromptVar = useCallback((varPayload: Var) => { diff --git a/web/app/components/workflow/utils/workflow-init.ts b/web/app/components/workflow/utils/workflow-init.ts index cd4ab1a095..8610bbe9d1 100644 --- a/web/app/components/workflow/utils/workflow-init.ts +++ b/web/app/components/workflow/utils/workflow-init.ts @@ -27,7 +27,6 @@ import type { QuestionClassifierNodeType } from '../nodes/question-classifier/ty import type { IfElseNodeType } from '../nodes/if-else/types' import { branchNameCorrect } from '../nodes/if-else/utils' import type { IterationNodeType } from '../nodes/iteration/types' -import type { AgentNodeType } from '../nodes/agent/types' import type { LoopNodeType } from '../nodes/loop/types' import type { ToolNodeType } from '../nodes/tool/types' import { @@ -305,13 +304,6 @@ export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => { } } - if (node.data.type === BlockEnum.Agent && !(node as Node).data.version) { - // TODO: formatting legacy agent node data - // (node as Node).data.version = '2' - // const toolData = (node as Node).data.agent_parameters?.tool - // const multipleTools = (node as Node).data.agent_parameters?.multiple_tools - } - return node }) } From 53951a3a8de88647296fbf5e200b0e3f64c7ce4b Mon Sep 17 00:00:00 2001 From: JzoNg Date: Thu, 12 Jun 2025 14:47:11 +0800 Subject: [PATCH 099/126] help link --- web/app/components/tools/mcp/create-card.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/app/components/tools/mcp/create-card.tsx b/web/app/components/tools/mcp/create-card.tsx index afb45b0834..0f48dfca92 100644 --- a/web/app/components/tools/mcp/create-card.tsx +++ b/web/app/components/tools/mcp/create-card.tsx @@ -32,9 +32,10 @@ const NewMCPCard = ({ handleCreate }: Props) => { } const linkUrl = useMemo(() => { - // TODO help link if (language.startsWith('zh_')) return 'https://docs.dify.ai/zh-hans/guides/tools/integrate-tool/mcp' + if (language.startsWith('ja_jp')) + return 'https://docs.dify.ai/ja_jp/guides/tools/integrate-tool/mcp' return 'https://docs.dify.ai/en/guides/tools/integrate-tool/mcp' }, [language]) From f6eb37f488e9a73eb92afb79ade0f6e6663c1c50 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Thu, 12 Jun 2025 17:04:45 +0800 Subject: [PATCH 100/126] fix MCP server card in app info --- .../app/(appDetailLayout)/[appId]/overview/cardView.tsx | 4 +++- web/app/components/tools/mcp/mcp-service-card.tsx | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx index 349d31cf9f..52dfe66057 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx @@ -36,6 +36,8 @@ const CardView: FC = ({ appId, isInPanel, className }) => { const setAppDetail = useAppStore(state => state.setAppDetail) const systemFeatures = useContextSelector(AppContext, state => state.systemFeatures) + const showMCPCard = isInPanel && (appDetail?.mode === 'advanced-chat' || appDetail?.mode === 'workflow') + const updateAppDetail = async () => { try { const res = await fetchAppDetail({ url: '/apps', id: appId }) @@ -138,7 +140,7 @@ const CardView: FC = ({ appId, isInPanel, className }) => { isInPanel={isInPanel} onChangeStatus={onChangeApiStatus} /> - {isInPanel && ( + {showMCPCard && ( diff --git a/web/app/components/tools/mcp/mcp-service-card.tsx b/web/app/components/tools/mcp/mcp-service-card.tsx index b8492c3dc3..d829d77556 100644 --- a/web/app/components/tools/mcp/mcp-service-card.tsx +++ b/web/app/components/tools/mcp/mcp-service-card.tsx @@ -70,8 +70,10 @@ function MCPServiceCard({ const onChangeStatus = async (state: boolean) => { setActivated(state) if (state) { - if (!serverPublished) + if (!serverPublished) { setShowMCPServerModal(true) + return + } await updateMCPServer({ appID: appInfo.id, From 0e550e45c77e79e5a7e38d93e8a422fcaef17858 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Mon, 16 Jun 2025 11:10:18 +0800 Subject: [PATCH 101/126] new mixed input --- .../tool-selector/reasoning-config-form.tsx | 20 +- .../_base/components/form-input-item.tsx | 27 +- .../nodes/tool/components/input-var-list.tsx | 247 ------------------ .../mixed-variable-text-input/index.tsx | 37 ++- .../mixed-variable-text-input/placeholder.tsx | 6 +- web/i18n/en-US/workflow.ts | 2 + web/i18n/zh-Hans/workflow.ts | 2 + 7 files changed, 44 insertions(+), 297 deletions(-) delete mode 100644 web/app/components/workflow/nodes/tool/components/input-var-list.tsx 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 c77ea74c88..130e3ec198 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 @@ -7,7 +7,7 @@ import { } from '@remixicon/react' import Tooltip from '@/app/components/base/tooltip' import Switch from '@/app/components/base/switch' -import MixedInput from '@/app/components/workflow/nodes/_base/components/input-support-select-var' +import MixedVariableTextInput from '@/app/components/workflow/nodes/tool/components/mixed-variable-text-input' import Input from '@/app/components/base/input' import FormInputTypeSwitch from '@/app/components/workflow/nodes/_base/components/form-input-type-switch' import FormInputBoolean from '@/app/components/workflow/nodes/_base/components/form-input-boolean' @@ -60,18 +60,6 @@ const ReasoningConfigForm: React.FC = ({ return VarKindType.mixed } - const [inputsIsFocus, setInputsIsFocus] = useState>({}) - const handleInputFocus = useCallback((variable: string) => { - return (value: boolean) => { - setInputsIsFocus((prev) => { - return { - ...prev, - [variable]: value, - } - }) - } - }, []) - const handleAutomatic = (key: string, val: any, type: FormTypeEnum) => { onChange({ ...value, @@ -259,15 +247,11 @@ const ReasoningConfigForm: React.FC = ({ )} {isString && ( - )} {isNumber && isConstant && ( diff --git a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx index 0c98620ada..76f5c7c97a 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx @@ -1,7 +1,5 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useState } from 'react' -import { useTranslation } from 'react-i18next' import type { ToolVarInputs } from '@/app/components/workflow/nodes/tool/types' import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' @@ -11,17 +9,17 @@ import { VarType } from '@/app/components/workflow/types' import type { ValueSelector, Var } from '@/app/components/workflow/types' import FormInputTypeSwitch from './form-input-type-switch' -import MixedInput from '@/app/components/workflow/nodes/_base/components/input-support-select-var' import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' import Input from '@/app/components/base/input' import { SimpleSelect } from '@/app/components/base/select' +import MixedVariableTextInput from '@/app/components/workflow/nodes/tool/components/mixed-variable-text-input' import FormInputBoolean from './form-input-boolean' import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector' import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector' import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' -import cn from '@/utils/classnames' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' +import cn from '@/utils/classnames' type Props = { readOnly: boolean @@ -40,7 +38,6 @@ const FormInputItem: FC = ({ onChange, inPanel, }) => { - const { t } = useTranslation() const language = useLanguage() const { @@ -178,34 +175,18 @@ const FormInputItem: FC = ({ }) } - const [inputsIsFocus, setInputsIsFocus] = useState>({}) - const handleInputFocus = useCallback((variable: string) => { - return (value: boolean) => { - setInputsIsFocus((prev) => { - return { - ...prev, - [variable]: value, - } - }) - } - }, []) - return (
{showTypeSwitch && ( )} {isString && ( - )} {isNumber && isConstant && ( diff --git a/web/app/components/workflow/nodes/tool/components/input-var-list.tsx b/web/app/components/workflow/nodes/tool/components/input-var-list.tsx deleted file mode 100644 index dc25184f5a..0000000000 --- a/web/app/components/workflow/nodes/tool/components/input-var-list.tsx +++ /dev/null @@ -1,247 +0,0 @@ -'use client' -import type { FC } from 'react' -import React, { useCallback, useState } from 'react' -import produce from 'immer' -import { useTranslation } from 'react-i18next' -import type { ToolVarInputs } from '../types' -import { VarType as VarKindType } from '../types' -import cn from '@/utils/classnames' -import type { ValueSelector, Var } from '@/app/components/workflow/types' -import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' -import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' -import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' -import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var' -import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' -import { VarType } from '@/app/components/workflow/types' -import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector' -import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector' -import { noop } from 'lodash-es' - -type Props = { - readOnly: boolean - nodeId: string - schema: CredentialFormSchema[] - value: ToolVarInputs - onChange: (value: ToolVarInputs) => void - onOpen?: (index: number) => void - isSupportConstantValue?: boolean - filterVar?: (payload: Var, valueSelector: ValueSelector) => boolean -} - -const InputVarList: FC = ({ - readOnly, - nodeId, - schema, - value, - onChange, - onOpen = noop, - isSupportConstantValue, - filterVar, -}) => { - const language = useLanguage() - const { t } = useTranslation() - const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, { - onlyLeafNodeVar: false, - filterVar: (varPayload: Var) => { - return [VarType.string, VarType.number, VarType.secret].includes(varPayload.type) - }, - }) - const paramType = (type: string) => { - if (type === FormTypeEnum.textNumber) - return 'Number' - else if (type === FormTypeEnum.file || type === FormTypeEnum.files) - return 'Files' - else if (type === FormTypeEnum.appSelector) - return 'AppSelector' - else if (type === FormTypeEnum.modelSelector) - return 'ModelSelector' - else if (type === FormTypeEnum.toolSelector) - return 'ToolSelector' - else - return 'String' - } - - const handleNotMixedTypeChange = useCallback((variable: string) => { - return (varValue: ValueSelector | string, varKindType: VarKindType) => { - const newValue = produce(value, (draft: ToolVarInputs) => { - const target = draft[variable] - if (target) { - target.type = varKindType - target.value = varValue - } - else { - draft[variable] = { - type: varKindType, - value: varValue, - } - } - }) - onChange(newValue) - } - }, [value, onChange]) - - const handleMixedTypeChange = useCallback((variable: string) => { - return (itemValue: string) => { - const newValue = produce(value, (draft: ToolVarInputs) => { - const target = draft[variable] - if (target) { - target.value = itemValue - } - else { - draft[variable] = { - type: VarKindType.mixed, - value: itemValue, - } - } - }) - onChange(newValue) - } - }, [value, onChange]) - - const handleFileChange = useCallback((variable: string) => { - return (varValue: ValueSelector | string) => { - const newValue = produce(value, (draft: ToolVarInputs) => { - draft[variable] = { - type: VarKindType.variable, - value: varValue, - } - }) - onChange(newValue) - } - }, [value, onChange]) - - const handleAppChange = useCallback((variable: string) => { - return (app: { - app_id: string - inputs: Record - files?: any[] - }) => { - const newValue = produce(value, (draft: ToolVarInputs) => { - draft[variable] = app as any - }) - onChange(newValue) - } - }, [onChange, value]) - const handleModelChange = useCallback((variable: string) => { - return (model: any) => { - const newValue = produce(value, (draft: ToolVarInputs) => { - draft[variable] = { - ...draft[variable], - ...model, - } as any - }) - onChange(newValue) - } - }, [onChange, value]) - - const [inputsIsFocus, setInputsIsFocus] = useState>({}) - const handleInputFocus = useCallback((variable: string) => { - return (value: boolean) => { - setInputsIsFocus((prev) => { - return { - ...prev, - [variable]: value, - } - }) - } - }, []) - const handleOpen = useCallback((index: number) => { - return () => onOpen(index) - }, [onOpen]) - return ( -
- { - schema.map((schema, index) => { - const { - variable, - label, - type, - required, - tooltip, - scope, - } = schema - const varInput = value[variable] - const isNumber = type === FormTypeEnum.textNumber - const isSelect = type === FormTypeEnum.select - const isFile = type === FormTypeEnum.file || type === FormTypeEnum.files - const isAppSelector = type === FormTypeEnum.appSelector - const isModelSelector = type === FormTypeEnum.modelSelector - // const isToolSelector = type === FormTypeEnum.toolSelector - const isString = !isNumber && !isSelect && !isFile && !isAppSelector && !isModelSelector - - return ( -
-
- {label[language] || label.en_US} - {paramType(type)} - {required && Required} -
- {isString && ( - - )} - {(isNumber || isSelect) && ( - - )} - {isFile && ( - varPayload.type === VarType.file || varPayload.type === VarType.arrayFile} - /> - )} - {isAppSelector && ( - - )} - {isModelSelector && ( - - )} - {tooltip &&
{tooltip[language] || tooltip.en_US}
} -
- ) - }) - } -
- ) -} -export default React.memo(InputVarList) diff --git a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx index 4bb562ba3a..6680c8ebb6 100644 --- a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx +++ b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx @@ -1,34 +1,57 @@ import { memo, } from 'react' +import { useTranslation } from 'react-i18next' import PromptEditor from '@/app/components/base/prompt-editor' -import cn from '@/utils/classnames' import Placeholder from './placeholder' +import type { + Node, + NodeOutPutVar, +} from '@/app/components/workflow/types' +import { BlockEnum } from '@/app/components/workflow/types' +import cn from '@/utils/classnames' type MixedVariableTextInputProps = { - editable?: boolean + readOnly?: boolean + nodesOutputVars?: NodeOutPutVar[] + availableNodes?: Node[] value?: string onChange?: (text: string) => void } const MixedVariableTextInput = ({ - editable = true, + readOnly = false, + nodesOutputVars, + availableNodes = [], value = '', onChange, }: MixedVariableTextInputProps) => { + const { t } = useTranslation() return ( { + acc[node.id] = { + title: node.data.title, + type: node.data.type, + } + if (node.data.type === BlockEnum.Start) { + acc.sys = { + title: t('workflow.blocks.start'), + type: BlockEnum.Start, + } + } + return acc + }, {} as any), }} placeholder={} onChange={onChange} diff --git a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/placeholder.tsx b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/placeholder.tsx index e84ffbeb28..3337d6ae66 100644 --- a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/placeholder.tsx +++ b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/placeholder.tsx @@ -1,4 +1,5 @@ import { useCallback } from 'react' +import { useTranslation } from 'react-i18next' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { FOCUS_COMMAND } from 'lexical' import { $insertNodes } from 'lexical' @@ -6,6 +7,7 @@ import { CustomTextNode } from '@/app/components/base/prompt-editor/plugins/cust import Badge from '@/app/components/base/badge' const Placeholder = () => { + const { t } = useTranslation() const [editor] = useLexicalComposerContext() const handleInsert = useCallback((text: string) => { @@ -25,7 +27,7 @@ const Placeholder = () => { }} >
- Type or press + {t('workflow.nodes.tool.insertPlaceholder1')}
/
{ handleInsert('/') })} > - insert variable + {t('workflow.nodes.tool.insertPlaceholder2')}
Date: Mon, 16 Jun 2025 16:56:27 +0800 Subject: [PATCH 102/126] fix: null of tool_configurations --- web/app/components/workflow/utils/workflow-init.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/components/workflow/utils/workflow-init.ts b/web/app/components/workflow/utils/workflow-init.ts index 8610bbe9d1..dc22d61ca5 100644 --- a/web/app/components/workflow/utils/workflow-init.ts +++ b/web/app/components/workflow/utils/workflow-init.ts @@ -293,7 +293,7 @@ export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => { if (toolConfigurations && Object.keys(toolConfigurations).length > 0) { const newValues = { ...toolConfigurations } Object.keys(toolConfigurations).forEach((key) => { - if (typeof toolConfigurations[key] !== 'object') { + if (typeof toolConfigurations[key] !== 'object' || toolConfigurations[key] === null) { newValues[key] = { type: 'constant', value: toolConfigurations[key], From 8c95cf359e24f506929bfec18478712f3d4da36a Mon Sep 17 00:00:00 2001 From: jZonG Date: Thu, 19 Jun 2025 11:51:51 +0800 Subject: [PATCH 103/126] add warning of MCP editing --- web/app/components/tools/mcp/modal.tsx | 8 +++++++- web/i18n/en-US/tools.ts | 2 ++ web/i18n/zh-Hans/tools.ts | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/web/app/components/tools/mcp/modal.tsx b/web/app/components/tools/mcp/modal.tsx index 4e8e38959c..ff28c23550 100644 --- a/web/app/components/tools/mcp/modal.tsx +++ b/web/app/components/tools/mcp/modal.tsx @@ -51,6 +51,7 @@ const MCPModal = ({ }: DuplicateAppModalProps) => { const { t } = useTranslation() + const originalServerUrl = data?.server_url const [name, setName] = React.useState(data?.name || '') const [appIcon, setAppIcon] = useState(getIcon(data)) const [url, setUrl] = React.useState(data?.server_url || '') @@ -77,7 +78,7 @@ const MCPModal = ({
-
{t('tools.mcp.modal.title')}
+
{data ? t('tools.mcp.modal.editTitle') : t('tools.mcp.modal.title')}
@@ -110,6 +111,11 @@ const MCPModal = ({ onChange={e => setUrl(e.target.value)} placeholder={t('tools.mcp.modal.serverUrlPlaceholder')} /> + {originalServerUrl && originalServerUrl !== url && ( +
+ {t('tools.mcp.modal.warning')} +
+ )}
diff --git a/web/i18n/en-US/tools.ts b/web/i18n/en-US/tools.ts index bc8c0b2d1c..86892cc880 100644 --- a/web/i18n/en-US/tools.ts +++ b/web/i18n/en-US/tools.ts @@ -164,10 +164,12 @@ const translation = { noTools: 'No tools available', modal: { title: 'Add MCP Server (HTTP)', + editTitle: 'Edit MCP Server (HTTP)', name: 'Name & Icon', namePlaceholder: 'Name your MCP server', serverUrl: 'Server URL', serverUrlPlaceholder: 'URL to server endpiont', + warning: 'Updating the server address may affect applications currently using this MCP', cancel: 'Cancel', save: 'Save', confirm: 'Add & Authorize', diff --git a/web/i18n/zh-Hans/tools.ts b/web/i18n/zh-Hans/tools.ts index 5aecc01f1a..56ef356c13 100644 --- a/web/i18n/zh-Hans/tools.ts +++ b/web/i18n/zh-Hans/tools.ts @@ -164,10 +164,12 @@ const translation = { noTools: '没有可用的工具', modal: { title: '添加 MCP 服务 (HTTP)', + editTitle: '修改 MCP 服务 (HTTP)', name: '名称和图标', namePlaceholder: '命名你的 MCP 服务', serverUrl: '服务端点 URL', serverUrlPlaceholder: '服务端点的 URL', + warning: '修改服务端点 URL 可能会影响使用当前 MCP 的应用。', cancel: '取消', save: '保存', confirm: '添加并授权', From 81ea5f1b778eae0219cf72132d1b07aed3bb7e0c Mon Sep 17 00:00:00 2001 From: jZonG Date: Thu, 19 Jun 2025 16:09:05 +0800 Subject: [PATCH 104/126] use popup window for oauth --- .../components/tools/mcp/detail/content.tsx | 33 ++++++++++-- .../tools/mcp/detail/provider-detail.tsx | 6 +++ web/app/components/tools/mcp/index.tsx | 53 ++----------------- web/app/components/tools/provider-list.tsx | 2 + web/hooks/use-oauth.ts | 47 ++++++++++++++++ 5 files changed, 89 insertions(+), 52 deletions(-) create mode 100644 web/hooks/use-oauth.ts diff --git a/web/app/components/tools/mcp/detail/content.tsx b/web/app/components/tools/mcp/detail/content.tsx index c3df795836..60f2c13b5f 100644 --- a/web/app/components/tools/mcp/detail/content.tsx +++ b/web/app/components/tools/mcp/detail/content.tsx @@ -1,6 +1,5 @@ 'use client' -import React, { useCallback } from 'react' -import { useRouter } from 'next/navigation' +import React, { useCallback, useEffect } from 'react' import type { FC } from 'react' import { useBoolean } from 'ahooks' import { useTranslation } from 'react-i18next' @@ -26,29 +25,35 @@ import { useInvalidateMCPTools, useMCPTools, useUpdateMCP, + useUpdateMCPAuthorizationToken, useUpdateMCPTools, } from '@/service/use-tools' +import { openOAuthPopup } from '@/hooks/use-oauth' import cn from '@/utils/classnames' type Props = { detail: ToolWithProvider onUpdate: (isDelete?: boolean) => void onHide: () => void + isCreation: boolean + onFirstCreate: () => void } const MCPDetailContent: FC = ({ detail, onUpdate, onHide, + isCreation, + onFirstCreate, }) => { const { t } = useTranslation() - const router = useRouter() const { isCurrentWorkspaceManager } = useAppContext() 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() + const { mutateAsync: updateMCPAuthorizationToken } = useUpdateMCPAuthorizationToken() const toolList = data?.tools || [] const handleUpdateTools = useCallback(async () => { @@ -81,7 +86,22 @@ const MCPDetailContent: FC = ({ setFalse: hideDeleting, }] = useBoolean(false) + const handleOAuthCallback = async (state: string, code: string) => { + if (!isCurrentWorkspaceManager) + return + if (detail.id !== state) + return + await updateMCPAuthorizationToken({ + provider_id: state, + authorization_code: code, + }) + handleUpdateTools() + } + const handleAuthorize = useCallback(async () => { + onFirstCreate() + if (!isCurrentWorkspaceManager) + return if (!detail) return const res = await authorizeMcp({ @@ -91,7 +111,7 @@ const MCPDetailContent: FC = ({ handleUpdateTools() else if (res.authorization_url) - router.push(res.authorization_url) + openOAuthPopup(res.authorization_url, handleOAuthCallback) }, [detail, updateMCP, hideUpdateModal, onUpdate]) const handleUpdate = useCallback(async (data: any) => { @@ -119,6 +139,11 @@ const MCPDetailContent: FC = ({ } }, [detail, showDeleting, hideDeleting, hideDeleteConfirm, onUpdate]) + useEffect(() => { + if (isCreation) + handleAuthorize() + }, []) + if (!detail) return null diff --git a/web/app/components/tools/mcp/detail/provider-detail.tsx b/web/app/components/tools/mcp/detail/provider-detail.tsx index effb2363c9..1ac4223fa4 100644 --- a/web/app/components/tools/mcp/detail/provider-detail.tsx +++ b/web/app/components/tools/mcp/detail/provider-detail.tsx @@ -10,12 +10,16 @@ type Props = { detail?: ToolWithProvider onUpdate: () => void onHide: () => void + isCreation: boolean + onFirstCreate: () => void } const MCPDetailPanel: FC = ({ detail, onUpdate, onHide, + isCreation, + onFirstCreate, }) => { const handleUpdate = (isDelete = false) => { if (isDelete) @@ -41,6 +45,8 @@ const MCPDetailPanel: FC = ({ detail={detail} onHide={onHide} onUpdate={handleUpdate} + isCreation={isCreation} + onFirstCreate={onFirstCreate} /> )} diff --git a/web/app/components/tools/mcp/index.tsx b/web/app/components/tools/mcp/index.tsx index fa069ac7f0..b6ed308d5b 100644 --- a/web/app/components/tools/mcp/index.tsx +++ b/web/app/components/tools/mcp/index.tsx @@ -1,15 +1,10 @@ 'use client' -import { useEffect, useMemo, useState } from 'react' -import { usePathname, useRouter, useSearchParams } from 'next/navigation' +import { useMemo, useState } from 'react' import NewMCPCard from './create-card' import MCPCard from './provider-card' import MCPDetailPanel from './detail/provider-detail' import { useAllMCPTools, - useAuthorizeMCP, - useInvalidateMCPTools, - useUpdateMCPAuthorizationToken, - useUpdateMCPTools, } from '@/service/use-tools' import type { ToolWithProvider } from '@/app/components/workflow/types' import cn from '@/utils/classnames' @@ -39,17 +34,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') || '' - const { data: list = [], refetch } = useAllMCPTools() - const { mutateAsync: authorizeMcp } = useAuthorizeMCP() - const { mutateAsync: updateTools } = useUpdateMCPTools() - const invalidateMCPTools = useInvalidateMCPTools() - const { mutateAsync: updateMCPAuthorizationToken } = useUpdateMCPAuthorizationToken() + const [isCreation, setIsCreation] = useState(false) const filteredList = useMemo(() => { return list.filter((collection) => { @@ -68,40 +54,9 @@ const MCPList = ({ const handleCreate = async (provider: ToolWithProvider) => { await refetch() // update list setCurrentProviderID(provider.id) - const res = await authorizeMcp({ - provider_id: provider.id, - }) - if (res.result === 'success') { - await refetch() // update authorization in list - await updateTools(provider.id) - invalidateMCPTools(provider.id) - await refetch() // update tool list in provider list - } - else if (res.authorization_url) { - router.push(res.authorization_url) - } + setIsCreation(true) } - 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, - authorization_code: code, - }) - await refetch() - setCurrentProviderID(providerID) - await updateTools(providerID) - invalidateMCPTools(providerID) - await refetch() - } - - useEffect(() => { - if (authCode && providerID && list.length > 0) - handleUpdateAuthorization(providerID, authCode) - }, [authCode, providerID, list]) - return ( <>
setCurrentProviderID(undefined)} onUpdate={refetch} + isCreation={isCreation} + onFirstCreate={() => setIsCreation(false)} /> )} diff --git a/web/app/components/tools/provider-list.tsx b/web/app/components/tools/provider-list.tsx index 8aee0beb0e..35a3ab3dc7 100644 --- a/web/app/components/tools/provider-list.tsx +++ b/web/app/components/tools/provider-list.tsx @@ -20,11 +20,13 @@ 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' +import { useOAuthCallback } from '@/hooks/use-oauth' const ProviderList = () => { const { t } = useTranslation() const containerRef = useRef(null) const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures) + useOAuthCallback() const searchParams = useSearchParams() const authCode = searchParams.get('code') || '' diff --git a/web/hooks/use-oauth.ts b/web/hooks/use-oauth.ts new file mode 100644 index 0000000000..0fe4d8e926 --- /dev/null +++ b/web/hooks/use-oauth.ts @@ -0,0 +1,47 @@ +'use client' +import { useEffect } from 'react' +import { useSearchParams } from 'next/navigation' + +export const useOAuthCallback = () => { + const searchParams = useSearchParams() + + useEffect(() => { + const code = searchParams.get('code') + const state = searchParams.get('state') + + if (code && state && window.opener) { + window.opener.postMessage({ + type: 'oauth_callback', + payload: { + code, + state, + }, + }, '*') + window.close() + } + }, [searchParams]) +} + +export const openOAuthPopup = (url: string, callback: (state: string, code: string) => void) => { + const width = 600 + const height = 600 + const left = window.screenX + (window.outerWidth - width) / 2 + const top = window.screenY + (window.outerHeight - height) / 2 + + const popup = window.open( + url, + 'OAuth', + `width=${width},height=${height},left=${left},top=${top},scrollbars=yes`, + ) + + const handleMessage = (event: MessageEvent) => { + if (event.data?.type === 'oauth_callback') { + window.removeEventListener('message', handleMessage) + const { code, state } = event.data.payload + callback(state, code) + } + } + + window.addEventListener('message', handleMessage) + return popup +} From 2b0193dd791ec9e1497e46b71704ed62283a9077 Mon Sep 17 00:00:00 2001 From: jZonG Date: Fri, 20 Jun 2025 14:50:29 +0800 Subject: [PATCH 105/126] fix: object value change in tool node --- .../plugin-detail-panel/tool-selector/reasoning-config-form.tsx | 2 +- .../workflow/nodes/_base/components/form-input-item.tsx | 2 +- 2 files changed, 2 insertions(+), 2 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 130e3ec198..98ad490348 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 @@ -54,7 +54,7 @@ const ReasoningConfigForm: React.FC = ({ const getVarKindType = (type: FormTypeEnum) => { if (type === FormTypeEnum.file || type === FormTypeEnum.files) return VarKindType.variable - if (type === FormTypeEnum.select || type === FormTypeEnum.boolean || type === FormTypeEnum.textNumber) + if (type === FormTypeEnum.select || type === FormTypeEnum.boolean || type === FormTypeEnum.textNumber || type === FormTypeEnum.array || type === FormTypeEnum.object) return VarKindType.constant if (type === FormTypeEnum.textInput || type === FormTypeEnum.secretInput) return VarKindType.mixed diff --git a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx index 76f5c7c97a..15a538343a 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx @@ -114,7 +114,7 @@ const FormInputItem: FC = ({ const getVarKindType = () => { if (isFile) return VarKindType.variable - if (isSelect || isBoolean || isNumber) + if (isSelect || isBoolean || isNumber || isArray || isObject) return VarKindType.constant if (isString) return VarKindType.mixed From d2ac38445874788450fb81b404fe3882331dcdf0 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Mon, 23 Jun 2025 11:08:56 +0800 Subject: [PATCH 106/126] support re-authorization --- .../components/tools/mcp/detail/content.tsx | 18 +++++++----------- web/hooks/use-oauth.ts | 14 ++++++-------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/web/app/components/tools/mcp/detail/content.tsx b/web/app/components/tools/mcp/detail/content.tsx index 60f2c13b5f..366b4d8b85 100644 --- a/web/app/components/tools/mcp/detail/content.tsx +++ b/web/app/components/tools/mcp/detail/content.tsx @@ -25,7 +25,6 @@ import { useInvalidateMCPTools, useMCPTools, useUpdateMCP, - useUpdateMCPAuthorizationToken, useUpdateMCPTools, } from '@/service/use-tools' import { openOAuthPopup } from '@/hooks/use-oauth' @@ -53,7 +52,6 @@ const MCPDetailContent: FC = ({ const invalidateMCPTools = useInvalidateMCPTools() const { mutateAsync: updateTools, isPending: isUpdating } = useUpdateMCPTools() const { mutateAsync: authorizeMcp, isPending: isAuthorizing } = useAuthorizeMCP() - const { mutateAsync: updateMCPAuthorizationToken } = useUpdateMCPAuthorizationToken() const toolList = data?.tools || [] const handleUpdateTools = useCallback(async () => { @@ -62,7 +60,7 @@ const MCPDetailContent: FC = ({ await updateTools(detail.id) invalidateMCPTools(detail.id) onUpdate() - }, [detail, updateTools]) + }, [detail, invalidateMCPTools, onUpdate, updateTools]) const { mutate: updateMCP } = useUpdateMCP({ onSuccess: onUpdate, @@ -86,17 +84,13 @@ const MCPDetailContent: FC = ({ setFalse: hideDeleting, }] = useBoolean(false) - const handleOAuthCallback = async (state: string, code: string) => { + const handleOAuthCallback = useCallback((state: string) => { if (!isCurrentWorkspaceManager) return if (detail.id !== state) return - await updateMCPAuthorizationToken({ - provider_id: state, - authorization_code: code, - }) handleUpdateTools() - } + }, [detail.id, handleUpdateTools, isCurrentWorkspaceManager]) const handleAuthorize = useCallback(async () => { onFirstCreate() @@ -112,7 +106,7 @@ const MCPDetailContent: FC = ({ else if (res.authorization_url) openOAuthPopup(res.authorization_url, handleOAuthCallback) - }, [detail, updateMCP, hideUpdateModal, onUpdate]) + }, [onFirstCreate, isCurrentWorkspaceManager, detail, authorizeMcp, handleUpdateTools, handleOAuthCallback]) const handleUpdate = useCallback(async (data: any) => { if (!detail) @@ -137,11 +131,12 @@ const MCPDetailContent: FC = ({ hideDeleteConfirm() onUpdate(true) } - }, [detail, showDeleting, hideDeleting, hideDeleteConfirm, onUpdate]) + }, [detail, showDeleting, deleteMCP, hideDeleting, hideDeleteConfirm, onUpdate]) useEffect(() => { if (isCreation) handleAuthorize() + // eslint-disable-next-line react-hooks/exhaustive-deps }, []) if (!detail) @@ -175,6 +170,7 @@ const MCPDetailContent: FC = ({
- @@ -279,6 +285,14 @@ const MCPDetailContent: FC = ({ isDisabled={deleting} /> )} + {isShowUpdateConfirm && ( + + )} ) } From 9745570490b483f97e6d6d5b3d9101527f387ff2 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Mon, 23 Jun 2025 14:05:29 +0800 Subject: [PATCH 108/126] modify oauth --- web/app/components/tools/mcp/detail/content.tsx | 4 ++-- web/app/components/tools/provider-list.tsx | 2 -- web/app/oauth-callback/page.tsx | 10 ++++++++++ web/hooks/use-oauth.ts | 17 ++++------------- 4 files changed, 16 insertions(+), 17 deletions(-) create mode 100644 web/app/oauth-callback/page.tsx diff --git a/web/app/components/tools/mcp/detail/content.tsx b/web/app/components/tools/mcp/detail/content.tsx index 7d6525c104..b9401d4246 100644 --- a/web/app/components/tools/mcp/detail/content.tsx +++ b/web/app/components/tools/mcp/detail/content.tsx @@ -90,10 +90,10 @@ const MCPDetailContent: FC = ({ setFalse: hideDeleting, }] = useBoolean(false) - const handleOAuthCallback = useCallback((state: string) => { + const handleOAuthCallback = useCallback(() => { if (!isCurrentWorkspaceManager) return - if (detail.id !== state) + if (!detail.id) return handleUpdateTools() }, [detail.id, handleUpdateTools, isCurrentWorkspaceManager]) diff --git a/web/app/components/tools/provider-list.tsx b/web/app/components/tools/provider-list.tsx index 35a3ab3dc7..8aee0beb0e 100644 --- a/web/app/components/tools/provider-list.tsx +++ b/web/app/components/tools/provider-list.tsx @@ -20,13 +20,11 @@ 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' -import { useOAuthCallback } from '@/hooks/use-oauth' const ProviderList = () => { const { t } = useTranslation() const containerRef = useRef(null) const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures) - useOAuthCallback() const searchParams = useSearchParams() const authCode = searchParams.get('code') || '' diff --git a/web/app/oauth-callback/page.tsx b/web/app/oauth-callback/page.tsx new file mode 100644 index 0000000000..38355f435e --- /dev/null +++ b/web/app/oauth-callback/page.tsx @@ -0,0 +1,10 @@ +'use client' +import { useOAuthCallback } from '@/hooks/use-oauth' + +const OAuthCallback = () => { + useOAuthCallback() + + return
+} + +export default OAuthCallback diff --git a/web/hooks/use-oauth.ts b/web/hooks/use-oauth.ts index c4402ff6eb..ae9c1cda66 100644 --- a/web/hooks/use-oauth.ts +++ b/web/hooks/use-oauth.ts @@ -1,26 +1,18 @@ 'use client' import { useEffect } from 'react' -import { useSearchParams } from 'next/navigation' export const useOAuthCallback = () => { - const searchParams = useSearchParams() - useEffect(() => { - const MCPProviderID = searchParams.get('mcp_provider_id') - - if (MCPProviderID && window.opener) { + if (window.opener) { window.opener.postMessage({ type: 'oauth_callback', - payload: { - state: MCPProviderID, - }, }, '*') window.close() } - }, [searchParams]) + }, []) } -export const openOAuthPopup = (url: string, callback: (state: string) => void) => { +export const openOAuthPopup = (url: string, callback: () => void) => { const width = 600 const height = 600 const left = window.screenX + (window.outerWidth - width) / 2 @@ -35,8 +27,7 @@ export const openOAuthPopup = (url: string, callback: (state: string) => void) = const handleMessage = (event: MessageEvent) => { if (event.data?.type === 'oauth_callback') { window.removeEventListener('message', handleMessage) - const { state } = event.data.payload - callback(state) + callback() } } From 21911b86b053147b7ad4ddae59de51059796bda5 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Mon, 23 Jun 2025 14:39:40 +0800 Subject: [PATCH 109/126] fix: style of MCP 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 9ff579ef96..4c2d764f25 100644 --- a/web/app/components/tools/mcp/provider-card.tsx +++ b/web/app/components/tools/mcp/provider-card.tsx @@ -94,10 +94,10 @@ const MCPCard = ({
{data.tools.length > 0 && ( -
{t('tools.mcp.toolsCount', { count: data.tools.length })}
+
{t('tools.mcp.toolsCount', { count: data.tools.length })}
)} {!data.tools.length && ( -
{t('tools.mcp.noTools')}
+
{t('tools.mcp.noTools')}
)}
/
From 6332627345f6db3178433d226c4b4153c9d1631f Mon Sep 17 00:00:00 2001 From: Joel Date: Mon, 23 Jun 2025 14:44:35 +0800 Subject: [PATCH 110/126] fix: tool picker ui --- .../workflow/block-selector/tool/tool.tsx | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/web/app/components/workflow/block-selector/tool/tool.tsx b/web/app/components/workflow/block-selector/tool/tool.tsx index 05a34096ee..dd77536c16 100644 --- a/web/app/components/workflow/block-selector/tool/tool.tsx +++ b/web/app/components/workflow/block-selector/tool/tool.tsx @@ -15,6 +15,7 @@ import BlockIcon from '../../block-icon' import { useTranslation } from 'react-i18next' import { useHover } from 'ahooks' import McpToolNotSupportTooltip from '../../nodes/_base/components/mcp-tool-not-support-tooltip' +import { Mcp } from '@/app/components/base/icons/src/vender/other' type Props = { className?: string @@ -50,7 +51,8 @@ const Tool: FC = ({ const [isFold, setFold] = React.useState(true) const ref = useRef(null) const isHovering = useHover(ref) - const isShowCanNotChooseMCPTip = !canChooseMCPTool && payload.type === CollectionType.mcp + const isMCPTool = payload.type === CollectionType.mcp + const isShowCanNotChooseMCPTip = !canChooseMCPTool && isMCPTool 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) @@ -143,12 +145,12 @@ const Tool: FC = ({ return (
{ if (hasAction) { setFold(!isFold) @@ -183,11 +185,12 @@ const Tool: FC = ({ type={BlockEnum.Tool} toolIcon={payload.icon} /> -
- {notShowProvider ? actions[0]?.label[language] : payload.label[language]} - {isFlatView && ( - {groupName} +
+ {notShowProvider ? actions[0]?.label[language] : payload.label[language]} + {isFlatView && groupName && ( + {groupName} )} + {isMCPTool && }
@@ -195,7 +198,7 @@ const Tool: FC = ({ {!isShowCanNotChooseMCPTip && !canNotSelectMultiple && (notShowProvider ? notShowProviderSelectInfo : selectedInfo)} {isShowCanNotChooseMCPTip && } {hasAction && ( - + )}
From 96cd676ffb7ffc784ba25fc119b2f5a0ea170671 Mon Sep 17 00:00:00 2001 From: Joel Date: Mon, 23 Jun 2025 15:47:04 +0800 Subject: [PATCH 111/126] chore: data undefined page crash --- web/app/components/workflow/hooks/use-workflow.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/app/components/workflow/hooks/use-workflow.ts b/web/app/components/workflow/hooks/use-workflow.ts index df60803d00..13e6360244 100644 --- a/web/app/components/workflow/hooks/use-workflow.ts +++ b/web/app/components/workflow/hooks/use-workflow.ts @@ -507,6 +507,8 @@ export const useToolIcon = (data: Node['data']) => { const mcpTools = useStore(s => s.mcpTools) const toolIcon = useMemo(() => { + if(!data) + return '' if (data.type === BlockEnum.Tool) { let targetTools = buildInTools if (data.provider_type === CollectionType.builtIn) @@ -519,7 +521,7 @@ export const useToolIcon = (data: Node['data']) => { targetTools = workflowTools return targetTools.find(toolWithProvider => canFindTool(toolWithProvider.id, data.provider_id))?.icon } - }, [data.type, data.provider_type, data.provider_id, buildInTools, customTools, mcpTools, workflowTools]) + }, [data, buildInTools, customTools, mcpTools, workflowTools]) return toolIcon } From 8e0961751b0e1eb0a626af7ae30c5cf3ecd46b8b Mon Sep 17 00:00:00 2001 From: JzoNg Date: Mon, 23 Jun 2025 15:59:14 +0800 Subject: [PATCH 112/126] server url submit check --- web/app/components/tools/mcp/modal.tsx | 55 ++++++++++++++++++-------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/web/app/components/tools/mcp/modal.tsx b/web/app/components/tools/mcp/modal.tsx index ff28c23550..084c46cf65 100644 --- a/web/app/components/tools/mcp/modal.tsx +++ b/web/app/components/tools/mcp/modal.tsx @@ -11,6 +11,7 @@ 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 Toast from '@/app/components/base/toast' import cn from '@/utils/classnames' export type DuplicateAppModalProps = { @@ -57,10 +58,32 @@ const MCPModal = ({ const [url, setUrl] = React.useState(data?.server_url || '') const [showAppIconPicker, setShowAppIconPicker] = useState(false) + const isValidUrl = (string: string) => { + try { + const urlPattern = new RegExp( + '^(https?:\\/\\/)?' // protocol + + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' // domain + + '((\\d{1,3}\\.){3}\\d{1,3}))' // IP address + + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' // port and path + + '(\\?[;&a-z\\d%_.~+=-]*)?' // query string + + '(\\#[-a-z\\d_]*)?$', // anchor + 'i', + ) + return urlPattern.test(string) + } + catch (e) { + return false + } + } + const submit = async () => { + if (!isValidUrl(url)) { + Toast.notify({ type: 'error', message: 'invalid server url' }) + return + } await onConfirm({ name, - server_url: url, + server_url: originalServerUrl === url ? '[__HIDDEN__]' : url, icon_type: appIcon.type, icon: appIcon.type === 'emoji' ? appIcon.icon : appIcon.fileId, icon_background: appIcon.type === 'emoji' ? appIcon.background : undefined, @@ -80,6 +103,21 @@ const MCPModal = ({
{data ? t('tools.mcp.modal.editTitle') : t('tools.mcp.modal.title')}
+
+
+ {t('tools.mcp.modal.serverUrl')} +
+ setUrl(e.target.value)} + placeholder={t('tools.mcp.modal.serverUrlPlaceholder')} + /> + {originalServerUrl && originalServerUrl !== url && ( +
+ {t('tools.mcp.modal.warning')} +
+ )} +
@@ -102,21 +140,6 @@ const MCPModal = ({ />
-
-
- {t('tools.mcp.modal.serverUrl')} -
- setUrl(e.target.value)} - placeholder={t('tools.mcp.modal.serverUrlPlaceholder')} - /> - {originalServerUrl && originalServerUrl !== url && ( -
- {t('tools.mcp.modal.warning')} -
- )} -
From 51df69fbffde2be1c3431666097672c9a3ffc3bf Mon Sep 17 00:00:00 2001 From: JzoNg Date: Mon, 23 Jun 2025 16:42:12 +0800 Subject: [PATCH 113/126] validation of server url --- web/app/components/tools/mcp/modal.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/web/app/components/tools/mcp/modal.tsx b/web/app/components/tools/mcp/modal.tsx index 084c46cf65..5d250071fd 100644 --- a/web/app/components/tools/mcp/modal.tsx +++ b/web/app/components/tools/mcp/modal.tsx @@ -60,15 +60,7 @@ const MCPModal = ({ const isValidUrl = (string: string) => { try { - const urlPattern = new RegExp( - '^(https?:\\/\\/)?' // protocol - + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' // domain - + '((\\d{1,3}\\.){3}\\d{1,3}))' // IP address - + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' // port and path - + '(\\?[;&a-z\\d%_.~+=-]*)?' // query string - + '(\\#[-a-z\\d_]*)?$', // anchor - 'i', - ) + const urlPattern = /^(https?:\/\/)((([a-z\d]([a-z\d-]*[a-z\d])*)\.)+[a-z]{2,}|((\d{1,3}\.){3}\d{1,3}))(\:\d+)?(\/[-a-z\d%_.~+]*)*(\?[;&a-z\d%_.~+=-]*)?/i return urlPattern.test(string) } catch (e) { From 3960521e76884e5e30cfd65ca6c5800ebb3b475e Mon Sep 17 00:00:00 2001 From: JzoNg Date: Mon, 23 Jun 2025 16:53:18 +0800 Subject: [PATCH 114/126] trim server url --- web/app/components/tools/mcp/modal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/components/tools/mcp/modal.tsx b/web/app/components/tools/mcp/modal.tsx index 5d250071fd..2d1ec544ec 100644 --- a/web/app/components/tools/mcp/modal.tsx +++ b/web/app/components/tools/mcp/modal.tsx @@ -75,7 +75,7 @@ const MCPModal = ({ } await onConfirm({ name, - server_url: originalServerUrl === url ? '[__HIDDEN__]' : url, + server_url: originalServerUrl === url ? '[__HIDDEN__]' : url.trim(), icon_type: appIcon.type, icon: appIcon.type === 'emoji' ? appIcon.icon : appIcon.fileId, icon_background: appIcon.type === 'emoji' ? appIcon.background : undefined, From 10e5cb4382ad4eaf095414a7936e12ef4f94a664 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Tue, 24 Jun 2025 14:21:09 +0800 Subject: [PATCH 115/126] add server identifier --- .../components/tools/mcp/detail/content.tsx | 12 +++++++- web/app/components/tools/mcp/modal.tsx | 21 ++++++++++++-- .../components/tools/mcp/provider-card.tsx | 28 +++++++++---------- web/app/components/tools/types.ts | 1 + web/i18n/en-US/tools.ts | 4 +++ web/i18n/zh-Hans/tools.ts | 4 +++ 6 files changed, 52 insertions(+), 18 deletions(-) diff --git a/web/app/components/tools/mcp/detail/content.tsx b/web/app/components/tools/mcp/detail/content.tsx index b9401d4246..5984cda2b1 100644 --- a/web/app/components/tools/mcp/detail/content.tsx +++ b/web/app/components/tools/mcp/detail/content.tsx @@ -2,6 +2,7 @@ import React, { useCallback, useEffect } from 'react' import type { FC } from 'react' import { useBoolean } from 'ahooks' +import copy from 'copy-to-clipboard' import { useTranslation } from 'react-i18next' import { useAppContext } from '@/context/app-context' import { @@ -15,6 +16,7 @@ import ActionButton from '@/app/components/base/action-button' import Button from '@/app/components/base/button' import Confirm from '@/app/components/base/confirm' import Indicator from '@/app/components/header/indicator' +import Tooltip from '@/app/components/base/tooltip' import MCPModal from '../modal' import OperationDropdown from './operation-dropdown' import ListLoading from './list-loading' @@ -159,7 +161,15 @@ const MCPDetailContent: FC = ({
{detail.name}
-
{detail.server_url}
+
+ +
copy(detail.server_identifier || '')}>{detail.server_identifier}
+
+
·
+ +
{detail.server_url}
+
+
void onHide: () => void } @@ -53,10 +54,11 @@ const MCPModal = ({ const { t } = useTranslation() const originalServerUrl = data?.server_url + const [url, setUrl] = React.useState(data?.server_url || '') 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 [serverIdentifier, setServerIdentifier] = React.useState(data?.server_identifier || '') const isValidUrl = (string: string) => { try { @@ -73,12 +75,14 @@ const MCPModal = ({ Toast.notify({ type: 'error', message: 'invalid server url' }) return } + // TODO server identifier validation await onConfirm({ - name, server_url: originalServerUrl === url ? '[__HIDDEN__]' : url.trim(), + name, icon_type: appIcon.type, icon: appIcon.type === 'emoji' ? appIcon.icon : appIcon.fileId, icon_background: appIcon.type === 'emoji' ? appIcon.background : undefined, + server_identifier: serverIdentifier.trim(), }) onHide() } @@ -132,9 +136,20 @@ const MCPModal = ({ />
+
+
+ {t('tools.mcp.modal.serverIdentifier')} +
+
{t('tools.mcp.modal.serverIdentifierTip')}
+ setServerIdentifier(e.target.value)} + placeholder={t('tools.mcp.modal.serverIdentifierPlaceholder')} + /> +
- +
diff --git a/web/app/components/tools/mcp/provider-card.tsx b/web/app/components/tools/mcp/provider-card.tsx index 4c2d764f25..69e00ac918 100644 --- a/web/app/components/tools/mcp/provider-card.tsx +++ b/web/app/components/tools/mcp/provider-card.tsx @@ -90,23 +90,23 @@ const MCPCard = ({
{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.updated_at! * 1000)}`}
-
+
{data.server_identifier}
-
{data.server_url}
+
+
+ + {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.updated_at! * 1000)}`}
+
{data.is_team_authorization && data.tools.length > 0 && } {(!data.is_team_authorization || !data.tools.length) && (
diff --git a/web/app/components/tools/types.ts b/web/app/components/tools/types.ts index cba7d92027..d444ee1f38 100644 --- a/web/app/components/tools/types.ts +++ b/web/app/components/tools/types.ts @@ -54,6 +54,7 @@ export type Collection = { // MCP Server server_url?: string updated_at?: number + server_identifier?: string } export type ToolParameter = { diff --git a/web/i18n/en-US/tools.ts b/web/i18n/en-US/tools.ts index 37526e95e1..0f340ac5fc 100644 --- a/web/i18n/en-US/tools.ts +++ b/web/i18n/en-US/tools.ts @@ -169,6 +169,9 @@ const translation = { serverUrl: 'Server URL', serverUrlPlaceholder: 'URL to server endpiont', warning: 'Updating the server address may affect applications currently using this MCP', + serverIdentifier: 'Server Identifier', + serverIdentifierTip: 'This text will be displayed on the client side, providing basic guidance on how to use the application', + serverIdentifierPlaceholder: 'Unique identifier for this server', cancel: 'Cancel', save: 'Save', confirm: 'Add & Authorize', @@ -191,6 +194,7 @@ const translation = { getTools: 'Get tools', toolsNum: '{{count}} tools included', onlyTool: '1 tool included', + identifier: 'Server Identifier (Click to Copy)', server: { title: 'MCP Server', url: 'Server URL', diff --git a/web/i18n/zh-Hans/tools.ts b/web/i18n/zh-Hans/tools.ts index 6e302c17e8..4a62ffd901 100644 --- a/web/i18n/zh-Hans/tools.ts +++ b/web/i18n/zh-Hans/tools.ts @@ -169,6 +169,9 @@ const translation = { serverUrl: '服务端点 URL', serverUrlPlaceholder: '服务端点的 URL', warning: '修改服务端点 URL 可能会影响使用当前 MCP 的应用。', + serverIdentifier: '服务器标识符', + serverIdentifierTip: '此文本将在客户端显示,为如何使用应用程序提供基本指导', + serverIdentifierPlaceholder: '此服务器的唯一标识符', cancel: '取消', save: '保存', confirm: '添加并授权', @@ -191,6 +194,7 @@ const translation = { getTools: '获取工具', toolsNum: '包含 {{count}} 个工具', onlyTool: '包含 1 个工具', + identifier: '服务器标识符 (点击复制)', server: { title: 'MCP 服务', url: '服务端点 URL', From 14a1bba9b7c268993cca5ba58c3b1093be82e2e4 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Tue, 24 Jun 2025 15:43:48 +0800 Subject: [PATCH 116/126] fetch remote icon --- web/app/components/tools/mcp/modal.tsx | 32 ++++++++++++++++++++++++-- web/package.json | 1 + web/pnpm-lock.yaml | 16 +++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/web/app/components/tools/mcp/modal.tsx b/web/app/components/tools/mcp/modal.tsx index 48f4bbfe0c..b28a813152 100644 --- a/web/app/components/tools/mcp/modal.tsx +++ b/web/app/components/tools/mcp/modal.tsx @@ -1,6 +1,7 @@ 'use client' import React, { useState } from 'react' import { useTranslation } from 'react-i18next' +import { getDomain } from 'tldts' import { RiCloseLine } from '@remixicon/react' import AppIconPicker from '@/app/components/base/app-icon-picker' import type { AppIconSelection } from '@/app/components/base/app-icon-picker' @@ -12,6 +13,7 @@ import type { AppIconType } from '@/types/app' import type { ToolWithProvider } from '@/app/components/workflow/types' import { noop } from 'lodash-es' import Toast from '@/app/components/base/toast' +import { uploadRemoteFileInfo } from '@/service/common' import cn from '@/utils/classnames' export type DuplicateAppModalProps = { @@ -59,6 +61,7 @@ const MCPModal = ({ const [appIcon, setAppIcon] = useState(getIcon(data)) const [showAppIconPicker, setShowAppIconPicker] = useState(false) const [serverIdentifier, setServerIdentifier] = React.useState(data?.server_identifier || '') + const [isFetchingIcon, setIsFetchingIcon] = useState(false) const isValidUrl = (string: string) => { try { @@ -70,12 +73,36 @@ const MCPModal = ({ } } + const handleBlur = async (url: string) => { + if (data) + return + if (!isValidUrl(url)) + return + const domain = getDomain(url) + const remoteIcon = `https://www.google.com/s2/favicons?domain=${domain}&sz=128` + setIsFetchingIcon(true) + try { + const res = await uploadRemoteFileInfo(remoteIcon) + setAppIcon({ type: 'image', url: res.url, fileId: extractFileId(res.url) || '' }) + } + catch (e) { + console.error('Failed to fetch remote icon:', e) + Toast.notify({ type: 'error', message: 'Failed to fetch remote icon' }) + } + finally { + setIsFetchingIcon(false) + } + } + const submit = async () => { if (!isValidUrl(url)) { Toast.notify({ type: 'error', message: 'invalid server url' }) return } - // TODO server identifier validation + if (!serverIdentifier.trim()) { + Toast.notify({ type: 'error', message: 'invalid server identifier' }) + return + } await onConfirm({ server_url: originalServerUrl === url ? '[__HIDDEN__]' : url.trim(), name, @@ -106,6 +133,7 @@ const MCPModal = ({ setUrl(e.target.value)} + onBlur={e => handleBlur(e.target.value.trim())} placeholder={t('tools.mcp.modal.serverUrlPlaceholder')} /> {originalServerUrl && originalServerUrl !== url && ( @@ -149,7 +177,7 @@ const MCPModal = ({
- +
diff --git a/web/package.json b/web/package.json index f583d859e6..8369c7d2cc 100644 --- a/web/package.json +++ b/web/package.json @@ -144,6 +144,7 @@ "sortablejs": "^1.15.0", "swr": "^2.3.0", "tailwind-merge": "^2.5.4", + "tldts": "^7.0.9", "use-context-selector": "^2.0.0", "uuid": "^10.0.0", "zod": "^3.23.8", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index fce3b6581b..9c45786d8a 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -329,6 +329,9 @@ importers: tailwind-merge: specifier: ^2.5.4 version: 2.6.0 + tldts: + specifier: ^7.0.9 + version: 7.0.9 use-context-selector: specifier: ^2.0.0 version: 2.0.0(react@19.0.0)(scheduler@0.23.2) @@ -8039,6 +8042,13 @@ packages: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} + tldts-core@7.0.9: + resolution: {integrity: sha512-/FGY1+CryHsxF9SFiPZlMOcwQsfABkAvOJO5VEKE8TNifVEqgMF7+UVXHGhm1z4gPUfvVS/EYcwhiRU3vUa1ag==} + + tldts@7.0.9: + resolution: {integrity: sha512-/nFtBeNs9nAKIAZE1i3ssOAroci8UqRldFVw5H6RCsNZw7NzDr+Yc3Ek7Tm8XSQKMzw7NSyRSszNxCM0ENsUbg==} + hasBin: true + tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} @@ -18039,6 +18049,12 @@ snapshots: tinyspy@3.0.2: {} + tldts-core@7.0.9: {} + + tldts@7.0.9: + dependencies: + tldts-core: 7.0.9 + tmpl@1.0.5: {} to-regex-range@5.0.1: From 061eada8c536ac51b8a3d4472c7970ecebd083ee Mon Sep 17 00:00:00 2001 From: JzoNg Date: Tue, 24 Jun 2025 16:53:57 +0800 Subject: [PATCH 117/126] server id validation --- 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 b28a813152..357875d7c0 100644 --- a/web/app/components/tools/mcp/modal.tsx +++ b/web/app/components/tools/mcp/modal.tsx @@ -73,6 +73,10 @@ const MCPModal = ({ } } + const isValidServerID = (str: string) => { + return /^[a-z0-9_-]{1,24}$/.test(str) + } + const handleBlur = async (url: string) => { if (data) return @@ -99,7 +103,7 @@ const MCPModal = ({ Toast.notify({ type: 'error', message: 'invalid server url' }) return } - if (!serverIdentifier.trim()) { + if (!isValidServerID(serverIdentifier.trim())) { Toast.notify({ type: 'error', message: 'invalid server identifier' }) return } From f0c89fe4e506355a294172b64bfa1483774c34d6 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Tue, 24 Jun 2025 17:52:29 +0800 Subject: [PATCH 118/126] copy id of tool node --- .../components/workflow/nodes/_base/node.tsx | 6 +++ .../nodes/tool/components/copy-id.tsx | 48 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 web/app/components/workflow/nodes/tool/components/copy-id.tsx diff --git a/web/app/components/workflow/nodes/_base/node.tsx b/web/app/components/workflow/nodes/_base/node.tsx index 527b2f094d..ea60f9e41c 100644 --- a/web/app/components/workflow/nodes/_base/node.tsx +++ b/web/app/components/workflow/nodes/_base/node.tsx @@ -32,6 +32,7 @@ import { import { useNodeIterationInteractions } from '../iteration/use-interactions' import { useNodeLoopInteractions } from '../loop/use-interactions' import type { IterationNodeType } from '../iteration/types' +import CopyID from '../tool/components/copy-id' import { NodeSourceHandle, NodeTargetHandle, @@ -315,6 +316,11 @@ const BaseNode: FC = ({
) } + {data.type === BlockEnum.Tool && ( +
+ +
+ )}
) diff --git a/web/app/components/workflow/nodes/tool/components/copy-id.tsx b/web/app/components/workflow/nodes/tool/components/copy-id.tsx new file mode 100644 index 0000000000..1381447f56 --- /dev/null +++ b/web/app/components/workflow/nodes/tool/components/copy-id.tsx @@ -0,0 +1,48 @@ +'use client' +import React, { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { RiFileCopyLine } from '@remixicon/react' +import copy from 'copy-to-clipboard' +import { debounce } from 'lodash-es' +import Tooltip from '@/app/components/base/tooltip' + +type Props = { + content: string +} + +const prefixEmbedded = 'appOverview.overview.appInfo.embedded' + +const CopyFeedbackNew = ({ content }: Props) => { + const { t } = useTranslation() + const [isCopied, setIsCopied] = useState(false) + + const onClickCopy = debounce(() => { + copy(content) + setIsCopied(true) + }, 100) + + const onMouseLeave = debounce(() => { + setIsCopied(false) + }, 100) + + return ( +
e.stopPropagation()}> + +
{content}
+
+ +
+ ) +} + +export default CopyFeedbackNew From d4f666a480416669faff3281ad4ba0380e95edba Mon Sep 17 00:00:00 2001 From: JzoNg Date: Tue, 24 Jun 2025 20:19:35 +0800 Subject: [PATCH 119/126] use all tool list api --- web/app/components/tools/mcp/index.tsx | 12 ++++++------ web/app/components/tools/provider-list.tsx | 7 +------ 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/web/app/components/tools/mcp/index.tsx b/web/app/components/tools/mcp/index.tsx index b6ed308d5b..76735c0753 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 './detail/provider-detail' import { - useAllMCPTools, + useAllToolProviders, } from '@/service/use-tools' import type { ToolWithProvider } from '@/app/components/workflow/types' import cn from '@/utils/classnames' @@ -34,15 +34,15 @@ function renderDefaultCard() { const MCPList = ({ searchText, }: Props) => { - const { data: list = [], refetch } = useAllMCPTools() + const { data: list = [] as ToolWithProvider[], refetch } = useAllToolProviders() const [isCreation, setIsCreation] = useState(false) 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 - }) + return collection.type === 'mcp' + }) as ToolWithProvider[] }, [list, searchText]) const [currentProviderID, setCurrentProviderID] = useState() @@ -70,7 +70,7 @@ const MCPList = ({ @@ -79,7 +79,7 @@ const MCPList = ({
{currentProvider && ( setCurrentProviderID(undefined)} onUpdate={refetch} isCreation={isCreation} diff --git a/web/app/components/tools/provider-list.tsx b/web/app/components/tools/provider-list.tsx index 72a3351940..9f532ab90c 100644 --- a/web/app/components/tools/provider-list.tsx +++ b/web/app/components/tools/provider-list.tsx @@ -1,7 +1,6 @@ 'use client' import { useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import { useSearchParams } from 'next/navigation' import type { Collection } from './types' import Marketplace from './marketplace' import cn from '@/utils/classnames' @@ -26,12 +25,8 @@ const ProviderList = () => { const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) const containerRef = useRef(null) - const searchParams = useSearchParams() - const authCode = searchParams.get('code') || '' - const providerID = searchParams.get('state') || '' - const [activeTab, setActiveTab] = useTabSearchParams({ - defaultTab: authCode && providerID ? 'mcp' : 'builtin', + defaultTab: 'builtin', }) const options = [ { value: 'builtin', text: t('tools.type.builtIn') }, From fbc8a665ece0717df3cdcdf9295193dcebcf0e74 Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 26 Jun 2025 15:52:56 +0800 Subject: [PATCH 120/126] fix: not config tag overflow --- web/app/components/tools/mcp/provider-card.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/components/tools/mcp/provider-card.tsx b/web/app/components/tools/mcp/provider-card.tsx index 69e00ac918..c225700ae5 100644 --- a/web/app/components/tools/mcp/provider-card.tsx +++ b/web/app/components/tools/mcp/provider-card.tsx @@ -94,7 +94,7 @@ const MCPCard = ({
-
+
{data.tools.length > 0 && ( From 3e047a1c71fec2b3b745ff8fc2ac70a09fe85dad Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 26 Jun 2025 15:59:38 +0800 Subject: [PATCH 121/126] fix: authing two buttons --- web/app/components/tools/mcp/detail/content.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/components/tools/mcp/detail/content.tsx b/web/app/components/tools/mcp/detail/content.tsx index 5984cda2b1..39da653a43 100644 --- a/web/app/components/tools/mcp/detail/content.tsx +++ b/web/app/components/tools/mcp/detail/content.tsx @@ -182,7 +182,7 @@ const MCPDetailContent: FC = ({
- {detail.is_team_authorization && ( + {!isAuthorizing && detail.is_team_authorization && (
-
+
+ +
) : null + } onClick={() => { setShowAppIconPicker(true) }} />
From 08af2112044464113e5b0d313085d1d24c72c7eb Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 26 Jun 2025 17:37:30 +0800 Subject: [PATCH 123/126] fix: scrollbar effect --- .../workflow/block-selector/all-tools.tsx | 3 ++ .../workflow/block-selector/index-bar.tsx | 7 +++-- .../workflow/block-selector/tool/tool.tsx | 2 +- .../workflow/block-selector/tools.tsx | 4 ++- .../use-check-vertical-scrollbar.ts | 31 +++++++++++++++++++ 5 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 web/app/components/workflow/block-selector/use-check-vertical-scrollbar.ts diff --git a/web/app/components/workflow/block-selector/all-tools.tsx b/web/app/components/workflow/block-selector/all-tools.tsx index 22a7482d65..16bdefc814 100644 --- a/web/app/components/workflow/block-selector/all-tools.tsx +++ b/web/app/components/workflow/block-selector/all-tools.tsx @@ -21,6 +21,7 @@ import PluginList, { type ListProps } from '@/app/components/workflow/block-sele import { PluginType } from '../../plugins/types' import { useMarketplacePlugins } from '../../plugins/marketplace/hooks' import { useGlobalPublicStore } from '@/context/global-public-context' +import useCheckVerticalScrollbar from './use-check-vertical-scrollbar' type AllToolsProps = { className?: string @@ -107,6 +108,7 @@ const AllTools = ({ const pluginRef = useRef(null) const wrapElemRef = useRef(null) + const hasVerticalScrollbar = useCheckVerticalScrollbar(wrapElemRef) const isSupportGroupView = [ToolTypeEnum.All, ToolTypeEnum.BuiltIn].includes(activeTab) return ( @@ -149,6 +151,7 @@ const AllTools = ({ hasSearchText={!!searchText} selectedTools={selectedTools} canChooseMCPTool={canChooseMCPTool} + hasScrollBar={hasVerticalScrollbar} /> {/* Plugins from marketplace */} {enable_marketplace && className?: string + hasScrollBar: boolean } -const IndexBar: FC = ({ letters, itemRefs, className }) => { +const IndexBar: FC = ({ letters, itemRefs, className, hasScrollBar }) => { const handleIndexClick = (letter: string) => { const element = itemRefs.current?.[letter] if (element) element.scrollIntoView({ behavior: 'smooth' }) } return ( -
-
+
+
{letters.map(letter => (
handleIndexClick(letter)}> {letter} diff --git a/web/app/components/workflow/block-selector/tool/tool.tsx b/web/app/components/workflow/block-selector/tool/tool.tsx index 0cae4c3384..2130f0160f 100644 --- a/web/app/components/workflow/block-selector/tool/tool.tsx +++ b/web/app/components/workflow/block-selector/tool/tool.tsx @@ -145,7 +145,7 @@ const Tool: FC = ({ return (
diff --git a/web/app/components/workflow/block-selector/tools.tsx b/web/app/components/workflow/block-selector/tools.tsx index cc4cbd2a5d..86893652af 100644 --- a/web/app/components/workflow/block-selector/tools.tsx +++ b/web/app/components/workflow/block-selector/tools.tsx @@ -26,6 +26,7 @@ type ToolsProps = { indexBarClassName?: string selectedTools?: ToolValue[] canChooseMCPTool?: boolean + hasScrollBar: boolean } const Blocks = ({ showWorkflowEmpty, @@ -39,6 +40,7 @@ const Blocks = ({ indexBarClassName, selectedTools, canChooseMCPTool, + hasScrollBar, }: ToolsProps) => { const { t } = useTranslation() const language = useGetLanguage() @@ -131,7 +133,7 @@ const Blocks = ({ ) )} - {isShowLetterIndex && } + {isShowLetterIndex && }
) } diff --git a/web/app/components/workflow/block-selector/use-check-vertical-scrollbar.ts b/web/app/components/workflow/block-selector/use-check-vertical-scrollbar.ts new file mode 100644 index 0000000000..98986cf3b6 --- /dev/null +++ b/web/app/components/workflow/block-selector/use-check-vertical-scrollbar.ts @@ -0,0 +1,31 @@ +import { useEffect, useState } from 'react' + +const useCheckVerticalScrollbar = (ref: React.RefObject) => { + const [hasVerticalScrollbar, setHasVerticalScrollbar] = useState(false) + + useEffect(() => { + const elem = ref.current + if (!elem) return + + const checkScrollbar = () => { + setHasVerticalScrollbar(elem.scrollHeight > elem.clientHeight) + } + + checkScrollbar() + + const resizeObserver = new ResizeObserver(checkScrollbar) + resizeObserver.observe(elem) + + const mutationObserver = new MutationObserver(checkScrollbar) + mutationObserver.observe(elem, { childList: true, subtree: true, characterData: true }) + + return () => { + resizeObserver.disconnect() + mutationObserver.disconnect() + } + }, [ref]) + + return hasVerticalScrollbar +} + +export default useCheckVerticalScrollbar From e3590bc0c1438fd0e4c302a6581a76174163fa02 Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 26 Jun 2025 17:58:21 +0800 Subject: [PATCH 124/126] fix: tool selector size --- .../plugins/plugin-detail-panel/tool-selector/index.tsx | 2 +- web/app/components/workflow/block-selector/tabs.tsx | 2 +- web/app/components/workflow/block-selector/tool-picker.tsx | 5 +++-- web/app/components/workflow/block-selector/tools.tsx | 2 +- 4 files changed, 6 insertions(+), 5 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 db5c2b7854..b9f55387e3 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 @@ -293,7 +293,7 @@ const ToolSelector: FC = ({
{t('plugin.detailPanel.toolSelector.toolLabel')}
= ({ { activeTab === TabsEnum.Tools && ( = ({ -
+
= ({ supportAddCustomTool={supportAddCustomTool} onAddedCustomTool={handleAddedCustomTool} onShowAddCustomCollectionModal={showEditCustomCollectionModal} + inputClassName='grow' />
+
{ !tools.length && !showWorkflowEmpty && (
{t('workflow.tabs.noResult')}
From 4a66d4021079184f4c5b7a67abf9eca53fb5f433 Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 26 Jun 2025 18:14:21 +0800 Subject: [PATCH 125/126] fix: update time cover other info --- 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 c225700ae5..39ce439ec4 100644 --- a/web/app/components/tools/mcp/provider-card.tsx +++ b/web/app/components/tools/mcp/provider-card.tsx @@ -104,8 +104,8 @@ const MCPCard = ({
{t('tools.mcp.noTools')}
)}
-
/
-
{`${t('tools.mcp.updateTime')} ${formatTimeFromNow(data.updated_at! * 1000)}`}
+
/
+
{`${t('tools.mcp.updateTime')} ${formatTimeFromNow(data.updated_at! * 1000)}`}
{data.is_team_authorization && data.tools.length > 0 && } {(!data.is_team_authorization || !data.tools.length) && ( From ba46b2a5fb47825e8d110d4734b0277483df6043 Mon Sep 17 00:00:00 2001 From: Novice Date: Fri, 27 Jun 2025 09:43:03 +0800 Subject: [PATCH 126/126] chore: handle the file upload error --- web/app/components/tools/mcp/modal.tsx | 2 +- web/service/common.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/web/app/components/tools/mcp/modal.tsx b/web/app/components/tools/mcp/modal.tsx index 5812b52828..a60bf4e77c 100644 --- a/web/app/components/tools/mcp/modal.tsx +++ b/web/app/components/tools/mcp/modal.tsx @@ -89,7 +89,7 @@ const MCPModal = ({ const remoteIcon = `https://www.google.com/s2/favicons?domain=${domain}&sz=128` setIsFetchingIcon(true) try { - const res = await uploadRemoteFileInfo(remoteIcon) + const res = await uploadRemoteFileInfo(remoteIcon, undefined, true) setAppIcon({ type: 'image', url: res.url, fileId: extractFileId(res.url) || '' }) } catch (e) { diff --git a/web/service/common.ts b/web/service/common.ts index 700cd4bf51..e071d556d1 100644 --- a/web/service/common.ts +++ b/web/service/common.ts @@ -337,8 +337,8 @@ export const verifyWebAppForgotPasswordToken: Fetcher = ({ url, body }) => post(url, { body }, { isPublicAPI: true }) -export const uploadRemoteFileInfo = (url: string, isPublic?: boolean) => { - return post<{ id: string; name: string; size: number; mime_type: string; url: string }>('/remote-files/upload', { body: { url } }, { isPublicAPI: isPublic }) +export const uploadRemoteFileInfo = (url: string, isPublic?: boolean, silent?: boolean) => { + return post<{ id: string; name: string; size: number; mime_type: string; url: string }>('/remote-files/upload', { body: { url } }, { isPublicAPI: isPublic, silent }) } export const sendEMailLoginCode = (email: string, language = 'en-US') =>