From 03861bcee3ab1dd42ce5d4965cd11d1e98bb63f3 Mon Sep 17 00:00:00 2001
From: yyh <92089059+lyzno1@users.noreply.github.com>
Date: Wed, 13 May 2026 14:16:26 +0800
Subject: [PATCH] refactor: stabilize selector preview cards (#36105)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
---
eslint-suppressions.json | 39 ------
.../model-selector/__tests__/index.spec.tsx | 17 +++
.../__tests__/popup-item.spec.tsx | 46 +++++--
.../model-selector/index.tsx | 2 +-
.../model-selector/popup-item.tsx | 91 +++++---------
.../model-selector/popup.tsx | 94 +++++++++++++-
.../workflow/block-selector/blocks.tsx | 102 +++++++++------
.../block-selector/featured-tools.tsx | 73 +++++++----
.../block-selector/featured-triggers.tsx | 82 ++++++++----
.../rag-tool-recommendations/list.tsx | 15 ++-
.../workflow/block-selector/start-blocks.tsx | 118 +++++++++++-------
.../tool/__tests__/tool.spec.tsx | 3 +
.../block-selector/tool/action-item.tsx | 69 +++++++---
.../__tests__/list.spec.tsx | 2 +
.../tool/tool-list-flat-view/list.tsx | 8 +-
.../__tests__/item.spec.tsx | 2 +
.../__tests__/list.spec.tsx | 2 +
.../tool/tool-list-tree-view/item.tsx | 4 +
.../tool/tool-list-tree-view/list.tsx | 4 +
.../workflow/block-selector/tool/tool.tsx | 23 ++--
.../workflow/block-selector/tools.tsx | 18 ++-
.../trigger-plugin/action-item.tsx | 67 +++++++---
.../block-selector/trigger-plugin/item.tsx | 25 ++--
.../block-selector/trigger-plugin/list.tsx | 10 ++
24 files changed, 609 insertions(+), 307 deletions(-)
diff --git a/eslint-suppressions.json b/eslint-suppressions.json
index 9f9c7dc889..f87754ce6a 100644
--- a/eslint-suppressions.json
+++ b/eslint-suppressions.json
@@ -2987,22 +2987,6 @@
"count": 1
}
},
- "web/app/components/workflow/block-selector/featured-tools.tsx": {
- "react/set-state-in-effect": {
- "count": 2
- },
- "ts/no-explicit-any": {
- "count": 2
- }
- },
- "web/app/components/workflow/block-selector/featured-triggers.tsx": {
- "react/set-state-in-effect": {
- "count": 2
- },
- "ts/no-explicit-any": {
- "count": 2
- }
- },
"web/app/components/workflow/block-selector/hooks.ts": {
"react/set-state-in-effect": {
"count": 1
@@ -3038,29 +3022,6 @@
"count": 1
}
},
- "web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx": {
- "ts/no-explicit-any": {
- "count": 1
- }
- },
- "web/app/components/workflow/block-selector/tool/tool.tsx": {
- "react/set-state-in-effect": {
- "count": 2
- }
- },
- "web/app/components/workflow/block-selector/trigger-plugin/action-item.tsx": {
- "ts/no-explicit-any": {
- "count": 1
- }
- },
- "web/app/components/workflow/block-selector/trigger-plugin/item.tsx": {
- "react/set-state-in-effect": {
- "count": 2
- },
- "ts/no-explicit-any": {
- "count": 1
- }
- },
"web/app/components/workflow/block-selector/types.ts": {
"erasable-syntax-only/enums": {
"count": 4
diff --git a/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/index.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/index.spec.tsx
index 4119bca6ae..b7af6a14a3 100644
--- a/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/index.spec.tsx
+++ b/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/index.spec.tsx
@@ -118,6 +118,23 @@ describe('ModelSelector', () => {
expect(triggerButton).toHaveAttribute('aria-expanded', 'false')
})
+ it('should use the default model settings popup width when the trigger is narrow', () => {
+ renderWithQueryClient(
+
+
+
,
+ )
+
+ fireEvent.click(screen.getByRole('combobox'))
+
+ expect(
+ Array.from(document.body.querySelectorAll('[class]')).some(element =>
+ element.className.includes('w-[432px]')
+ && element.className.includes('max-w-[432px]'),
+ ),
+ ).toBe(true)
+ })
+
it('should not open popup when readonly', () => {
renderWithQueryClient()
diff --git a/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/popup-item.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/popup-item.spec.tsx
index e198853ddd..a3db35302c 100644
--- a/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/popup-item.spec.tsx
+++ b/web/app/components/header/account-setting/model-provider-page/model-selector/__tests__/popup-item.spec.tsx
@@ -1,6 +1,7 @@
import type { ReactElement, ReactNode } from 'react'
import type { DefaultModel, Model, ModelItem } from '../../declarations'
import { Combobox } from '@langgenius/dify-ui/combobox'
+import { createPreviewCardHandle } from '@langgenius/dify-ui/preview-card'
import { fireEvent, render, screen } from '@testing-library/react'
import {
ConfigurationMethodEnum,
@@ -106,6 +107,11 @@ const makeProvider = (overrides: Record = {}) => ({
...overrides,
})
+const previewCardProps = () => ({
+ previewCardHandle: createPreviewCardHandle(),
+ onPreviewCardClose: vi.fn(),
+})
+
const createComboboxNode = (
node: ReactElement,
onValueChange = vi.fn(),
@@ -152,7 +158,7 @@ describe('PopupItem', () => {
})
const { container } = renderWithCombobox(
- ,
+ ,
)
expect(container.textContent).toBe('')
@@ -160,7 +166,7 @@ describe('PopupItem', () => {
it('should select the combobox value when clicking an active model', () => {
const onValueChange = vi.fn()
- renderWithCombobox(, onValueChange)
+ renderWithCombobox(, onValueChange)
fireEvent.click(screen.getByText('GPT-4'))
@@ -170,10 +176,27 @@ describe('PopupItem', () => {
)
})
+ it('should close the shared preview before pressing an active model', () => {
+ const onPreviewCardClose = vi.fn()
+ renderWithCombobox(
+ ,
+ )
+
+ fireEvent.pointerDown(screen.getByText('GPT-4'))
+
+ expect(onPreviewCardClose).toHaveBeenCalledTimes(1)
+ })
+
it('should not select the combobox value when model is not active', () => {
const onValueChange = vi.fn()
renderWithCombobox(
,
@@ -188,7 +211,7 @@ describe('PopupItem', () => {
it('should open model modal when clicking add on unconfigured model', () => {
const onValueChange = vi.fn()
const { rerender } = renderWithCombobox(
- ,
+ ,
onValueChange,
)
@@ -206,6 +229,7 @@ describe('PopupItem', () => {
rerender(createComboboxNode(
{
const defaultModel: DefaultModel = { provider: 'openai', model: 'gpt-4' }
renderWithCombobox(
{
renderWithCombobox(
{
})
it('should toggle collapsed state when clicking provider header', () => {
- renderWithCombobox()
+ renderWithCombobox()
expect(screen.getByText('GPT-4'))!.toBeInTheDocument()
@@ -266,7 +292,7 @@ describe('PopupItem', () => {
})
it('should show credential name when using custom provider', () => {
- renderWithCombobox()
+ renderWithCombobox()
expect(screen.getByText('my-api-key'))!.toBeInTheDocument()
})
@@ -283,7 +309,7 @@ describe('PopupItem', () => {
credits: 200,
})
- renderWithCombobox()
+ renderWithCombobox()
expect(screen.getByText('stale-key'))!.toBeInTheDocument()
expect(document.querySelector('.bg-components-badge-status-light-error-bg')).not.toBeNull()
@@ -309,7 +335,7 @@ describe('PopupItem', () => {
credits: 0,
})
- renderWithCombobox()
+ renderWithCombobox()
expect(screen.getByText(/modelProvider\.selector\.configureRequired/))!.toBeInTheDocument()
})
@@ -331,7 +357,7 @@ describe('PopupItem', () => {
credits: 200,
})
- renderWithCombobox()
+ renderWithCombobox()
expect(screen.getByText(/modelProvider\.selector\.aiCredits/))!.toBeInTheDocument()
})
@@ -356,7 +382,7 @@ describe('PopupItem', () => {
credits: 0,
})
- renderWithCombobox()
+ renderWithCombobox()
expect(screen.getByText(/modelProvider\.selector\.creditsExhausted/))!.toBeInTheDocument()
})
@@ -364,7 +390,7 @@ describe('PopupItem', () => {
it('should close the dropdown through dropdown content callbacks', () => {
const onHide = vi.fn()
- renderWithCombobox()
+ renderWithCombobox()
fireEvent.click(screen.getByRole('button', { name: /my-api-key/ }))
fireEvent.click(screen.getByRole('button', { name: 'close dropdown' }))
diff --git a/web/app/components/header/account-setting/model-provider-page/model-selector/index.tsx b/web/app/components/header/account-setting/model-provider-page/model-selector/index.tsx
index debd06d7cd..79ea3dc9c3 100644
--- a/web/app/components/header/account-setting/model-provider-page/model-selector/index.tsx
+++ b/web/app/components/header/account-setting/model-provider-page/model-selector/index.tsx
@@ -130,7 +130,7 @@ function ModelSelector({
['handle']>
type PopupItemProps = {
defaultModel?: DefaultModel
model: Model
+ previewCardHandle: PreviewCardHandle
+ onPreviewCardClose: () => void
onHide: () => void
}
function PopupItem({
defaultModel,
model,
+ previewCardHandle,
+ onPreviewCardClose,
onHide,
}: PopupItemProps) {
const [collapsed, setCollapsed] = useState(false)
@@ -167,7 +176,11 @@ function PopupItem({
)
const itemRender = modelItem.status === ModelStatusEnum.noConfigure
? (
-
+
{rowContent}
+
+ {({ payload }) => (
+
+ )}
+
)
}
+type ModelSelectorPreviewCardProps = {
+ capabilitiesLabel: string
+ language: string
+ payload?: ModelSelectorPreviewPayload
+}
+
+function ModelSelectorPreviewCard({
+ capabilitiesLabel,
+ language,
+ payload,
+}: ModelSelectorPreviewCardProps) {
+ if (!payload)
+ return null
+
+ const { provider, modelItem } = payload
+
+ return (
+
+
+
+
+
{modelItem.label[language] || modelItem.label.en_US}
+
+
+ {!!modelItem.model_type && (
+
+ {modelTypeFormat(modelItem.model_type)}
+
+ )}
+ {!!modelItem.model_properties.mode && (
+
+ {(modelItem.model_properties.mode as string).toLocaleUpperCase()}
+
+ )}
+ {!!modelItem.model_properties.context_size && (
+
+ {sizeFormat(modelItem.model_properties.context_size as number)}
+
+ )}
+
+ {[ModelTypeEnum.textGeneration, ModelTypeEnum.textEmbedding, ModelTypeEnum.rerank].includes(modelItem.model_type as ModelTypeEnum)
+ && modelItem.features?.some(feature => [ModelFeatureEnum.vision, ModelFeatureEnum.audio, ModelFeatureEnum.video, ModelFeatureEnum.document].includes(feature))
+ && (
+
+
{capabilitiesLabel}
+
+ {modelItem.features?.map(feature => (
+
+ ))}
+
+
+ )}
+
+
+ )
+}
+
export default Popup
diff --git a/web/app/components/workflow/block-selector/blocks.tsx b/web/app/components/workflow/block-selector/blocks.tsx
index 8258e4d450..40fcc073f6 100644
--- a/web/app/components/workflow/block-selector/blocks.tsx
+++ b/web/app/components/workflow/block-selector/blocks.tsx
@@ -1,6 +1,7 @@
import type { NodeDefault } from '../types'
import type { BlockClassificationEnum } from './types'
import {
+ createPreviewCardHandle,
PreviewCard,
PreviewCardContent,
PreviewCardTrigger,
@@ -25,6 +26,10 @@ type BlocksProps = {
availableBlocksTypes?: BlockEnum[]
blocks?: NodeDefault[]
}
+type BlockPreviewPayload = {
+ block: NodeDefault
+}
+
const Blocks = ({
searchText,
onSelect,
@@ -34,6 +39,7 @@ const Blocks = ({
const { t } = useTranslation()
const store = useStoreApi()
const blocksFromHooks = useBlocks()
+ const previewCardHandle = useMemo(() => createPreviewCardHandle
(), [])
// Use external blocks if provided, otherwise fallback to hook-based blocks
const blocks = blocksFromProps || blocksFromHooks.map(block => ({
@@ -101,51 +107,38 @@ const Blocks = ({
// hover/focus-only activation is a11y-safe. See
// packages/dify-ui/AGENTS.md → Overlay Primitive Selection.
filteredList.map(block => (
-
- onSelect(block.metaData.type)}
- >
-
- {block.metaData.title}
- {
- block.metaData.type === BlockEnum.LoopEnd && (
-
- )
- }
-
- )}
- />
-
-
+
onSelect(block.metaData.type)}
+ >
- {block.metaData.title}
- {block.metaData.description}
+ {block.metaData.title}
+ {
+ block.metaData.type === BlockEnum.LoopEnd && (
+
+ )
+ }
-
-
+ )}
+ />
))
}
)
- }, [groups, onSelect, t, store])
+ }, [groups, onSelect, previewCardHandle, t, store])
return (
@@ -157,8 +150,43 @@ const Blocks = ({
{
!isEmpty && BLOCK_CLASSIFICATIONS.map(renderGroup)
}
+
+ {({ payload }) => (
+
+ )}
+
)
}
+type BlockPreviewCardProps = {
+ payload?: BlockPreviewPayload
+}
+
+function BlockPreviewCard({
+ payload,
+}: BlockPreviewCardProps) {
+ if (!payload)
+ return null
+
+ const { block } = payload
+
+ return (
+
+
+
+
{block.metaData.title}
+
{block.metaData.description}
+
+
+ )
+}
+
export default memo(Blocks)
diff --git a/web/app/components/workflow/block-selector/featured-tools.tsx b/web/app/components/workflow/block-selector/featured-tools.tsx
index 0cdeebcb79..4b1dc8b138 100644
--- a/web/app/components/workflow/block-selector/featured-tools.tsx
+++ b/web/app/components/workflow/block-selector/featured-tools.tsx
@@ -1,9 +1,10 @@
'use client'
+import type { TFunction } from 'i18next'
import type { ToolWithProvider } from '../types'
import type { ToolDefaultValue, ToolValue } from './types'
import type { Plugin } from '@/app/components/plugins/types'
import type { Locale } from '@/i18n-config'
-import { PreviewCard, PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card'
+import { createPreviewCardHandle, PreviewCard, PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card'
import { RiMoreLine } from '@remixicon/react'
import { useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
@@ -33,6 +34,11 @@ type FeaturedToolsProps = {
isLoading?: boolean
onInstallSuccess?: () => void
}
+type FeaturedToolPreviewPayload = {
+ plugin: Plugin
+ label: string
+ description: string
+}
const STORAGE_KEY = 'workflow_tools_featured_collapsed'
@@ -46,7 +52,9 @@ const FeaturedTools = ({
}: FeaturedToolsProps) => {
const { t } = useTranslation()
const language = useGetLanguage()
+ const previewCardHandle = useMemo(() => createPreviewCardHandle(), [])
const [visibleCount, setVisibleCount] = useState(INITIAL_VISIBLE_COUNT)
+ const [visibleCountPlugins, setVisibleCountPlugins] = useState(plugins)
const [isCollapsed, setIsCollapsed] = useState(() => {
if (isServer)
return false
@@ -54,23 +62,16 @@ const FeaturedTools = ({
return stored === 'true'
})
- useEffect(() => {
- if (isServer)
- return
- const stored = window.localStorage.getItem(STORAGE_KEY)
- if (stored !== null)
- setIsCollapsed(stored === 'true')
- }, [])
-
useEffect(() => {
if (isServer)
return
window.localStorage.setItem(STORAGE_KEY, String(isCollapsed))
}, [isCollapsed])
- useEffect(() => {
+ if (visibleCountPlugins !== plugins) {
+ setVisibleCountPlugins(plugins)
setVisibleCount(INITIAL_VISIBLE_COUNT)
- }, [plugins])
+ }
const limitedPlugins = useMemo(
() => plugins.slice(0, MAX_RECOMMENDED_COUNT),
@@ -174,10 +175,11 @@ const FeaturedTools = ({
key={plugin.plugin_id}
plugin={plugin}
language={language}
+ previewCardHandle={previewCardHandle}
onInstallSuccess={async () => {
await onInstallSuccess?.()
}}
- t={t as any}
+ t={t}
/>
))}
@@ -214,6 +216,11 @@ const FeaturedTools = ({
)}
>
)}
+
+ {({ payload }) => (
+
+ )}
+
)
}
@@ -221,13 +228,15 @@ const FeaturedTools = ({
type FeaturedToolUninstalledItemProps = {
plugin: Plugin
language: Locale
+ previewCardHandle: ReturnType>
onInstallSuccess?: () => Promise | void
- t: (key: string, options?: Record) => string
+ t: TFunction
}
function FeaturedToolUninstalledItem({
plugin,
language,
+ previewCardHandle,
onInstallSuccess,
t,
}: FeaturedToolUninstalledItemProps) {
@@ -296,16 +305,13 @@ function FeaturedToolUninstalledItem({
// Preview is supplementary: icon / label / brief are all reachable from
// the InstallFromMarketplace modal that opens on click, so hover/focus-only
// activation is a11y-safe. See packages/dify-ui/AGENTS.md → Overlay Primitive Selection.
-
-
-
-
-
-
{label}
-
{description}
-
-
-
+
)
: row}
{isInstallModalOpen && (
@@ -325,4 +331,25 @@ function FeaturedToolUninstalledItem({
)
}
+type FeaturedToolPreviewCardProps = {
+ payload?: FeaturedToolPreviewPayload
+}
+
+function FeaturedToolPreviewCard({
+ payload,
+}: FeaturedToolPreviewCardProps) {
+ if (!payload)
+ return null
+
+ return (
+
+
+
+
{payload.label}
+
{payload.description}
+
+
+ )
+}
+
export default FeaturedTools
diff --git a/web/app/components/workflow/block-selector/featured-triggers.tsx b/web/app/components/workflow/block-selector/featured-triggers.tsx
index 3d3cdee2b7..842541d88d 100644
--- a/web/app/components/workflow/block-selector/featured-triggers.tsx
+++ b/web/app/components/workflow/block-selector/featured-triggers.tsx
@@ -1,8 +1,10 @@
'use client'
+import type { TFunction } from 'i18next'
+import type { TriggerPluginActionPreviewPayload } from './trigger-plugin/action-item'
import type { TriggerDefaultValue, TriggerWithProvider } from './types'
import type { Plugin } from '@/app/components/plugins/types'
import type { Locale } from '@/i18n-config'
-import { PreviewCard, PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card'
+import { createPreviewCardHandle, PreviewCard, PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card'
import { RiMoreLine } from '@remixicon/react'
import { useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
@@ -17,6 +19,7 @@ import { formatNumber } from '@/utils/format'
import { getMarketplaceUrl } from '@/utils/var'
import BlockIcon from '../block-icon'
import { BlockEnum } from '../types'
+import { TriggerPluginActionPreviewCard } from './trigger-plugin/action-item'
import TriggerPluginItem from './trigger-plugin/item'
const MAX_RECOMMENDED_COUNT = 15
@@ -29,6 +32,11 @@ type FeaturedTriggersProps = {
isLoading?: boolean
onInstallSuccess?: () => void | Promise
}
+type FeaturedTriggerPreviewPayload = {
+ plugin: Plugin
+ label: string
+ description: string
+}
const STORAGE_KEY = 'workflow_triggers_featured_collapsed'
@@ -41,7 +49,10 @@ const FeaturedTriggers = ({
}: FeaturedTriggersProps) => {
const { t } = useTranslation()
const language = useGetLanguage()
+ const previewCardHandle = useMemo(() => createPreviewCardHandle(), [])
+ const triggerActionPreviewCardHandle = useMemo(() => createPreviewCardHandle(), [])
const [visibleCount, setVisibleCount] = useState(INITIAL_VISIBLE_COUNT)
+ const [visibleCountPlugins, setVisibleCountPlugins] = useState(plugins)
const [isCollapsed, setIsCollapsed] = useState(() => {
if (isServer)
return false
@@ -49,23 +60,16 @@ const FeaturedTriggers = ({
return stored === 'true'
})
- useEffect(() => {
- if (isServer)
- return
- const stored = window.localStorage.getItem(STORAGE_KEY)
- if (stored !== null)
- setIsCollapsed(stored === 'true')
- }, [])
-
useEffect(() => {
if (isServer)
return
window.localStorage.setItem(STORAGE_KEY, String(isCollapsed))
}, [isCollapsed])
- useEffect(() => {
+ if (visibleCountPlugins !== plugins) {
+ setVisibleCountPlugins(plugins)
setVisibleCount(INITIAL_VISIBLE_COUNT)
- }, [plugins])
+ }
const limitedPlugins = useMemo(
() => plugins.slice(0, MAX_RECOMMENDED_COUNT),
@@ -156,6 +160,7 @@ const FeaturedTriggers = ({
key={provider.id}
payload={provider}
hasSearchText={false}
+ previewCardHandle={triggerActionPreviewCardHandle}
onSelect={onSelect}
/>
))}
@@ -169,10 +174,11 @@ const FeaturedTriggers = ({
key={plugin.plugin_id}
plugin={plugin}
language={language}
+ previewCardHandle={previewCardHandle}
onInstallSuccess={async () => {
await onInstallSuccess?.()
}}
- t={t as any}
+ t={t}
/>
))}
@@ -209,6 +215,16 @@ const FeaturedTriggers = ({
)}
>
)}
+
+ {({ payload }) => (
+
+ )}
+
+
+ {({ payload }) => (
+
+ )}
+
)
}
@@ -216,13 +232,15 @@ const FeaturedTriggers = ({
type FeaturedTriggerUninstalledItemProps = {
plugin: Plugin
language: Locale
+ previewCardHandle: ReturnType>
onInstallSuccess?: () => Promise | void
- t: (key: string, options?: Record) => string
+ t: TFunction
}
function FeaturedTriggerUninstalledItem({
plugin,
language,
+ previewCardHandle,
onInstallSuccess,
t,
}: FeaturedTriggerUninstalledItemProps) {
@@ -291,16 +309,13 @@ function FeaturedTriggerUninstalledItem({
// Preview is supplementary: icon / label / brief are all reachable from
// the InstallFromMarketplace modal that opens on click, so hover/focus-only
// activation is a11y-safe. See packages/dify-ui/AGENTS.md → Overlay Primitive Selection.
-
-
-
-
-
-
{label}
-
{description}
-
-
-
+
)
: row}
{isInstallModalOpen && (
@@ -320,4 +335,25 @@ function FeaturedTriggerUninstalledItem({
)
}
+type FeaturedTriggerPreviewCardProps = {
+ payload?: FeaturedTriggerPreviewPayload
+}
+
+function FeaturedTriggerPreviewCard({
+ payload,
+}: FeaturedTriggerPreviewCardProps) {
+ if (!payload)
+ return null
+
+ return (
+
+
+
+
{payload.label}
+
{payload.description}
+
+
+ )
+}
+
export default FeaturedTriggers
diff --git a/web/app/components/workflow/block-selector/rag-tool-recommendations/list.tsx b/web/app/components/workflow/block-selector/rag-tool-recommendations/list.tsx
index d5873ddc1f..3e536b91b5 100644
--- a/web/app/components/workflow/block-selector/rag-tool-recommendations/list.tsx
+++ b/web/app/components/workflow/block-selector/rag-tool-recommendations/list.tsx
@@ -1,11 +1,14 @@
import type { BlockEnum, ToolWithProvider } from '../../types'
+import type { ToolActionPreviewPayload } from '../tool/action-item'
import type { ToolDefaultValue } from '../types'
import type { Plugin } from '@/app/components/plugins/types'
import type { OnSelectBlock } from '@/app/components/workflow/types'
import { cn } from '@langgenius/dify-ui/cn'
+import { createPreviewCardHandle, PreviewCard } from '@langgenius/dify-ui/preview-card'
import { useCallback, useMemo, useRef } from 'react'
import { useGetLanguage } from '@/context/i18n'
import { groupItems } from '../index-bar'
+import { ToolActionPreviewCard } from '../tool/action-item'
import ToolListFlatView from '../tool/tool-list-flat-view/list'
import ToolListTreeView from '../tool/tool-list-tree-view/list'
import { ViewType } from '../view-type-select'
@@ -27,6 +30,7 @@ const List = ({
className,
}: ListProps) => {
const language = useGetLanguage()
+ const previewCardHandle = useMemo(() => createPreviewCardHandle(), [])
const isFlatView = viewType === ViewType.flat
const { letters, groups: withLetterAndGroupViewToolsData } = groupItems(tools, tool => tool.label[language]![0]!)
@@ -58,7 +62,7 @@ const List = ({
return result
}, [withLetterAndGroupViewToolsData, letters])
- const toolRefs = useRef({})
+ const toolRefsRef = useRef>({})
const handleSelect = useCallback((type: BlockEnum, tool: ToolDefaultValue) => {
onSelect(type, tool)
@@ -70,9 +74,10 @@ const List = ({
isFlatView
? (
)
)}
+
+ {({ payload }) => (
+
+ )}
+
{
unInstalledPlugins.map((item) => {
return (
diff --git a/web/app/components/workflow/block-selector/start-blocks.tsx b/web/app/components/workflow/block-selector/start-blocks.tsx
index efc1e5c1b9..9ed4aa4008 100644
--- a/web/app/components/workflow/block-selector/start-blocks.tsx
+++ b/web/app/components/workflow/block-selector/start-blocks.tsx
@@ -1,6 +1,7 @@
import type { BlockEnum, CommonNodeType } from '../types'
import type { TriggerDefaultValue } from './types'
import {
+ createPreviewCardHandle,
PreviewCard,
PreviewCardContent,
PreviewCardTrigger,
@@ -25,6 +26,9 @@ type StartBlocksProps = {
onContentStateChange?: (hasContent: boolean) => void
hideUserInput?: boolean
}
+type StartBlockPreviewPayload = {
+ block: typeof START_BLOCKS[number]
+}
const StartBlocks = ({
searchText,
@@ -35,6 +39,7 @@ const StartBlocks = ({
}: StartBlocksProps) => {
const { t } = useTranslation()
const nodes = useNodes()
+ const previewCardHandle = useMemo(() => createPreviewCardHandle(), [])
// const nodeMetaData = useNodeMetaData()
const filteredBlocks = useMemo(() => {
@@ -74,54 +79,31 @@ const StartBlocks = ({
// the start node, so hover/focus-only activation is a11y-safe. See
// packages/dify-ui/AGENTS.md → Overlay Primitive Selection.
const renderBlock = useCallback((block: typeof START_BLOCKS[number]) => (
-
- onSelect(block.type)}
- >
-
-
- {t(`blocks.${block.type}`, { ns: 'workflow' })}
- {block.type === BlockEnumValues.Start && (
- {t('blocks.originalStartNode', { ns: 'workflow' })}
- )}
-
-
- )}
- />
-
-
+
onSelect(block.type)}
+ >
-
- {block.type === BlockEnumValues.TriggerWebhook
- ? t('customWebhook', { ns: 'workflow' })
- : t(`blocks.${block.type}`, { ns: 'workflow' })}
+
+ {t(`blocks.${block.type}`, { ns: 'workflow' })}
+ {block.type === BlockEnumValues.Start && (
+ {t('blocks.originalStartNode', { ns: 'workflow' })}
+ )}
-
- {t(`blocksAbout.${block.type}`, { ns: 'workflow' })}
-
- {(block.type === BlockEnumValues.TriggerWebhook || block.type === BlockEnumValues.TriggerSchedule) && (
-
- {t('author', { ns: 'tools' })}
- {' '}
- {t('difyTeam', { ns: 'workflow' })}
-
- )}
-
-
- ), [onSelect, t])
+ )}
+ />
+ ), [onSelect, previewCardHandle, t])
if (isEmpty)
return null
@@ -140,8 +122,58 @@ const StartBlocks = ({
))}
+
+ {({ payload }) => (
+
+ )}
+
)
}
+type StartBlockPreviewCardProps = {
+ payload?: StartBlockPreviewPayload
+ t: ReturnType['t']
+}
+
+function StartBlockPreviewCard({
+ payload,
+ t,
+}: StartBlockPreviewCardProps) {
+ if (!payload)
+ return null
+
+ const { block } = payload
+
+ return (
+
+
+
+
+ {block.type === BlockEnumValues.TriggerWebhook
+ ? t('customWebhook', { ns: 'workflow' })
+ : t(`blocks.${block.type}`, { ns: 'workflow' })}
+
+
+ {t(`blocksAbout.${block.type}`, { ns: 'workflow' })}
+
+ {(block.type === BlockEnumValues.TriggerWebhook || block.type === BlockEnumValues.TriggerSchedule) && (
+
+ {t('author', { ns: 'tools' })}
+ {' '}
+ {t('difyTeam', { ns: 'workflow' })}
+
+ )}
+
+
+ )
+}
+
export default memo(StartBlocks)
diff --git a/web/app/components/workflow/block-selector/tool/__tests__/tool.spec.tsx b/web/app/components/workflow/block-selector/tool/__tests__/tool.spec.tsx
index d9fad38854..b0cb229626 100644
--- a/web/app/components/workflow/block-selector/tool/__tests__/tool.spec.tsx
+++ b/web/app/components/workflow/block-selector/tool/__tests__/tool.spec.tsx
@@ -1,3 +1,4 @@
+import { createPreviewCardHandle } from '@langgenius/dify-ui/preview-card'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { trackEvent } from '@/app/components/base/amplitude'
@@ -51,6 +52,7 @@ describe('Tool', () => {
createTool('tool-b', 'Tool B'),
],
})}
+ previewCardHandle={createPreviewCardHandle()}
viewType={ViewType.flat}
hasSearchText={false}
onSelect={onSelect}
@@ -82,6 +84,7 @@ describe('Tool', () => {
type: CollectionType.workflow,
tools: [createTool('workflow-tool', 'Workflow Tool')],
})}
+ previewCardHandle={createPreviewCardHandle()}
viewType={ViewType.flat}
hasSearchText={false}
onSelect={onSelect}
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 343f8482df..05aa05c162 100644
--- a/web/app/components/workflow/block-selector/tool/action-item.tsx
+++ b/web/app/components/workflow/block-selector/tool/action-item.tsx
@@ -1,10 +1,10 @@
'use client'
-import type { FC } from 'react'
+import type { ComponentProps, FC } from 'react'
import type { ToolWithProvider } from '../../types'
import type { ToolDefaultValue } from '../types'
import type { Tool } from '@/app/components/tools/types'
import { cn } from '@langgenius/dify-ui/cn'
-import { PreviewCard, PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card'
+import { PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card'
import * as React from 'react'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
@@ -27,14 +27,25 @@ const normalizeProviderIcon = (icon?: ToolWithProvider['icon']) => {
type Props = {
provider: ToolWithProvider
payload: Tool
+ previewCardHandle: PreviewCardHandle
disabled?: boolean
isAdded?: boolean
onSelect: (type: BlockEnum, tool: ToolDefaultValue) => void
}
+export type ToolActionPreviewPayload = {
+ providerIcon: ToolWithProvider['icon']
+ payload: Tool
+ language: ReturnType
+}
+
+type PreviewCardHandle = NonNullable['handle']>
+export type ToolActionPreviewCardHandle = PreviewCardHandle
+
const ToolItem: FC = ({
provider,
payload,
+ previewCardHandle,
onSelect,
disabled,
isAdded,
@@ -107,21 +118,45 @@ const ToolItem: FC = ({
// reachable from the node inspector after the row is clicked to add the tool,
// so hover/focus-only activation is a11y-safe. See
// packages/dify-ui/AGENTS.md → Overlay Primitive Selection.
-
-
-
-
-
-
{payload.label[language]}
-
{payload.description[language]}
-
-
-
+
)
}
+
+type ToolActionPreviewCardProps = {
+ payload?: ToolActionPreviewPayload
+}
+
+export function ToolActionPreviewCard({
+ payload,
+}: ToolActionPreviewCardProps) {
+ if (!payload)
+ return null
+
+ return (
+
+
+
+
{payload.payload.label[payload.language]}
+
{payload.payload.description[payload.language]}
+
+
+ )
+}
+
export default React.memo(ToolItem)
diff --git a/web/app/components/workflow/block-selector/tool/tool-list-flat-view/__tests__/list.spec.tsx b/web/app/components/workflow/block-selector/tool/tool-list-flat-view/__tests__/list.spec.tsx
index ecb5dfe0a6..38bac7efcb 100644
--- a/web/app/components/workflow/block-selector/tool/tool-list-flat-view/__tests__/list.spec.tsx
+++ b/web/app/components/workflow/block-selector/tool/tool-list-flat-view/__tests__/list.spec.tsx
@@ -1,3 +1,4 @@
+import { createPreviewCardHandle } from '@langgenius/dify-ui/preview-card'
import { render, screen } from '@testing-library/react'
import { useGetLanguage } from '@/context/i18n'
import useTheme from '@/hooks/use-theme'
@@ -37,6 +38,7 @@ describe('ToolListFlatView', () => {
render(
void
letters: string[]
- toolRefs: any
+ toolRefs: RefObject>
selectedTools?: ToolValue[]
}
const ToolViewFlatView: FC = ({
letters,
payload,
+ previewCardHandle,
isShowLetterIndex,
indexBar,
hasSearchText,
@@ -55,6 +58,7 @@ const ToolViewFlatView: FC = ({
>
{
toolList={[createToolProvider({
label: { en_US: 'Provider Alpha', zh_Hans: 'Provider Alpha' },
})]}
+ previewCardHandle={createPreviewCardHandle()}
hasSearchText={false}
onSelect={vi.fn()}
/>,
diff --git a/web/app/components/workflow/block-selector/tool/tool-list-tree-view/__tests__/list.spec.tsx b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/__tests__/list.spec.tsx
index 7b3c083e85..66b5a043ab 100644
--- a/web/app/components/workflow/block-selector/tool/tool-list-tree-view/__tests__/list.spec.tsx
+++ b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/__tests__/list.spec.tsx
@@ -1,3 +1,4 @@
+import { createPreviewCardHandle } from '@langgenius/dify-ui/preview-card'
import { render, screen } from '@testing-library/react'
import { useGetLanguage } from '@/context/i18n'
import useTheme from '@/hooks/use-theme'
@@ -43,6 +44,7 @@ describe('ToolListTreeView', () => {
label: { en_US: 'Custom Provider', zh_Hans: 'Custom Provider' },
})],
}}
+ previewCardHandle={createPreviewCardHandle()}
hasSearchText={false}
onSelect={vi.fn()}
/>,
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 97049e5f79..aa4ce9abe2 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
@@ -2,6 +2,7 @@
import type { FC } from 'react'
import type { BlockEnum, ToolWithProvider } from '../../../types'
import type { ToolDefaultValue, ToolValue } from '../../types'
+import type { ToolActionPreviewCardHandle } from '../action-item'
import * as React from 'react'
import { ViewType } from '../../view-type-select'
import Tool from '../tool'
@@ -9,6 +10,7 @@ import Tool from '../tool'
type Props = {
groupName: string
toolList: ToolWithProvider[]
+ previewCardHandle: ToolActionPreviewCardHandle
hasSearchText: boolean
onSelect: (type: BlockEnum, tool: ToolDefaultValue) => void
canNotSelectMultiple?: boolean
@@ -19,6 +21,7 @@ type Props = {
const Item: FC = ({
groupName,
toolList,
+ previewCardHandle,
hasSearchText,
onSelect,
canNotSelectMultiple,
@@ -35,6 +38,7 @@ const Item: FC = ({
+ previewCardHandle: ToolActionPreviewCardHandle
hasSearchText: boolean
onSelect: (type: BlockEnum, tool: ToolDefaultValue) => void
canNotSelectMultiple?: boolean
@@ -19,6 +21,7 @@ type Props = {
const ToolListTreeView: FC = ({
payload,
+ previewCardHandle,
hasSearchText,
onSelect,
canNotSelectMultiple,
@@ -49,6 +52,7 @@ const ToolListTreeView: FC = ({
key={groupName}
groupName={getI18nGroupName(groupName)}
toolList={payload[groupName]!}
+ previewCardHandle={previewCardHandle}
hasSearchText={hasSearchText}
onSelect={onSelect}
canNotSelectMultiple={canNotSelectMultiple}
diff --git a/web/app/components/workflow/block-selector/tool/tool.tsx b/web/app/components/workflow/block-selector/tool/tool.tsx
index 9a25d10208..ebcfc3b8d1 100644
--- a/web/app/components/workflow/block-selector/tool/tool.tsx
+++ b/web/app/components/workflow/block-selector/tool/tool.tsx
@@ -3,11 +3,12 @@ import type { FC } from 'react'
import type { Tool as ToolType } from '../../../tools/types'
import type { ToolWithProvider } from '../../types'
import type { ToolDefaultValue, ToolValue } from '../types'
+import type { ToolActionPreviewCardHandle } from './action-item'
import { cn } from '@langgenius/dify-ui/cn'
import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react'
import { useHover } from 'ahooks'
import * as React from 'react'
-import { useCallback, useEffect, useMemo, useRef } from 'react'
+import { useCallback, useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { Mcp } from '@/app/components/base/icons/src/vender/other'
import { useMCPToolAvailability } from '@/app/components/workflow/nodes/_base/components/mcp-tool-availability'
@@ -33,6 +34,7 @@ const normalizeProviderIcon = (icon?: ToolWithProvider['icon']) => {
type Props = {
className?: string
payload: ToolWithProvider
+ previewCardHandle: ToolActionPreviewCardHandle
viewType: ViewType
hasSearchText: boolean
onSelect: (type: BlockEnum, tool: ToolDefaultValue) => void
@@ -45,6 +47,7 @@ type Props = {
const Tool: FC = ({
className,
payload,
+ previewCardHandle,
viewType,
hasSearchText,
onSelect,
@@ -59,7 +62,8 @@ const Tool: FC = ({
const notShowProvider = payload.type === CollectionType.workflow
const actions = payload.tools
const hasAction = !notShowProvider
- const [isFold, setFold] = React.useState(true)
+ const [isFold, setIsFold] = React.useState(true)
+ const [isFoldHasSearchText, setIsFoldHasSearchText] = React.useState(hasSearchText)
const ref = useRef(null)
const isHovering = useHover(ref)
const isMCPTool = payload.type === CollectionType.mcp
@@ -146,14 +150,10 @@ const Tool: FC = ({
)
}, [actions, getIsDisabled, isAllSelected, isHovering, language, onSelectMultiple, payload.id, payload.is_team_authorization, payload.name, payload.type, selectedToolsNum, t, totalToolsNum])
- useEffect(() => {
- if (hasSearchText && isFold) {
- setFold(false)
- return
- }
- if (!hasSearchText && !isFold)
- setFold(true)
- }, [hasSearchText])
+ if (isFoldHasSearchText !== hasSearchText) {
+ setIsFoldHasSearchText(hasSearchText)
+ setIsFold(!hasSearchText)
+ }
const FoldIcon = isFold ? RiArrowRightSLine : RiArrowDownSLine
@@ -181,7 +181,7 @@ const Tool: FC = ({
className="group/item flex w-full cursor-pointer items-center justify-between rounded-lg pr-1 pl-3 select-none hover:bg-state-base-hover"
onClick={() => {
if (hasAction) {
- setFold(!isFold)
+ setIsFold(!isFold)
return
}
@@ -240,6 +240,7 @@ const Tool: FC = ({
key={action.name}
provider={payload}
payload={action}
+ previewCardHandle={previewCardHandle}
onSelect={onSelect}
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 cabc079750..e2c4a88048 100644
--- a/web/app/components/workflow/block-selector/tools.tsx
+++ b/web/app/components/workflow/block-selector/tools.tsx
@@ -1,10 +1,13 @@
import type { BlockEnum, ToolWithProvider } from '../types'
+import type { ToolActionPreviewPayload } from './tool/action-item'
import type { ToolDefaultValue, ToolTypeEnum, ToolValue } from './types'
import { cn } from '@langgenius/dify-ui/cn'
+import { createPreviewCardHandle, PreviewCard } from '@langgenius/dify-ui/preview-card'
import { memo, useMemo, useRef } from 'react'
import Empty from '@/app/components/tools/provider/empty'
import { useGetLanguage } from '@/context/i18n'
import IndexBar, { groupItems } from './index-bar'
+import { ToolActionPreviewCard } from './tool/action-item'
import ToolListFlatView from './tool/tool-list-flat-view/list'
import ToolListTreeView from './tool/tool-list-tree-view/list'
import { ViewType } from './view-type-select'
@@ -35,8 +38,8 @@ const Tools = ({
indexBarClassName,
selectedTools,
}: ToolsProps) => {
- // const tools: any = []
const language = useGetLanguage()
+ const previewCardHandle = useMemo(() => createPreviewCardHandle(), [])
const isFlatView = viewType === ViewType.flat
const isShowLetterIndex = isFlatView && tools.length > 10
@@ -85,7 +88,7 @@ const Tools = ({
return result
}, [withLetterAndGroupViewToolsData, letters])
- const toolRefs = useRef({})
+ const toolRefsRef = useRef>({})
return (
@@ -98,21 +101,23 @@ const Tools = ({
isFlatView
? (
}
+ indexBar={
}
/>
)
: (
)
)}
+
+ {({ payload }) => (
+
+ )}
+
)
}
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 38c4c2b0f5..d2829363e7 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
@@ -1,9 +1,9 @@
'use client'
-import type { FC } from 'react'
+import type { ComponentProps, FC } from 'react'
import type { TriggerDefaultValue, TriggerWithProvider } from '../types'
import type { Event } from '@/app/components/tools/types'
import { cn } from '@langgenius/dify-ui/cn'
-import { PreviewCard, PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card'
+import { PreviewCardContent, PreviewCardTrigger } from '@langgenius/dify-ui/preview-card'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { useGetLanguage } from '@/context/i18n'
@@ -13,14 +13,25 @@ import { BlockEnum } from '../../types'
type Props = {
provider: TriggerWithProvider
payload: Event
+ previewCardHandle: TriggerPluginActionPreviewCardHandle
disabled?: boolean
isAdded?: boolean
onSelect: (type: BlockEnum, trigger?: TriggerDefaultValue) => void
}
+export type TriggerPluginActionPreviewPayload = {
+ provider: TriggerWithProvider
+ payload: Event
+ language: ReturnType
+}
+
+type PreviewCardHandle = NonNullable['handle']>
+export type TriggerPluginActionPreviewCardHandle = PreviewCardHandle
+
const TriggerPluginActionItem: FC = ({
provider,
payload,
+ previewCardHandle,
onSelect,
disabled,
isAdded,
@@ -37,7 +48,7 @@ const TriggerPluginActionItem: FC = ({
return
const params: Record = {}
if (payload.parameters) {
- payload.parameters.forEach((item: any) => {
+ payload.parameters.forEach((item) => {
params[item.name] = ''
})
}
@@ -73,21 +84,41 @@ const TriggerPluginActionItem: FC = ({
// reachable from the node inspector after the row is clicked to add the trigger,
// so hover/focus-only activation is a11y-safe. See
// packages/dify-ui/AGENTS.md → Overlay Primitive Selection.
-
-
-
-
-
-
{payload.label[language]}
-
{payload.description[language]}
-
-
-
+
)
}
+
+type TriggerPluginActionPreviewCardProps = {
+ payload?: TriggerPluginActionPreviewPayload
+}
+
+export function TriggerPluginActionPreviewCard({
+ payload,
+}: TriggerPluginActionPreviewCardProps) {
+ if (!payload)
+ return null
+
+ return (
+
+
+
+
{payload.payload.label[payload.language]}
+
{payload.payload.description[payload.language]}
+
+
+ )
+}
+
export default React.memo(TriggerPluginActionItem)
diff --git a/web/app/components/workflow/block-selector/trigger-plugin/item.tsx b/web/app/components/workflow/block-selector/trigger-plugin/item.tsx
index 9e6c315506..68946fb04c 100644
--- a/web/app/components/workflow/block-selector/trigger-plugin/item.tsx
+++ b/web/app/components/workflow/block-selector/trigger-plugin/item.tsx
@@ -1,10 +1,11 @@
'use client'
import type { FC } from 'react'
+import type { TriggerPluginActionPreviewCardHandle } from './action-item'
import type { TriggerDefaultValue, TriggerWithProvider } from '@/app/components/workflow/block-selector/types'
import { cn } from '@langgenius/dify-ui/cn'
import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react'
import * as React from 'react'
-import { useEffect, useMemo, useRef } from 'react'
+import { useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { CollectionType } from '@/app/components/tools/types'
import BlockIcon from '@/app/components/workflow/block-icon'
@@ -27,6 +28,7 @@ type Props = {
className?: string
payload: TriggerWithProvider
hasSearchText: boolean
+ previewCardHandle: TriggerPluginActionPreviewCardHandle
onSelect: (type: BlockEnum, trigger?: TriggerDefaultValue) => void
}
@@ -34,6 +36,7 @@ const TriggerPluginItem: FC = ({
className,
payload,
hasSearchText,
+ previewCardHandle,
onSelect,
}) => {
const { t } = useTranslation()
@@ -42,17 +45,14 @@ const TriggerPluginItem: FC = ({
const notShowProvider = payload.type === CollectionType.workflow
const actions = payload.events
const hasAction = !notShowProvider
- const [isFold, setFold] = React.useState(true)
+ const [isFold, setIsFold] = React.useState(true)
+ const [isFoldHasSearchText, setIsFoldHasSearchText] = React.useState(hasSearchText)
const ref = useRef(null)
- useEffect(() => {
- if (hasSearchText && isFold) {
- setFold(false)
- return
- }
- if (!hasSearchText && !isFold)
- setFold(true)
- }, [hasSearchText])
+ if (isFoldHasSearchText !== hasSearchText) {
+ setIsFoldHasSearchText(hasSearchText)
+ setIsFold(!hasSearchText)
+ }
const FoldIcon = isFold ? RiArrowRightSLine : RiArrowDownSLine
@@ -97,14 +97,14 @@ const TriggerPluginItem: FC = ({
className="group/item flex w-full cursor-pointer items-center justify-between rounded-lg pr-1 pl-3 select-none hover:bg-state-base-hover"
onClick={() => {
if (hasAction) {
- setFold(!isFold)
+ setIsFold(!isFold)
return
}
const event = actions[0]
const params: Record = {}
if (event!.parameters) {
- event!.parameters.forEach((item: any) => {
+ event!.parameters.forEach((item) => {
params[item.name] = ''
})
}
@@ -150,6 +150,7 @@ const TriggerPluginItem: FC = ({
key={action.name}
provider={providerWithResolvedIcon}
payload={action}
+ previewCardHandle={previewCardHandle}
onSelect={onSelect}
disabled={false}
isAdded={false}
diff --git a/web/app/components/workflow/block-selector/trigger-plugin/list.tsx b/web/app/components/workflow/block-selector/trigger-plugin/list.tsx
index 126583be73..2d2752c4f6 100644
--- a/web/app/components/workflow/block-selector/trigger-plugin/list.tsx
+++ b/web/app/components/workflow/block-selector/trigger-plugin/list.tsx
@@ -1,9 +1,12 @@
'use client'
import type { BlockEnum } from '../../types'
import type { TriggerDefaultValue, TriggerWithProvider } from '../types'
+import type { TriggerPluginActionPreviewPayload } from './action-item'
+import { createPreviewCardHandle, PreviewCard } from '@langgenius/dify-ui/preview-card'
import { memo, useEffect, useMemo } from 'react'
import { useGetLanguage } from '@/context/i18n'
import { useAllTriggerPlugins } from '@/service/use-triggers'
+import { TriggerPluginActionPreviewCard } from './action-item'
import TriggerPluginItem from './item'
type TriggerPluginListProps = {
@@ -20,6 +23,7 @@ const TriggerPluginList = ({
}: TriggerPluginListProps) => {
const { data: triggerPluginsData } = useAllTriggerPlugins()
const language = useGetLanguage()
+ const previewCardHandle = useMemo(() => createPreviewCardHandle(), [])
const normalizedSearch = searchText.trim().toLowerCase()
const triggerPlugins = useMemo(() => {
@@ -96,8 +100,14 @@ const TriggerPluginList = ({
payload={plugin}
onSelect={onSelect}
hasSearchText={!!searchText}
+ previewCardHandle={previewCardHandle}
/>
))}
+
+ {({ payload }) => (
+
+ )}
+
)
}