From 6d4d25ee6f05f903193c6d9c88d7ad8ffe9e50f5 Mon Sep 17 00:00:00 2001 From: lyzno1 Date: Fri, 26 Sep 2025 20:05:59 +0800 Subject: [PATCH] feat(workflow): Restore block selector functionality - Restore BLOCKS constant array and useBlocks hook - Add intelligent fallback mechanism for blocks prop - Fix metadata access in StartBlocks tooltip - Restore defaultActiveTab support in NodeSelector - Improve component robustness with graceful degradation - Fix TypeScript errors and component interfaces Phase 1-3 of atomic refactoring complete: - Critical fixes: Constants, hooks, components - Interface fixes: Props, tabs, modal integration - Architecture improvements: Metadata, wrappers --- .../workflow/block-selector/blocks.tsx | 25 ++- .../workflow/block-selector/constants.tsx | 186 +++++++++--------- .../workflow/block-selector/hooks.ts | 47 +++-- .../workflow/block-selector/index.tsx | 4 +- .../workflow/block-selector/main.tsx | 6 +- .../workflow/block-selector/start-blocks.tsx | 6 +- 6 files changed, 149 insertions(+), 125 deletions(-) diff --git a/web/app/components/workflow/block-selector/blocks.tsx b/web/app/components/workflow/block-selector/blocks.tsx index 120fffa1a3..48e2247475 100644 --- a/web/app/components/workflow/block-selector/blocks.tsx +++ b/web/app/components/workflow/block-selector/blocks.tsx @@ -10,6 +10,7 @@ import BlockIcon from '../block-icon' import { BlockEnum } from '../types' import type { NodeDefault } from '../types' import { BLOCK_CLASSIFICATIONS } from './constants' +import { useBlocks } from './hooks' import type { ToolDefaultValue } from './types' import Tooltip from '@/app/components/base/tooltip' import Badge from '@/app/components/base/badge' @@ -18,20 +19,36 @@ type BlocksProps = { searchText: string onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void availableBlocksTypes?: BlockEnum[] - blocks: NodeDefault[] + blocks?: NodeDefault[] } const Blocks = ({ searchText, onSelect, availableBlocksTypes = [], - blocks, + blocks: blocksFromProps, }: BlocksProps) => { const { t } = useTranslation() const store = useStoreApi() + const blocksFromHooks = useBlocks() + + // Use external blocks if provided, otherwise fallback to hook-based blocks + const blocks = blocksFromProps || blocksFromHooks.map(block => ({ + metaData: { + classification: block.classification, + sort: 0, // Default sort order + type: block.type, + title: block.title, + author: 'Dify', + description: block.description, + }, + defaultValue: {}, + checkValid: () => ({ isValid: true }), + }) as NodeDefault) const groups = useMemo(() => { return BLOCK_CLASSIFICATIONS.reduce((acc, classification) => { - const list = groupBy(blocks, 'metaData.classification')[classification].filter((block) => { + const grouped = groupBy(blocks, 'metaData.classification') + const list = (grouped[classification] || []).filter((block) => { return block.metaData.title.toLowerCase().includes(searchText.toLowerCase()) && availableBlocksTypes.includes(block.metaData.type) }) @@ -44,7 +61,7 @@ const Blocks = ({ const isEmpty = Object.values(groups).every(list => !list.length) const renderGroup = useCallback((classification: string) => { - const list = groups[classification].sort((a, b) => a.metaData.sort - b.metaData.sort) + const list = groups[classification].sort((a, b) => (a.metaData.sort || 0) - (b.metaData.sort || 0)) const { getNodes } = store.getState() const nodes = getNodes() const hasKnowledgeBaseNode = nodes.some(node => node.data.type === BlockEnum.KnowledgeBase) diff --git a/web/app/components/workflow/block-selector/constants.tsx b/web/app/components/workflow/block-selector/constants.tsx index d31eb548a7..ec05985453 100644 --- a/web/app/components/workflow/block-selector/constants.tsx +++ b/web/app/components/workflow/block-selector/constants.tsx @@ -60,96 +60,96 @@ export const ENTRY_NODE_TYPES = [ BlockEnum.TriggerPlugin, ] as const -// export const BLOCKS: Block[] = [ -// { -// classification: BlockClassificationEnum.Default, -// type: BlockEnum.LLM, -// title: 'LLM', -// }, -// { -// classification: BlockClassificationEnum.Default, -// type: BlockEnum.KnowledgeRetrieval, -// title: 'Knowledge Retrieval', -// }, -// { -// classification: BlockClassificationEnum.Default, -// type: BlockEnum.End, -// title: 'End', -// }, -// { -// classification: BlockClassificationEnum.Default, -// type: BlockEnum.Answer, -// title: 'Direct Answer', -// }, -// { -// classification: BlockClassificationEnum.QuestionUnderstand, -// type: BlockEnum.QuestionClassifier, -// title: 'Question Classifier', -// }, -// { -// classification: BlockClassificationEnum.Logic, -// type: BlockEnum.IfElse, -// title: 'IF/ELSE', -// }, -// { -// classification: BlockClassificationEnum.Logic, -// type: BlockEnum.LoopEnd, -// title: 'Exit Loop', -// description: '', -// }, -// { -// classification: BlockClassificationEnum.Logic, -// type: BlockEnum.Iteration, -// title: 'Iteration', -// }, -// { -// classification: BlockClassificationEnum.Logic, -// type: BlockEnum.Loop, -// title: 'Loop', -// }, -// { -// classification: BlockClassificationEnum.Transform, -// type: BlockEnum.Code, -// title: 'Code', -// }, -// { -// classification: BlockClassificationEnum.Transform, -// type: BlockEnum.TemplateTransform, -// title: 'Templating Transform', -// }, -// { -// classification: BlockClassificationEnum.Transform, -// type: BlockEnum.VariableAggregator, -// title: 'Variable Aggregator', -// }, -// { -// classification: BlockClassificationEnum.Transform, -// type: BlockEnum.DocExtractor, -// title: 'Doc Extractor', -// }, -// { -// classification: BlockClassificationEnum.Transform, -// type: BlockEnum.Assigner, -// title: 'Variable Assigner', -// }, -// { -// classification: BlockClassificationEnum.Transform, -// type: BlockEnum.ParameterExtractor, -// title: 'Parameter Extractor', -// }, -// { -// classification: BlockClassificationEnum.Utilities, -// type: BlockEnum.HttpRequest, -// title: 'HTTP Request', -// }, -// { -// classification: BlockClassificationEnum.Utilities, -// type: BlockEnum.ListFilter, -// title: 'List Filter', -// }, -// { -// classification: BlockClassificationEnum.Default, -// type: BlockEnum.Agent, -// title: 'Agent', -// }, -// ] +export const BLOCKS: Block[] = [ + { + classification: BlockClassificationEnum.Default, + type: BlockEnum.LLM, + title: 'LLM', + }, + { + classification: BlockClassificationEnum.Default, + type: BlockEnum.KnowledgeRetrieval, + title: 'Knowledge Retrieval', + }, + { + classification: BlockClassificationEnum.Default, + type: BlockEnum.End, + title: 'End', + }, + { + classification: BlockClassificationEnum.Default, + type: BlockEnum.Answer, + title: 'Direct Answer', + }, + { + classification: BlockClassificationEnum.QuestionUnderstand, + type: BlockEnum.QuestionClassifier, + title: 'Question Classifier', + }, + { + classification: BlockClassificationEnum.Logic, + type: BlockEnum.IfElse, + title: 'IF/ELSE', + }, + { + classification: BlockClassificationEnum.Logic, + type: BlockEnum.LoopEnd, + title: 'Exit Loop', + description: '', + }, + { + classification: BlockClassificationEnum.Logic, + type: BlockEnum.Iteration, + title: 'Iteration', + }, + { + classification: BlockClassificationEnum.Logic, + type: BlockEnum.Loop, + title: 'Loop', + }, + { + classification: BlockClassificationEnum.Transform, + type: BlockEnum.Code, + title: 'Code', + }, + { + classification: BlockClassificationEnum.Transform, + type: BlockEnum.TemplateTransform, + title: 'Templating Transform', + }, + { + classification: BlockClassificationEnum.Transform, + type: BlockEnum.VariableAggregator, + title: 'Variable Aggregator', + }, + { + classification: BlockClassificationEnum.Transform, + type: BlockEnum.DocExtractor, + title: 'Doc Extractor', + }, + { + classification: BlockClassificationEnum.Transform, + type: BlockEnum.Assigner, + title: 'Variable Assigner', + }, + { + classification: BlockClassificationEnum.Transform, + type: BlockEnum.ParameterExtractor, + title: 'Parameter Extractor', + }, + { + classification: BlockClassificationEnum.Utilities, + type: BlockEnum.HttpRequest, + title: 'HTTP Request', + }, + { + classification: BlockClassificationEnum.Utilities, + type: BlockEnum.ListFilter, + title: 'List Filter', + }, + { + classification: BlockClassificationEnum.Default, + type: BlockEnum.Agent, + title: 'Agent', + }, +] diff --git a/web/app/components/workflow/block-selector/hooks.ts b/web/app/components/workflow/block-selector/hooks.ts index e0656711a5..d595f8fd09 100644 --- a/web/app/components/workflow/block-selector/hooks.ts +++ b/web/app/components/workflow/block-selector/hooks.ts @@ -3,39 +3,40 @@ import { useState, } from 'react' import { useTranslation } from 'react-i18next' -// import { BLOCKS, START_BLOCKS } from './constants' +import { BLOCKS, START_BLOCKS } from './constants' import { TabsEnum, ToolTypeEnum, } from './types' -// export const useBlocks = () => { -// const { t } = useTranslation() +export const useBlocks = () => { + const { t } = useTranslation() -// return BLOCKS.map((block) => { -// return { -// ...block, -// title: t(`workflow.blocks.${block.type}`), -// } -// }) -// } + return BLOCKS.map((block) => { + return { + ...block, + title: t(`workflow.blocks.${block.type}`), + } + }) +} -// export const useStartBlocks = () => { -// const { t } = useTranslation() +export const useStartBlocks = () => { + const { t } = useTranslation() -// return START_BLOCKS.map((block) => { -// return { -// ...block, -// title: t(`workflow.blocks.${block.type}`), -// } -// }) -// } + return START_BLOCKS.map((block) => { + return { + ...block, + title: t(`workflow.blocks.${block.type}`), + } + }) +} -export const useTabs = ({ noBlocks, noSources, noTools, noStart = true }: { +export const useTabs = ({ noBlocks, noSources, noTools, noStart = true, defaultActiveTab }: { noBlocks?: boolean noSources?: boolean noTools?: boolean noStart?: boolean + defaultActiveTab?: TabsEnum }) => { const { t } = useTranslation() const tabs = useMemo(() => { @@ -60,6 +61,10 @@ export const useTabs = ({ noBlocks, noSources, noTools, noStart = true }: { }, [t, noBlocks, noSources, noTools, noStart]) const initialTab = useMemo(() => { + // If a default tab is specified, use it + if (defaultActiveTab) + return defaultActiveTab + if (noBlocks) return noTools ? TabsEnum.Sources : TabsEnum.Tools @@ -67,7 +72,7 @@ export const useTabs = ({ noBlocks, noSources, noTools, noStart = true }: { return noBlocks ? TabsEnum.Sources : TabsEnum.Blocks return TabsEnum.Blocks - }, [noBlocks, noSources, noTools]) + }, [noBlocks, noSources, noTools, defaultActiveTab]) const [activeTab, setActiveTab] = useState(initialTab) return { diff --git a/web/app/components/workflow/block-selector/index.tsx b/web/app/components/workflow/block-selector/index.tsx index 54e8078e7b..9f7989265a 100644 --- a/web/app/components/workflow/block-selector/index.tsx +++ b/web/app/components/workflow/block-selector/index.tsx @@ -40,8 +40,8 @@ const NodeSelectorWrapper = (props: NodeSelectorProps) => { return ( ) } diff --git a/web/app/components/workflow/block-selector/main.tsx b/web/app/components/workflow/block-selector/main.tsx index ad552f6e3f..cdb22cd2f3 100644 --- a/web/app/components/workflow/block-selector/main.tsx +++ b/web/app/components/workflow/block-selector/main.tsx @@ -52,7 +52,7 @@ export type NodeSelectorProps = { noBlocks?: boolean noTools?: boolean showStartTab?: boolean - // defaultActiveTab?: TabsEnum + defaultActiveTab?: TabsEnum forceShowStartContent?: boolean } const NodeSelector: FC = ({ @@ -74,7 +74,7 @@ const NodeSelector: FC = ({ noBlocks = false, noTools = false, showStartTab = false, - // defaultActiveTab, + defaultActiveTab, forceShowStartContent = false, }) => { const { t } = useTranslation() @@ -107,7 +107,7 @@ const NodeSelector: FC = ({ activeTab, setActiveTab, tabs, - } = useTabs({ noBlocks, noSources: !dataSources.length, noTools, noStart: !showStartTab }) + } = useTabs({ noBlocks, noSources: !dataSources.length, noTools, noStart: !showStartTab, defaultActiveTab }) const handleActiveTabChange = useCallback((newActiveTab: TabsEnum) => { setActiveTab(newActiveTab) diff --git a/web/app/components/workflow/block-selector/start-blocks.tsx b/web/app/components/workflow/block-selector/start-blocks.tsx index d904d27bf9..34e4d4030f 100644 --- a/web/app/components/workflow/block-selector/start-blocks.tsx +++ b/web/app/components/workflow/block-selector/start-blocks.tsx @@ -57,7 +57,7 @@ const StartBlocks = ({ onContentStateChange?.(!isEmpty) }, [isEmpty, onContentStateChange]) - const renderBlock = useCallback((block: typeof START_BLOCKS[0]) => ( + const renderBlock = useCallback((block: { type: BlockEnum; title: string; description?: string }) => ( - {/*
{availableNodesMetaData.nodesMap?.[block.type]?.description}
*/} +
+ {block.description || availableNodesMetaData.nodesMap?.[block.type]?.description} +
{(block.type === BlockEnumValues.TriggerWebhook || block.type === BlockEnumValues.TriggerSchedule) && (
{t('tools.author')} {t('workflow.difyTeam')}