From 571a960c187654521f03551adef93556d5b2dc65 Mon Sep 17 00:00:00 2001 From: yyh Date: Wed, 1 Apr 2026 12:34:41 +0800 Subject: [PATCH] fix(web): migrate workflow overlay previews --- .../block-selector/__tests__/tabs.spec.tsx | 17 +-- .../workflow/block-selector/blocks.tsx | 59 +++++----- .../block-selector/featured-tools.tsx | 108 +++++++++--------- .../block-selector/featured-triggers.tsx | 108 +++++++++--------- .../workflow/block-selector/start-blocks.tsx | 51 +++++---- .../workflow/block-selector/tabs.tsx | 30 ++--- .../block-selector/tool/action-item.tsx | 107 ++++++++--------- .../trigger-plugin/action-item.tsx | 95 +++++++-------- .../__tests__/integration.spec.tsx | 2 +- .../components/advanced-setting.tsx | 20 +++- .../nodes/question-classifier/node.tsx | 17 +-- .../workflow/operator/tip-popup.tsx | 19 ++- web/app/components/workflow/run/node.tsx | 27 +++-- 13 files changed, 333 insertions(+), 327 deletions(-) diff --git a/web/app/components/workflow/block-selector/__tests__/tabs.spec.tsx b/web/app/components/workflow/block-selector/__tests__/tabs.spec.tsx index 7e73e1debdf..fc3e4017099 100644 --- a/web/app/components/workflow/block-selector/__tests__/tabs.spec.tsx +++ b/web/app/components/workflow/block-selector/__tests__/tabs.spec.tsx @@ -19,21 +19,6 @@ const { }, })) -vi.mock('@/app/components/base/tooltip', () => ({ - default: ({ - children, - popupContent, - }: { - children: React.ReactNode - popupContent: React.ReactNode - }) => ( -
- {popupContent} - {children} -
- ), -})) - vi.mock('@/context/global-public-context', () => ({ useGlobalPublicStore: (selector: (state: { systemFeatures: { enable_marketplace: boolean } }) => unknown) => selector({ systemFeatures: { enable_marketplace: true }, @@ -127,7 +112,7 @@ describe('Tabs', () => { render() expect(screen.getByText('start-content')).toBeInTheDocument() - expect(screen.getByText('workflow.tabs.startDisabledTip')).toBeInTheDocument() + expect(screen.getByText('Blocks')).toBeInTheDocument() }) it('should switch tabs through click handlers and render tools content with normalized icons', () => { diff --git a/web/app/components/workflow/block-selector/blocks.tsx b/web/app/components/workflow/block-selector/blocks.tsx index 1d036178522..2d6d219f55a 100644 --- a/web/app/components/workflow/block-selector/blocks.tsx +++ b/web/app/components/workflow/block-selector/blocks.tsx @@ -9,7 +9,7 @@ import { import { useTranslation } from 'react-i18next' import { useStoreApi } from 'reactflow' import Badge from '@/app/components/base/badge' -import Tooltip from '@/app/components/base/tooltip' +import { Popover, PopoverContent, PopoverTrigger } from '@/app/components/base/ui/popover' import BlockIcon from '../block-icon' import { BlockEnum } from '../types' import { BLOCK_CLASSIFICATIONS } from './constants' @@ -93,12 +93,33 @@ const Blocks = ({ } { filteredList.map(block => ( - + onSelect(block.metaData.type)} + > + +
{block.metaData.title}
+ { + block.metaData.type === BlockEnum.LoopEnd && ( + + ) + } + + )} + /> +
{block.metaData.title}
{block.metaData.description}
- )} - > -
onSelect(block.metaData.type)} - > - -
{block.metaData.title}
- { - block.metaData.type === BlockEnum.LoopEnd && ( - - ) - } -
-
+ + )) } diff --git a/web/app/components/workflow/block-selector/featured-tools.tsx b/web/app/components/workflow/block-selector/featured-tools.tsx index 2491ceca9cc..917f71f95c9 100644 --- a/web/app/components/workflow/block-selector/featured-tools.tsx +++ b/web/app/components/workflow/block-selector/featured-tools.tsx @@ -8,7 +8,7 @@ import { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { ArrowDownDoubleLine, ArrowDownRoundFill, ArrowUpDoubleLine } from '@/app/components/base/icons/src/vender/solid/arrows' import Loading from '@/app/components/base/loading' -import Tooltip from '@/app/components/base/tooltip' +import { Popover, PopoverContent, PopoverTrigger } from '@/app/components/base/ui/popover' import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace' import Action from '@/app/components/workflow/block-selector/market-place-plugin/action' import { useGetLanguage } from '@/context/i18n' @@ -256,63 +256,65 @@ function FeaturedToolUninstalledItem({ return ( <> - + +
+ +
+
{label}
+
+
+
+ {installCountLabel} +
setIsActionHovered(true)} + onMouseLeave={() => { + if (!actionOpen) + setIsActionHovered(false) + }} + > + + { + setActionOpen(value) + setIsActionHovered(value) + }} + author={plugin.org} + name={plugin.name} + version={plugin.latest_version} + /> +
+
+ + )} + /> +
{label}
{description}
- )} - disabled={!description || isActionHovered || actionOpen || isInstallModalOpen} - > -
-
- -
-
{label}
-
-
-
- {installCountLabel} -
setIsActionHovered(true)} - onMouseLeave={() => { - if (!actionOpen) - setIsActionHovered(false) - }} - > - - { - setActionOpen(value) - setIsActionHovered(value) - }} - author={plugin.org} - name={plugin.name} - version={plugin.latest_version} - /> -
-
-
-
+ + {isInstallModalOpen && ( - + +
+ +
+
{label}
+
+
+
+ {installCountLabel} +
setIsActionHovered(true)} + onMouseLeave={() => { + if (!actionOpen) + setIsActionHovered(false) + }} + > + + { + setActionOpen(value) + setIsActionHovered(value) + }} + author={plugin.org} + name={plugin.name} + version={plugin.latest_version} + /> +
+
+ + )} + /> +
{label}
{description}
- )} - disabled={!description || isActionHovered || actionOpen || isInstallModalOpen} - > -
-
- -
-
{label}
-
-
-
- {installCountLabel} -
setIsActionHovered(true)} - onMouseLeave={() => { - if (!actionOpen) - setIsActionHovered(false) - }} - > - - { - setActionOpen(value) - setIsActionHovered(value) - }} - author={plugin.org} - name={plugin.name} - version={plugin.latest_version} - /> -
-
-
-
+ + {isInstallModalOpen && ( ( - + onSelect(block.type)} + > + +
+ {t(`blocks.${block.type}`, { ns: 'workflow' })} + {block.type === BlockEnumValues.Start && ( + {t('blocks.originalStartNode', { ns: 'workflow' })} + )} +
+ + )} + /> +
)}
- )} - > -
onSelect(block.type)} - > - -
- {t(`blocks.${block.type}`, { ns: 'workflow' })} - {block.type === BlockEnumValues.Start && ( - {t('blocks.originalStartNode', { ns: 'workflow' })} - )} -
-
-
+ + ), [availableNodesMetaData, onSelect, t]) if (isEmpty) diff --git a/web/app/components/workflow/block-selector/tabs.tsx b/web/app/components/workflow/block-selector/tabs.tsx index 50d9f4970c3..521323878c1 100644 --- a/web/app/components/workflow/block-selector/tabs.tsx +++ b/web/app/components/workflow/block-selector/tabs.tsx @@ -7,7 +7,7 @@ import type { } from '../types' import { memo, useEffect, useMemo } from 'react' import { useTranslation } from 'react-i18next' -import Tooltip from '@/app/components/base/tooltip' +import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip' import { useGlobalPublicStore } from '@/context/global-public-context' import { useFeaturedToolsRecommendations } from '@/service/use-plugins' import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools, useInvalidateAllBuiltInTools } from '@/service/use-tools' @@ -128,19 +128,21 @@ const TabHeaderItem = ({ if (tab.disabled) { return ( - -
- {tab.name} -
+ + + {tab.name} + + )} + /> + + {disabledTip} + ) } 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 e95d02e8d78..5ad47eb285f 100644 --- a/web/app/components/workflow/block-selector/tool/action-item.tsx +++ b/web/app/components/workflow/block-selector/tool/action-item.tsx @@ -7,7 +7,7 @@ import * as React from 'react' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { trackEvent } from '@/app/components/base/amplitude' -import Tooltip from '@/app/components/base/tooltip' +import { Popover, PopoverContent, PopoverTrigger } from '@/app/components/base/ui/popover' import { useGetLanguage } from '@/context/i18n' import useTheme from '@/hooks/use-theme' import { Theme } from '@/types/app' @@ -58,12 +58,57 @@ const ToolItem: FC = ({ }, [theme, normalizedIcon, normalizedIconDark]) return ( - + { + if (disabled) + return + const params: Record = {} + if (payload.parameters) { + payload.parameters.forEach((item) => { + params[item.name] = '' + }) + } + onSelect(BlockEnum.Tool, { + provider_id: provider.id, + provider_type: provider.type, + provider_name: provider.name, + plugin_id: provider.plugin_id, + plugin_unique_identifier: provider.plugin_unique_identifier, + provider_icon: normalizedIcon, + provider_icon_dark: normalizedIconDark, + tool_name: payload.name, + tool_label: payload.label[language], + tool_description: payload.description[language], + title: payload.label[language], + is_team_authorization: provider.is_team_authorization, + paramSchemas: payload.parameters, + params, + meta: provider.meta, + }) + trackEvent('tool_selected', { + tool_name: payload.name, + plugin_id: provider.plugin_id, + }) + }} + > +
+ {payload.label[language]} +
+ {isAdded && ( +
{t('addToolModal.added', { ns: 'tools' })}
+ )} + + )} + /> +
= ({
{payload.label[language]}
{payload.description[language]}
- )} - > -
{ - if (disabled) - return - const params: Record = {} - if (payload.parameters) { - payload.parameters.forEach((item) => { - params[item.name] = '' - }) - } - onSelect(BlockEnum.Tool, { - provider_id: provider.id, - provider_type: provider.type, - provider_name: provider.name, - plugin_id: provider.plugin_id, - plugin_unique_identifier: provider.plugin_unique_identifier, - provider_icon: normalizedIcon, - provider_icon_dark: normalizedIconDark, - tool_name: payload.name, - tool_label: payload.label[language], - tool_description: payload.description[language], - title: payload.label[language], - is_team_authorization: provider.is_team_authorization, - paramSchemas: payload.parameters, - params, - meta: provider.meta, - }) - trackEvent('tool_selected', { - tool_name: payload.name, - plugin_id: provider.plugin_id, - }) - }} - > -
- {payload.label[language]} -
- {isAdded && ( -
{t('addToolModal.added', { ns: 'tools' })}
- )} -
-
+ + ) } export default React.memo(ToolItem) diff --git a/web/app/components/workflow/block-selector/trigger-plugin/action-item.tsx b/web/app/components/workflow/block-selector/trigger-plugin/action-item.tsx index 38ad4951eaf..fb96888d4a9 100644 --- a/web/app/components/workflow/block-selector/trigger-plugin/action-item.tsx +++ b/web/app/components/workflow/block-selector/trigger-plugin/action-item.tsx @@ -4,7 +4,7 @@ import type { TriggerDefaultValue, TriggerWithProvider } from '../types' import type { Event } from '@/app/components/tools/types' import * as React from 'react' import { useTranslation } from 'react-i18next' -import Tooltip from '@/app/components/base/tooltip' +import { Popover, PopoverContent, PopoverTrigger } from '@/app/components/base/ui/popover' import { useGetLanguage } from '@/context/i18n' import { cn } from '@/utils/classnames' import BlockIcon from '../../block-icon' @@ -29,12 +29,51 @@ const TriggerPluginActionItem: FC = ({ const language = useGetLanguage() return ( - + { + if (disabled) + return + const params: Record = {} + if (payload.parameters) { + payload.parameters.forEach((item: any) => { + params[item.name] = '' + }) + } + onSelect(BlockEnum.TriggerPlugin, { + plugin_id: provider.plugin_id, + provider_id: provider.name, + provider_type: provider.type as string, + provider_name: provider.name, + event_name: payload.name, + event_label: payload.label[language], + event_description: payload.description[language], + plugin_unique_identifier: provider.plugin_unique_identifier, + title: payload.label[language], + is_team_authorization: provider.is_team_authorization, + output_schema: payload.output_schema || {}, + paramSchemas: payload.parameters, + params, + meta: provider.meta, + }) + }} + > +
+ {payload.label[language]} +
+ {isAdded && ( +
{t('addToolModal.added', { ns: 'tools' })}
+ )} + + )} + /> +
= ({
{payload.label[language]}
{payload.description[language]}
- )} - > -
{ - if (disabled) - return - const params: Record = {} - if (payload.parameters) { - payload.parameters.forEach((item: any) => { - params[item.name] = '' - }) - } - onSelect(BlockEnum.TriggerPlugin, { - plugin_id: provider.plugin_id, - provider_id: provider.name, - provider_type: provider.type as string, - provider_name: provider.name, - event_name: payload.name, - event_label: payload.label[language], - event_description: payload.description[language], - plugin_unique_identifier: provider.plugin_unique_identifier, - title: payload.label[language], - is_team_authorization: provider.is_team_authorization, - output_schema: payload.output_schema || {}, - paramSchemas: payload.parameters, - params, - meta: provider.meta, - }) - }} - > -
- {payload.label[language]} -
- {isAdded && ( -
{t('addToolModal.added', { ns: 'tools' })}
- )} -
-
+ + ) } export default React.memo(TriggerPluginActionItem) diff --git a/web/app/components/workflow/nodes/question-classifier/__tests__/integration.spec.tsx b/web/app/components/workflow/nodes/question-classifier/__tests__/integration.spec.tsx index c11f78bc087..a151a06d1bf 100644 --- a/web/app/components/workflow/nodes/question-classifier/__tests__/integration.spec.tsx +++ b/web/app/components/workflow/nodes/question-classifier/__tests__/integration.spec.tsx @@ -345,7 +345,7 @@ describe('question-classifier path', () => { expect(screen.getByText(`${longName.slice(0, 50)}...`)).toBeInTheDocument() await user.hover(screen.getByText(`${longName.slice(0, 50)}...`)) - expect(screen.getByText(longName)).toBeInTheDocument() + expect(await screen.findByText(longName)).toBeInTheDocument() rerender( = ({ title={(
{t(`${i18nPrefix}.instruction`, { ns: 'workflow' })} - + + + + )} + /> +
{t(`${i18nPrefix}.instructionTip`, { ns: 'workflow' })}
- )} - triggerClassName="w-3.5 h-3.5 ml-0.5" - /> +
+
)} value={instruction} diff --git a/web/app/components/workflow/nodes/question-classifier/node.tsx b/web/app/components/workflow/nodes/question-classifier/node.tsx index 38ad4e75c69..37dfa697cc6 100644 --- a/web/app/components/workflow/nodes/question-classifier/node.tsx +++ b/web/app/components/workflow/nodes/question-classifier/node.tsx @@ -4,7 +4,7 @@ import type { NodeProps } from 'reactflow' import type { QuestionClassifierNodeType } from './types' import * as React from 'react' import { useTranslation } from 'react-i18next' -import Tooltip from '@/app/components/base/tooltip' +import { Popover, PopoverContent, PopoverTrigger } from '@/app/components/base/ui/popover' import { useTextGenerationCurrentProviderAndModelAndModelList, } from '@/app/components/header/account-setting/model-provider-page/hooks' @@ -47,15 +47,18 @@ const TruncatedClassItem: FC = ({ topic, index, nodeId, {shouldShowTooltip ? ( - + +
- )} - > - {content} -
+ + ) : content} diff --git a/web/app/components/workflow/operator/tip-popup.tsx b/web/app/components/workflow/operator/tip-popup.tsx index 4130db9071b..c0381831b73 100644 --- a/web/app/components/workflow/operator/tip-popup.tsx +++ b/web/app/components/workflow/operator/tip-popup.tsx @@ -1,10 +1,11 @@ +import type { ReactElement } from 'react' import { memo } from 'react' -import Tooltip from '@/app/components/base/tooltip' +import { Popover, PopoverContent, PopoverTrigger } from '@/app/components/base/ui/popover' import ShortcutsName from '../shortcuts-name' type TipPopupProps = { title: string - children: React.ReactNode + children: ReactElement shortcuts?: string[] } const TipPopup = ({ @@ -13,21 +14,17 @@ const TipPopup = ({ shortcuts, }: TipPopupProps) => { return ( - + +
{title} { shortcuts && }
- )} - > - {children} -
+ + ) } diff --git a/web/app/components/workflow/run/node.tsx b/web/app/components/workflow/run/node.tsx index 3075ec6a175..4fac36c0814 100644 --- a/web/app/components/workflow/run/node.tsx +++ b/web/app/components/workflow/run/node.tsx @@ -18,7 +18,7 @@ import { } from '@remixicon/react' import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import Tooltip from '@/app/components/base/tooltip' +import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import ErrorHandleTip from '@/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' @@ -142,18 +142,21 @@ const NodePanel: FC = ({ type={nodeInfo.node_type} toolIcon={((nodeInfo.extras as { icon?: string } | undefined)?.icon || nodeInfo.extras) as string | { content: string, background: string } | undefined} /> - + + {nodeInfo.title} + + )} + /> +
{nodeInfo.title}
- } - > -
- {nodeInfo.title} -
+
{!['running', 'paused'].includes(nodeInfo.status) && !hideInfo && (