From f7de55364f134e8fbc115c2354f4e55456a36c49 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Thu, 24 Apr 2025 16:29:58 +0800 Subject: [PATCH] chore: refactor workflow --- .../workflow-app/components/workflow-main.tsx | 6 +- .../components/workflow-app/hooks/index.ts | 1 + .../hooks/use-available-nodes-meta-data.ts | 60 +++ .../hooks/use-workflow-template.ts | 12 +- .../workflow/block-selector/blocks.tsx | 22 +- .../workflow/block-selector/constants.tsx | 102 ----- .../workflow/block-selector/hooks.ts | 12 - .../workflow/block-selector/index.tsx | 185 ++------ .../workflow/block-selector/main.tsx | 175 ++++++++ .../workflow/block-selector/tabs.tsx | 14 +- web/app/components/workflow/blocks.tsx | 5 - web/app/components/workflow/constants.ts | 399 ------------------ web/app/components/workflow/constants/node.ts | 42 ++ web/app/components/workflow/custom-edge.tsx | 5 +- .../components/workflow/hooks-store/store.ts | 15 +- web/app/components/workflow/hooks/index.ts | 3 +- .../workflow/hooks/use-available-blocks.ts | 58 +++ .../workflow/hooks/use-checklist.ts | 10 +- .../workflow/hooks/use-nodes-data.ts | 77 ---- .../workflow/hooks/use-nodes-interactions.ts | 27 +- .../workflow/hooks/use-nodes-meta-data.ts | 14 + .../components/workflow/hooks/use-workflow.ts | 10 +- .../error-handle/error-handle-tip.tsx | 2 +- .../nodes/_base/components/next-step/add.tsx | 2 +- .../_base/components/next-step/operator.tsx | 2 +- .../nodes/_base/components/node-handle.tsx | 4 +- .../panel-operator/change-block.tsx | 2 +- .../panel-operator/panel-operator-popup.tsx | 8 +- .../nodes/_base/hooks/use-node-help-link.ts | 70 +-- .../components/workflow/nodes/_base/panel.tsx | 2 +- .../workflow/nodes/agent/default.ts | 17 +- .../workflow/nodes/answer/default.ts | 20 +- .../workflow/nodes/assigner/default.ts | 21 +- .../components/workflow/nodes/code/default.ts | 20 +- .../nodes/document-extractor/default.ts | 21 +- .../components/workflow/nodes/end/default.ts | 17 +- .../components/workflow/nodes/http/default.ts | 23 +- .../workflow/nodes/if-else/default.ts | 22 +- .../workflow/nodes/iteration-start/default.ts | 14 +- .../workflow/nodes/iteration/default.ts | 28 +- .../nodes/iteration/use-interactions.ts | 5 +- .../nodes/knowledge-retrieval/default.ts | 18 +- .../workflow/nodes/list-operator/default.ts | 21 +- .../components/workflow/nodes/llm/default.ts | 19 +- .../workflow/nodes/loop-end/default.ts | 16 +- .../workflow/nodes/loop-start/default.ts | 14 +- .../workflow/nodes/loop/add-block.tsx | 2 +- .../components/workflow/nodes/loop/default.ts | 21 +- .../workflow/nodes/loop/use-interactions.ts | 15 +- .../nodes/parameter-extractor/default.ts | 20 +- .../nodes/question-classifier/default.ts | 20 +- .../workflow/nodes/start/default.ts | 14 +- .../nodes/template-transform/default.ts | 22 +- .../components/workflow/nodes/tool/default.ts | 19 +- .../nodes/variable-assigner/default.ts | 20 +- .../workflow/operator/add-block.tsx | 13 +- web/app/components/workflow/types.ts | 13 +- .../workflow/utils/gen-node-meta-data.ts | 28 ++ web/app/components/workflow/utils/index.ts | 1 + 59 files changed, 695 insertions(+), 1155 deletions(-) create mode 100644 web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts create mode 100644 web/app/components/workflow/block-selector/main.tsx delete mode 100644 web/app/components/workflow/blocks.tsx create mode 100644 web/app/components/workflow/constants/node.ts create mode 100644 web/app/components/workflow/hooks/use-available-blocks.ts delete mode 100644 web/app/components/workflow/hooks/use-nodes-data.ts create mode 100644 web/app/components/workflow/hooks/use-nodes-meta-data.ts create mode 100644 web/app/components/workflow/utils/gen-node-meta-data.ts diff --git a/web/app/components/workflow-app/components/workflow-main.tsx b/web/app/components/workflow-app/components/workflow-main.tsx index 4ff1f4c624..0af038911c 100644 --- a/web/app/components/workflow-app/components/workflow-main.tsx +++ b/web/app/components/workflow-app/components/workflow-main.tsx @@ -7,6 +7,7 @@ import { WorkflowWithInnerContext } from '@/app/components/workflow' import type { WorkflowProps } from '@/app/components/workflow' import WorkflowChildren from './workflow-children' import { + useAvailableNodesMetaData, useNodesSyncDraft, useWorkflowRun, useWorkflowStartRun, @@ -44,6 +45,7 @@ const WorkflowMain = ({ handleWorkflowStartRunInChatflow, handleWorkflowStartRunInWorkflow, } = useWorkflowStartRun() + const availableNodesMetaData = useAvailableNodesMetaData() const hooksStore = useMemo(() => { return { @@ -57,6 +59,7 @@ const WorkflowMain = ({ handleStartWorkflowRun, handleWorkflowStartRunInChatflow, handleWorkflowStartRunInWorkflow, + availableNodesMetaData, } }, [ syncWorkflowDraftWhenPageClose, @@ -69,6 +72,7 @@ const WorkflowMain = ({ handleStartWorkflowRun, handleWorkflowStartRunInChatflow, handleWorkflowStartRunInWorkflow, + availableNodesMetaData, ]) return ( @@ -77,7 +81,7 @@ const WorkflowMain = ({ edges={edges} viewport={viewport} onWorkflowDataUpdate={handleWorkflowDataUpdate} - hooksStore={hooksStore} + hooksStore={hooksStore as any} > diff --git a/web/app/components/workflow-app/hooks/index.ts b/web/app/components/workflow-app/hooks/index.ts index 1517eb9a16..227399b81b 100644 --- a/web/app/components/workflow-app/hooks/index.ts +++ b/web/app/components/workflow-app/hooks/index.ts @@ -4,3 +4,4 @@ export * from './use-nodes-sync-draft' export * from './use-workflow-run' export * from './use-workflow-start-run' export * from './use-is-chat-mode' +export * from './use-available-nodes-meta-data' diff --git a/web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts b/web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts new file mode 100644 index 0000000000..5fb46f7d40 --- /dev/null +++ b/web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts @@ -0,0 +1,60 @@ +import { useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import { useGetLanguage } from '@/context/i18n' +import StartDefault from '@/app/components/workflow/nodes/start/default' +import EndDefault from '@/app/components/workflow/nodes/end/default' +import AnswerDefault from '@/app/components/workflow/nodes/answer/default' +import { WORKFLOW_COMMON_NODES } from '@/app/components/workflow/constants/node' +import type { AvailableNodesMetaData } from '@/app/components/workflow/hooks-store/store' +import { useIsChatMode } from './use-is-chat-mode' + +export const useAvailableNodesMetaData = () => { + const { t } = useTranslation() + const isChatMode = useIsChatMode() + const language = useGetLanguage() + + console.log('isChatMode', isChatMode) + + const mergedNodesMetaData = useMemo(() => [ + ...WORKFLOW_COMMON_NODES, + StartDefault, + ...( + isChatMode + ? [AnswerDefault] + : [EndDefault] + ), + ], [isChatMode]) + + const prefixLink = useMemo(() => { + if (language === 'zh_Hans') + return 'https://docs.dify.ai/zh-hans/guides/workflow/node/' + + return 'https://docs.dify.ai/guides/workflow/node/' + }, [language]) + + const availableNodesMetaData = useMemo(() => mergedNodesMetaData.map((node) => { + return { + ...node, + defaultValue: { + ...node.defaultValue, + type: node.type, + }, + title: t(`workflow.blocks.${node.type}`), + description: t(`workflow.blocksAbout.${node.type}`), + helpLinkUri: `${prefixLink}${node.helpLinkUri}`, + + } + }), [mergedNodesMetaData, t, prefixLink]) + + const availableNodesMetaDataMap = useMemo(() => availableNodesMetaData.reduce((acc, node) => { + acc![node.type] = node + return acc + }, {} as AvailableNodesMetaData['nodesMap']), [availableNodesMetaData]) + + return useMemo(() => { + return { + nodes: availableNodesMetaData, + nodesMap: availableNodesMetaDataMap, + } + }, [availableNodesMetaData, availableNodesMetaDataMap]) +} diff --git a/web/app/components/workflow-app/hooks/use-workflow-template.ts b/web/app/components/workflow-app/hooks/use-workflow-template.ts index 9f47b981dc..c720a27093 100644 --- a/web/app/components/workflow-app/hooks/use-workflow-template.ts +++ b/web/app/components/workflow-app/hooks/use-workflow-template.ts @@ -3,15 +3,17 @@ import { NODE_WIDTH_X_OFFSET, START_INITIAL_POSITION, } from '@/app/components/workflow/constants' -import { useNodesInitialData } from '@/app/components/workflow/hooks' import { useIsChatMode } from './use-is-chat-mode' +import type { StartNodeType } from '@/app/components/workflow/nodes/start/types' +import startDefault from '@/app/components/workflow/nodes/start/default' +import llmDefault from '@/app/components/workflow/nodes/llm/default' +import answerDefault from '@/app/components/workflow/nodes/answer/default' export const useWorkflowTemplate = () => { const isChatMode = useIsChatMode() - const nodesInitialData = useNodesInitialData() const { newNode: startNode } = generateNewNode({ - data: nodesInitialData.start, + data: startDefault.defaultValue as StartNodeType, position: START_INITIAL_POSITION, }) @@ -19,7 +21,7 @@ export const useWorkflowTemplate = () => { const { newNode: llmNode } = generateNewNode({ id: 'llm', data: { - ...nodesInitialData.llm, + ...llmDefault.defaultValue, memory: { window: { enabled: false, size: 10 }, query_prompt_template: '{{#sys.query#}}', @@ -35,7 +37,7 @@ export const useWorkflowTemplate = () => { const { newNode: answerNode } = generateNewNode({ id: 'answer', data: { - ...nodesInitialData.answer, + ...answerDefault.defaultValue, answer: `{{#${llmNode.id}.text#}}`, }, position: { diff --git a/web/app/components/workflow/block-selector/blocks.tsx b/web/app/components/workflow/block-selector/blocks.tsx index 4182530a91..3f387379ed 100644 --- a/web/app/components/workflow/block-selector/blocks.tsx +++ b/web/app/components/workflow/block-selector/blocks.tsx @@ -7,12 +7,8 @@ import { useTranslation } from 'react-i18next' import { groupBy } from 'lodash-es' import BlockIcon from '../block-icon' import { BlockEnum } from '../types' -import { - useIsChatMode, - useNodesExtraData, -} from '../hooks' +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' @@ -21,23 +17,19 @@ type BlocksProps = { searchText: string onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void availableBlocksTypes?: BlockEnum[] + blocks: NodeDefault[] } const Blocks = ({ searchText, onSelect, availableBlocksTypes = [], + blocks, }: BlocksProps) => { const { t } = useTranslation() - const isChatMode = useIsChatMode() - const nodesExtraData = useNodesExtraData() - const blocks = useBlocks() const groups = useMemo(() => { return BLOCK_CLASSIFICATIONS.reduce((acc, classification) => { const list = groupBy(blocks, 'classification')[classification].filter((block) => { - if (block.type === BlockEnum.Answer && !isChatMode) - return false - return block.title.toLowerCase().includes(searchText.toLowerCase()) && availableBlocksTypes.includes(block.type) }) @@ -46,11 +38,11 @@ const Blocks = ({ [classification]: list, } }, {} as Record) - }, [blocks, isChatMode, searchText, availableBlocksTypes]) + }, [blocks, searchText, availableBlocksTypes]) const isEmpty = Object.values(groups).every(list => !list.length) const renderGroup = useCallback((classification: string) => { - const list = groups[classification] + const list = groups[classification].sort((a, b) => a.sort - b.sort) return (
{block.title}
-
{nodesExtraData[block.type].about}
+
{block.description}
)} > @@ -106,7 +98,7 @@ const Blocks = ({ } ) - }, [groups, nodesExtraData, onSelect, t]) + }, [groups, onSelect, t]) return (
diff --git a/web/app/components/workflow/block-selector/constants.tsx b/web/app/components/workflow/block-selector/constants.tsx index 680cbf45b9..f10035bcfd 100644 --- a/web/app/components/workflow/block-selector/constants.tsx +++ b/web/app/components/workflow/block-selector/constants.tsx @@ -1,107 +1,5 @@ -import type { Block } from '../types' -import { BlockEnum } from '../types' import { BlockClassificationEnum } from './types' -export const BLOCKS: Block[] = [ - { - classification: BlockClassificationEnum.Default, - type: BlockEnum.Start, - title: 'Start', - description: '', - }, - { - 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 BLOCK_CLASSIFICATIONS: string[] = [ BlockClassificationEnum.Default, BlockClassificationEnum.QuestionUnderstand, diff --git a/web/app/components/workflow/block-selector/hooks.ts b/web/app/components/workflow/block-selector/hooks.ts index a8b1759506..7f33a06e72 100644 --- a/web/app/components/workflow/block-selector/hooks.ts +++ b/web/app/components/workflow/block-selector/hooks.ts @@ -1,21 +1,9 @@ import { useTranslation } from 'react-i18next' -import { BLOCKS } from './constants' import { TabsEnum, ToolTypeEnum, } from './types' -export const useBlocks = () => { - const { t } = useTranslation() - - return BLOCKS.map((block) => { - return { - ...block, - title: t(`workflow.blocks.${block.type}`), - } - }) -} - export const useTabs = () => { const { t } = useTranslation() diff --git a/web/app/components/workflow/block-selector/index.tsx b/web/app/components/workflow/block-selector/index.tsx index f0f57adefe..c930e4b740 100644 --- a/web/app/components/workflow/block-selector/index.tsx +++ b/web/app/components/workflow/block-selector/index.tsx @@ -1,171 +1,36 @@ -import type { - FC, - MouseEventHandler, -} from 'react' -import { - memo, - useCallback, - useMemo, - useState, -} from 'react' -import { useTranslation } from 'react-i18next' -import type { - OffsetOptions, - Placement, -} from '@floating-ui/react' -import type { BlockEnum, OnSelectBlock } from '../types' -import Tabs from './tabs' -import { TabsEnum } from './types' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' -import Input from '@/app/components/base/input' -import SearchBox from '@/app/components/plugins/marketplace/search-box' +import { useMemo } from 'react' +import type { NodeSelectorProps } from './main' +import NodeSelector from './main' +import { useHooksStore } from '@/app/components/workflow/hooks-store/store' +import { BlockEnum } from '@/app/components/workflow/types' -import { - Plus02, -} from '@/app/components/base/icons/src/vender/line/general' +const NodeSelectorWrapper = (props: NodeSelectorProps) => { + const availableNodesMetaData = useHooksStore(s => s.availableNodesMetaData) -type NodeSelectorProps = { - open?: boolean - onOpenChange?: (open: boolean) => void - onSelect: OnSelectBlock - trigger?: (open: boolean) => React.ReactNode - placement?: Placement - offset?: OffsetOptions - triggerStyle?: React.CSSProperties - triggerClassName?: (open: boolean) => string - triggerInnerClassName?: string - popupClassName?: string - asChild?: boolean - availableBlocksTypes?: BlockEnum[] - disabled?: boolean - noBlocks?: boolean -} -const NodeSelector: FC = ({ - open: openFromProps, - onOpenChange, - onSelect, - trigger, - placement = 'right', - offset = 6, - triggerClassName, - triggerInnerClassName, - triggerStyle, - popupClassName, - asChild, - availableBlocksTypes, - disabled, - noBlocks = false, -}) => { - const { t } = useTranslation() - const [searchText, setSearchText] = useState('') - const [tags, setTags] = useState([]) - const [localOpen, setLocalOpen] = useState(false) - const open = openFromProps === undefined ? localOpen : openFromProps - const handleOpenChange = useCallback((newOpen: boolean) => { - setLocalOpen(newOpen) + const blocks = useMemo(() => { + const result = availableNodesMetaData?.nodes || [] + console.log(result, 'result') - if (!newOpen) - setSearchText('') + return result.filter((block) => { + if (block.type === BlockEnum.Start) + return false - if (onOpenChange) - onOpenChange(newOpen) - }, [onOpenChange]) - const handleTrigger = useCallback>((e) => { - if (disabled) - return - e.stopPropagation() - handleOpenChange(!open) - }, [handleOpenChange, open, disabled]) - const handleSelect = useCallback((type, toolDefaultValue) => { - handleOpenChange(false) - onSelect(type, toolDefaultValue) - }, [handleOpenChange, onSelect]) + if (block.type === BlockEnum.IterationStart) + return false - const [activeTab, setActiveTab] = useState(noBlocks ? TabsEnum.Tools : TabsEnum.Blocks) - const handleActiveTabChange = useCallback((newActiveTab: TabsEnum) => { - setActiveTab(newActiveTab) - }, []) - const searchPlaceholder = useMemo(() => { - if (activeTab === TabsEnum.Blocks) - return t('workflow.tabs.searchBlock') + if (block.type === BlockEnum.LoopStart) + return false - if (activeTab === TabsEnum.Tools) - return t('workflow.tabs.searchTool') - return '' - }, [activeTab, t]) + return true + }) + }, [availableNodesMetaData?.nodes]) return ( - - - { - trigger - ? trigger(open) - : ( -
- -
- ) - } -
- -
-
e.stopPropagation()}> - {activeTab === TabsEnum.Blocks && ( - setSearchText(e.target.value)} - onClear={() => setSearchText('')} - /> - )} - {activeTab === TabsEnum.Tools && ( - - )} - -
- -
-
-
+ ) } -export default memo(NodeSelector) +export default NodeSelectorWrapper diff --git a/web/app/components/workflow/block-selector/main.tsx b/web/app/components/workflow/block-selector/main.tsx new file mode 100644 index 0000000000..88dc7d06dc --- /dev/null +++ b/web/app/components/workflow/block-selector/main.tsx @@ -0,0 +1,175 @@ +import type { + FC, + MouseEventHandler, +} from 'react' +import { + memo, + useCallback, + useMemo, + useState, +} from 'react' +import { useTranslation } from 'react-i18next' +import type { + OffsetOptions, + Placement, +} from '@floating-ui/react' +import type { + BlockEnum, + NodeDefault, + OnSelectBlock, +} from '../types' +import Tabs from './tabs' +import { TabsEnum } from './types' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import Input from '@/app/components/base/input' +import SearchBox from '@/app/components/plugins/marketplace/search-box' + +import { + Plus02, +} from '@/app/components/base/icons/src/vender/line/general' + +export type NodeSelectorProps = { + open?: boolean + onOpenChange?: (open: boolean) => void + onSelect: OnSelectBlock + trigger?: (open: boolean) => React.ReactNode + placement?: Placement + offset?: OffsetOptions + triggerStyle?: React.CSSProperties + triggerClassName?: (open: boolean) => string + triggerInnerClassName?: string + popupClassName?: string + asChild?: boolean + availableBlocksTypes?: BlockEnum[] + disabled?: boolean + blocks?: NodeDefault[] +} +const NodeSelector: FC = ({ + open: openFromProps, + onOpenChange, + onSelect, + trigger, + placement = 'right', + offset = 6, + triggerClassName, + triggerInnerClassName, + triggerStyle, + popupClassName, + asChild, + availableBlocksTypes, + disabled, + blocks = [], +}) => { + const { t } = useTranslation() + const [searchText, setSearchText] = useState('') + const [tags, setTags] = useState([]) + const [localOpen, setLocalOpen] = useState(false) + const open = openFromProps === undefined ? localOpen : openFromProps + const handleOpenChange = useCallback((newOpen: boolean) => { + setLocalOpen(newOpen) + + if (!newOpen) + setSearchText('') + + if (onOpenChange) + onOpenChange(newOpen) + }, [onOpenChange]) + const handleTrigger = useCallback>((e) => { + if (disabled) + return + e.stopPropagation() + handleOpenChange(!open) + }, [handleOpenChange, open, disabled]) + const handleSelect = useCallback((type, toolDefaultValue) => { + handleOpenChange(false) + onSelect(type, toolDefaultValue) + }, [handleOpenChange, onSelect]) + + const [activeTab, setActiveTab] = useState(!blocks.length ? TabsEnum.Tools : TabsEnum.Blocks) + const handleActiveTabChange = useCallback((newActiveTab: TabsEnum) => { + setActiveTab(newActiveTab) + }, []) + const searchPlaceholder = useMemo(() => { + if (activeTab === TabsEnum.Blocks) + return t('workflow.tabs.searchBlock') + + if (activeTab === TabsEnum.Tools) + return t('workflow.tabs.searchTool') + return '' + }, [activeTab, t]) + + return ( + + + { + trigger + ? trigger(open) + : ( +
+ +
+ ) + } +
+ +
+
e.stopPropagation()}> + {activeTab === TabsEnum.Blocks && ( + setSearchText(e.target.value)} + onClear={() => setSearchText('')} + /> + )} + {activeTab === TabsEnum.Tools && ( + + )} + +
+ +
+
+
+ ) +} + +export default memo(NodeSelector) diff --git a/web/app/components/workflow/block-selector/tabs.tsx b/web/app/components/workflow/block-selector/tabs.tsx index 67aaaba1a5..db2f3d8535 100644 --- a/web/app/components/workflow/block-selector/tabs.tsx +++ b/web/app/components/workflow/block-selector/tabs.tsx @@ -1,7 +1,10 @@ import type { FC } from 'react' import { memo } from 'react' import { useAllBuiltInTools, useAllCustomTools, useAllWorkflowTools } from '@/service/use-tools' -import type { BlockEnum } from '../types' +import type { + BlockEnum, + NodeDefault, +} from '../types' import { useTabs } from './hooks' import type { ToolDefaultValue } from './types' import { TabsEnum } from './types' @@ -16,7 +19,7 @@ export type TabsProps = { tags: string[] onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void availableBlocksTypes?: BlockEnum[] - noBlocks?: boolean + blocks: NodeDefault[] } const Tabs: FC = ({ activeTab, @@ -25,7 +28,7 @@ const Tabs: FC = ({ searchText, onSelect, availableBlocksTypes, - noBlocks, + blocks, }) => { const tabs = useTabs() const { data: buildInTools } = useAllBuiltInTools() @@ -35,7 +38,7 @@ const Tabs: FC = ({ return (
e.stopPropagation()}> { - !noBlocks && ( + !!blocks.length && (
{ tabs.map(tab => ( @@ -57,11 +60,12 @@ const Tabs: FC = ({ ) } { - activeTab === TabsEnum.Blocks && !noBlocks && ( + activeTab === TabsEnum.Blocks && !!blocks.length && ( ) } diff --git a/web/app/components/workflow/blocks.tsx b/web/app/components/workflow/blocks.tsx deleted file mode 100644 index 334ddbf087..0000000000 --- a/web/app/components/workflow/blocks.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { BlockEnum } from './types' - -export const ALL_AVAILABLE_BLOCKS = Object.values(BlockEnum) -export const ALL_CHAT_AVAILABLE_BLOCKS = ALL_AVAILABLE_BLOCKS.filter(key => key !== BlockEnum.End && key !== BlockEnum.Start) as BlockEnum[] -export const ALL_COMPLETION_AVAILABLE_BLOCKS = ALL_AVAILABLE_BLOCKS.filter(key => key !== BlockEnum.Answer && key !== BlockEnum.Start) as BlockEnum[] diff --git a/web/app/components/workflow/constants.ts b/web/app/components/workflow/constants.ts index cdfd963cfa..6410192d89 100644 --- a/web/app/components/workflow/constants.ts +++ b/web/app/components/workflow/constants.ts @@ -1,405 +1,6 @@ import type { Var } from './types' import { BlockEnum, VarType } from './types' -import StartNodeDefault from './nodes/start/default' -import AnswerDefault from './nodes/answer/default' -import LLMDefault from './nodes/llm/default' -import KnowledgeRetrievalDefault from './nodes/knowledge-retrieval/default' -import QuestionClassifierDefault from './nodes/question-classifier/default' -import IfElseDefault from './nodes/if-else/default' -import CodeDefault from './nodes/code/default' -import TemplateTransformDefault from './nodes/template-transform/default' -import HttpRequestDefault from './nodes/http/default' -import ParameterExtractorDefault from './nodes/parameter-extractor/default' -import ToolDefault from './nodes/tool/default' -import VariableAssignerDefault from './nodes/variable-assigner/default' -import AssignerDefault from './nodes/assigner/default' -import EndNodeDefault from './nodes/end/default' -import IterationDefault from './nodes/iteration/default' -import LoopDefault from './nodes/loop/default' -import DocExtractorDefault from './nodes/document-extractor/default' -import ListFilterDefault from './nodes/list-operator/default' -import IterationStartDefault from './nodes/iteration-start/default' -import AgentDefault from './nodes/agent/default' -import LoopStartDefault from './nodes/loop-start/default' -import LoopEndDefault from './nodes/loop-end/default' -type NodesExtraData = { - author: string - about: string - availablePrevNodes: BlockEnum[] - availableNextNodes: BlockEnum[] - getAvailablePrevNodes: (isChatMode: boolean) => BlockEnum[] - getAvailableNextNodes: (isChatMode: boolean) => BlockEnum[] - checkValid: any -} -export const NODES_EXTRA_DATA: Record = { - [BlockEnum.Start]: { - author: 'Dify', - about: '', - availablePrevNodes: [], - availableNextNodes: [], - getAvailablePrevNodes: StartNodeDefault.getAvailablePrevNodes, - getAvailableNextNodes: StartNodeDefault.getAvailableNextNodes, - checkValid: StartNodeDefault.checkValid, - }, - [BlockEnum.End]: { - author: 'Dify', - about: '', - availablePrevNodes: [], - availableNextNodes: [], - getAvailablePrevNodes: EndNodeDefault.getAvailablePrevNodes, - getAvailableNextNodes: EndNodeDefault.getAvailableNextNodes, - checkValid: EndNodeDefault.checkValid, - }, - [BlockEnum.Answer]: { - author: 'Dify', - about: '', - availablePrevNodes: [], - availableNextNodes: [], - getAvailablePrevNodes: AnswerDefault.getAvailablePrevNodes, - getAvailableNextNodes: AnswerDefault.getAvailableNextNodes, - checkValid: AnswerDefault.checkValid, - }, - [BlockEnum.LLM]: { - author: 'Dify', - about: '', - availablePrevNodes: [], - availableNextNodes: [], - getAvailablePrevNodes: LLMDefault.getAvailablePrevNodes, - getAvailableNextNodes: LLMDefault.getAvailableNextNodes, - checkValid: LLMDefault.checkValid, - }, - [BlockEnum.KnowledgeRetrieval]: { - author: 'Dify', - about: '', - availablePrevNodes: [], - availableNextNodes: [], - getAvailablePrevNodes: KnowledgeRetrievalDefault.getAvailablePrevNodes, - getAvailableNextNodes: KnowledgeRetrievalDefault.getAvailableNextNodes, - checkValid: KnowledgeRetrievalDefault.checkValid, - }, - [BlockEnum.IfElse]: { - author: 'Dify', - about: '', - availablePrevNodes: [], - availableNextNodes: [], - getAvailablePrevNodes: IfElseDefault.getAvailablePrevNodes, - getAvailableNextNodes: IfElseDefault.getAvailableNextNodes, - checkValid: IfElseDefault.checkValid, - }, - [BlockEnum.Iteration]: { - author: 'Dify', - about: '', - availablePrevNodes: [], - availableNextNodes: [], - getAvailablePrevNodes: IterationDefault.getAvailablePrevNodes, - getAvailableNextNodes: IterationDefault.getAvailableNextNodes, - checkValid: IterationDefault.checkValid, - }, - [BlockEnum.IterationStart]: { - author: 'Dify', - about: '', - availablePrevNodes: [], - availableNextNodes: [], - getAvailablePrevNodes: IterationStartDefault.getAvailablePrevNodes, - getAvailableNextNodes: IterationStartDefault.getAvailableNextNodes, - checkValid: IterationStartDefault.checkValid, - }, - [BlockEnum.Loop]: { - author: 'AICT-Team', - about: '', - availablePrevNodes: [], - availableNextNodes: [], - getAvailablePrevNodes: LoopDefault.getAvailablePrevNodes, - getAvailableNextNodes: LoopDefault.getAvailableNextNodes, - checkValid: LoopDefault.checkValid, - }, - [BlockEnum.LoopStart]: { - author: 'AICT-Team', - about: '', - availablePrevNodes: [], - availableNextNodes: [], - getAvailablePrevNodes: LoopStartDefault.getAvailablePrevNodes, - getAvailableNextNodes: LoopStartDefault.getAvailableNextNodes, - checkValid: LoopStartDefault.checkValid, - }, - [BlockEnum.LoopEnd]: { - author: 'Dify', - about: '', - availablePrevNodes: [], - availableNextNodes: [], - getAvailablePrevNodes: LoopEndDefault.getAvailablePrevNodes, - getAvailableNextNodes: LoopEndDefault.getAvailableNextNodes, - checkValid: LoopEndDefault.checkValid, - }, - [BlockEnum.Code]: { - author: 'Dify', - about: '', - availablePrevNodes: [], - availableNextNodes: [], - getAvailablePrevNodes: CodeDefault.getAvailablePrevNodes, - getAvailableNextNodes: CodeDefault.getAvailableNextNodes, - checkValid: CodeDefault.checkValid, - }, - [BlockEnum.TemplateTransform]: { - author: 'Dify', - about: '', - availablePrevNodes: [], - availableNextNodes: [], - getAvailablePrevNodes: TemplateTransformDefault.getAvailablePrevNodes, - getAvailableNextNodes: TemplateTransformDefault.getAvailableNextNodes, - checkValid: TemplateTransformDefault.checkValid, - }, - [BlockEnum.QuestionClassifier]: { - author: 'Dify', - about: '', - availablePrevNodes: [], - availableNextNodes: [], - getAvailablePrevNodes: QuestionClassifierDefault.getAvailablePrevNodes, - getAvailableNextNodes: QuestionClassifierDefault.getAvailableNextNodes, - checkValid: QuestionClassifierDefault.checkValid, - }, - [BlockEnum.HttpRequest]: { - author: 'Dify', - about: '', - availablePrevNodes: [], - availableNextNodes: [], - getAvailablePrevNodes: HttpRequestDefault.getAvailablePrevNodes, - getAvailableNextNodes: HttpRequestDefault.getAvailableNextNodes, - checkValid: HttpRequestDefault.checkValid, - }, - [BlockEnum.VariableAssigner]: { - author: 'Dify', - about: '', - availablePrevNodes: [], - availableNextNodes: [], - getAvailablePrevNodes: VariableAssignerDefault.getAvailablePrevNodes, - getAvailableNextNodes: VariableAssignerDefault.getAvailableNextNodes, - checkValid: VariableAssignerDefault.checkValid, - }, - [BlockEnum.Assigner]: { - author: 'Dify', - about: '', - availablePrevNodes: [], - availableNextNodes: [], - getAvailablePrevNodes: AssignerDefault.getAvailablePrevNodes, - getAvailableNextNodes: AssignerDefault.getAvailableNextNodes, - checkValid: AssignerDefault.checkValid, - }, - [BlockEnum.VariableAggregator]: { - author: 'Dify', - about: '', - availablePrevNodes: [], - availableNextNodes: [], - getAvailablePrevNodes: VariableAssignerDefault.getAvailablePrevNodes, - getAvailableNextNodes: VariableAssignerDefault.getAvailableNextNodes, - checkValid: VariableAssignerDefault.checkValid, - }, - [BlockEnum.ParameterExtractor]: { - author: 'Dify', - about: '', - availablePrevNodes: [], - availableNextNodes: [], - getAvailablePrevNodes: ParameterExtractorDefault.getAvailablePrevNodes, - getAvailableNextNodes: ParameterExtractorDefault.getAvailableNextNodes, - checkValid: ParameterExtractorDefault.checkValid, - }, - [BlockEnum.Tool]: { - author: 'Dify', - about: '', - availablePrevNodes: [], - availableNextNodes: [], - getAvailablePrevNodes: ToolDefault.getAvailablePrevNodes, - getAvailableNextNodes: ToolDefault.getAvailableNextNodes, - checkValid: ToolDefault.checkValid, - }, - [BlockEnum.DocExtractor]: { - author: 'Dify', - about: '', - availablePrevNodes: [], - availableNextNodes: [], - getAvailablePrevNodes: DocExtractorDefault.getAvailablePrevNodes, - getAvailableNextNodes: DocExtractorDefault.getAvailableNextNodes, - checkValid: DocExtractorDefault.checkValid, - }, - [BlockEnum.ListFilter]: { - author: 'Dify', - about: '', - availablePrevNodes: [], - availableNextNodes: [], - getAvailablePrevNodes: ListFilterDefault.getAvailablePrevNodes, - getAvailableNextNodes: ListFilterDefault.getAvailableNextNodes, - checkValid: ListFilterDefault.checkValid, - }, - [BlockEnum.Agent]: { - author: 'Dify', - about: '', - availablePrevNodes: [], - availableNextNodes: [], - getAvailablePrevNodes: ListFilterDefault.getAvailablePrevNodes, - getAvailableNextNodes: ListFilterDefault.getAvailableNextNodes, - checkValid: AgentDefault.checkValid, - }, -} - -export const NODES_INITIAL_DATA = { - [BlockEnum.Start]: { - type: BlockEnum.Start, - title: '', - desc: '', - ...StartNodeDefault.defaultValue, - }, - [BlockEnum.End]: { - type: BlockEnum.End, - title: '', - desc: '', - ...EndNodeDefault.defaultValue, - }, - [BlockEnum.Answer]: { - type: BlockEnum.Answer, - title: '', - desc: '', - ...AnswerDefault.defaultValue, - }, - [BlockEnum.LLM]: { - type: BlockEnum.LLM, - title: '', - desc: '', - variables: [], - ...LLMDefault.defaultValue, - }, - [BlockEnum.KnowledgeRetrieval]: { - type: BlockEnum.KnowledgeRetrieval, - title: '', - desc: '', - query_variable_selector: [], - dataset_ids: [], - retrieval_mode: 'single', - ...KnowledgeRetrievalDefault.defaultValue, - }, - [BlockEnum.IfElse]: { - type: BlockEnum.IfElse, - title: '', - desc: '', - ...IfElseDefault.defaultValue, - }, - [BlockEnum.Iteration]: { - type: BlockEnum.Iteration, - title: '', - desc: '', - ...IterationDefault.defaultValue, - }, - [BlockEnum.IterationStart]: { - type: BlockEnum.IterationStart, - title: '', - desc: '', - ...IterationStartDefault.defaultValue, - }, - [BlockEnum.Loop]: { - type: BlockEnum.Loop, - title: '', - desc: '', - ...LoopDefault.defaultValue, - }, - [BlockEnum.LoopStart]: { - type: BlockEnum.LoopStart, - title: '', - desc: '', - ...LoopStartDefault.defaultValue, - }, - [BlockEnum.LoopEnd]: { - type: BlockEnum.LoopEnd, - title: '', - desc: '', - ...LoopEndDefault.defaultValue, - }, - [BlockEnum.Code]: { - type: BlockEnum.Code, - title: '', - desc: '', - variables: [], - code_language: 'python3', - code: '', - outputs: [], - ...CodeDefault.defaultValue, - }, - [BlockEnum.TemplateTransform]: { - type: BlockEnum.TemplateTransform, - title: '', - desc: '', - variables: [], - template: '', - ...TemplateTransformDefault.defaultValue, - }, - [BlockEnum.QuestionClassifier]: { - type: BlockEnum.QuestionClassifier, - title: '', - desc: '', - query_variable_selector: [], - topics: [], - ...QuestionClassifierDefault.defaultValue, - }, - [BlockEnum.HttpRequest]: { - type: BlockEnum.HttpRequest, - title: '', - desc: '', - variables: [], - ...HttpRequestDefault.defaultValue, - }, - [BlockEnum.ParameterExtractor]: { - type: BlockEnum.ParameterExtractor, - title: '', - desc: '', - variables: [], - ...ParameterExtractorDefault.defaultValue, - }, - [BlockEnum.VariableAssigner]: { - type: BlockEnum.VariableAssigner, - title: '', - desc: '', - variables: [], - output_type: '', - ...VariableAssignerDefault.defaultValue, - }, - [BlockEnum.VariableAggregator]: { - type: BlockEnum.VariableAggregator, - title: '', - desc: '', - variables: [], - output_type: '', - ...VariableAssignerDefault.defaultValue, - }, - [BlockEnum.Assigner]: { - type: BlockEnum.Assigner, - title: '', - desc: '', - ...AssignerDefault.defaultValue, - }, - [BlockEnum.Tool]: { - type: BlockEnum.Tool, - title: '', - desc: '', - ...ToolDefault.defaultValue, - }, - [BlockEnum.DocExtractor]: { - type: BlockEnum.DocExtractor, - title: '', - desc: '', - ...DocExtractorDefault.defaultValue, - }, - [BlockEnum.ListFilter]: { - type: BlockEnum.ListFilter, - title: '', - desc: '', - ...ListFilterDefault.defaultValue, - }, - [BlockEnum.Agent]: { - type: BlockEnum.Agent, - title: '', - desc: '', - ...AgentDefault.defaultValue, - }, -} export const MAX_ITERATION_PARALLEL_NUM = 10 export const MIN_ITERATION_PARALLEL_NUM = 1 export const DEFAULT_ITER_TIMES = 1 diff --git a/web/app/components/workflow/constants/node.ts b/web/app/components/workflow/constants/node.ts new file mode 100644 index 0000000000..fee3a51f6d --- /dev/null +++ b/web/app/components/workflow/constants/node.ts @@ -0,0 +1,42 @@ +import llmDefault from '@/app/components/workflow/nodes/llm/default' +import knowledgeRetrievalDefault from '@/app/components/workflow/nodes/knowledge-retrieval/default' +import agentDefault from '@/app/components/workflow/nodes/agent/default' + +import questionClassifierDefault from '@/app/components/workflow/nodes/question-classifier/default' + +import ifElseDefault from '@/app/components/workflow/nodes/if-else/default' +import iterationDefault from '@/app/components/workflow/nodes/iteration/default' +import iterationStartDefault from '@/app/components/workflow/nodes/iteration-start/default' +import loopDefault from '@/app/components/workflow/nodes/loop/default' +import loopStartDefault from '@/app/components/workflow/nodes/loop-start/default' +import loopEndDefault from '@/app/components/workflow/nodes/loop-end/default' + +import codeDefault from '@/app/components/workflow/nodes/code/default' +import templateTransformDefault from '@/app/components/workflow/nodes/template-transform/default' +import variableAggregatorDefault from '@/app/components/workflow/nodes/variable-assigner/default' +import documentExtractorDefault from '@/app/components/workflow/nodes/document-extractor/default' +import assignerDefault from '@/app/components/workflow/nodes/assigner/default' +import httpRequestDefault from '@/app/components/workflow/nodes/http/default' +import parameterExtractorDefault from '@/app/components/workflow/nodes/parameter-extractor/default' +import listOperatorDefault from '@/app/components/workflow/nodes/list-operator/default' + +export const WORKFLOW_COMMON_NODES = [ + llmDefault, + knowledgeRetrievalDefault, + agentDefault, + questionClassifierDefault, + ifElseDefault, + iterationDefault, + iterationStartDefault, + loopDefault, + loopStartDefault, + loopEndDefault, + codeDefault, + templateTransformDefault, + variableAggregatorDefault, + documentExtractorDefault, + assignerDefault, + parameterExtractorDefault, + httpRequestDefault, + listOperatorDefault, +] diff --git a/web/app/components/workflow/custom-edge.tsx b/web/app/components/workflow/custom-edge.tsx index 4467b0adb5..fa04b46fea 100644 --- a/web/app/components/workflow/custom-edge.tsx +++ b/web/app/components/workflow/custom-edge.tsx @@ -56,8 +56,9 @@ const CustomEdge = ({ }) const [open, setOpen] = useState(false) const { handleNodeAdd } = useNodesInteractions() - const { availablePrevBlocks } = useAvailableBlocks((data as Edge['data'])!.targetType, (data as Edge['data'])?.isInIteration, (data as Edge['data'])?.isInLoop) - const { availableNextBlocks } = useAvailableBlocks((data as Edge['data'])!.sourceType, (data as Edge['data'])?.isInIteration, (data as Edge['data'])?.isInLoop) + const { availablePrevBlocks } = useAvailableBlocks((data as Edge['data'])!.targetType, (data as Edge['data'])?.isInIteration || (data as Edge['data'])?.isInLoop) + const { availableNextBlocks } = useAvailableBlocks((data as Edge['data'])!.sourceType, (data as Edge['data'])?.isInIteration || (data as Edge['data'])?.isInLoop) + console.log(availableNextBlocks, 'xx') const { _sourceRunningStatus, _targetRunningStatus, diff --git a/web/app/components/workflow/hooks-store/store.ts b/web/app/components/workflow/hooks-store/store.ts index 2e40cbfbc9..775d24a3ce 100644 --- a/web/app/components/workflow/hooks-store/store.ts +++ b/web/app/components/workflow/hooks-store/store.ts @@ -7,8 +7,16 @@ import { } from 'zustand' import { createStore } from 'zustand/vanilla' import { HooksStoreContext } from './provider' +import type { + BlockEnum, + NodeDefault, +} from '@/app/components/workflow/types' -type CommonHooksFnMap = { +export type AvailableNodesMetaData = { + nodes: NodeDefault[] + nodesMap?: Record> +} +export type CommonHooksFnMap = { doSyncWorkflowDraft: ( notRefreshWhenSyncError?: boolean, callback?: { @@ -26,6 +34,7 @@ type CommonHooksFnMap = { handleStartWorkflowRun: () => void handleWorkflowStartRunInWorkflow: () => void handleWorkflowStartRunInChatflow: () => void + availableNodesMetaData?: AvailableNodesMetaData } export type Shape = { @@ -43,6 +52,9 @@ export const createHooksStore = ({ handleStartWorkflowRun = noop, handleWorkflowStartRunInWorkflow = noop, handleWorkflowStartRunInChatflow = noop, + availableNodesMetaData = { + nodes: [], + }, }: Partial) => { return createStore(set => ({ refreshAll: props => set(state => ({ ...state, ...props })), @@ -56,6 +68,7 @@ export const createHooksStore = ({ handleStartWorkflowRun, handleWorkflowStartRunInWorkflow, handleWorkflowStartRunInChatflow, + availableNodesMetaData, })) } diff --git a/web/app/components/workflow/hooks/index.ts b/web/app/components/workflow/hooks/index.ts index 20a34c69e3..2b58e20db6 100644 --- a/web/app/components/workflow/hooks/index.ts +++ b/web/app/components/workflow/hooks/index.ts @@ -1,7 +1,6 @@ export * from './use-edges-interactions' export * from './use-node-data-update' export * from './use-nodes-interactions' -export * from './use-nodes-data' export * from './use-nodes-sync-draft' export * from './use-workflow' export * from './use-workflow-run' @@ -16,3 +15,5 @@ export * from './use-shortcuts' export * from './use-workflow-interactions' export * from './use-workflow-mode' export * from './use-format-time-from-now' +export * from './use-nodes-meta-data' +export * from './use-available-blocks' diff --git a/web/app/components/workflow/hooks/use-available-blocks.ts b/web/app/components/workflow/hooks/use-available-blocks.ts new file mode 100644 index 0000000000..aa15eb3b7e --- /dev/null +++ b/web/app/components/workflow/hooks/use-available-blocks.ts @@ -0,0 +1,58 @@ +import { + useCallback, + useMemo, +} from 'react' +import { BlockEnum } from '../types' +import { useNodesMetaData } from './use-nodes-meta-data' + +const availableBlocksFilter = (nodeType: BlockEnum, inContainer?: boolean) => { + if (inContainer && (nodeType === BlockEnum.Iteration || nodeType === BlockEnum.Loop || nodeType === BlockEnum.End)) + return false + + if (!inContainer && nodeType === BlockEnum.LoopEnd) + return false + + return true +} + +export const useAvailableBlocks = (nodeType?: BlockEnum, inContainer?: boolean) => { + const { + nodes: availableNodes, + } = useNodesMetaData() + const availableNodesType = useMemo(() => availableNodes.map(node => node.type), [availableNodes]) + const availablePrevBlocks = useMemo(() => { + if (!nodeType || nodeType === BlockEnum.Start) + return [] + + return availableNodesType + }, [availableNodesType, nodeType]) + const availableNextBlocks = useMemo(() => { + if (!nodeType || nodeType === BlockEnum.End || nodeType === BlockEnum.LoopEnd) + return [] + + return availableNodesType + }, [availableNodesType, nodeType]) + + const getAvailableBlocks = useCallback((nodeType?: BlockEnum, inContainer?: boolean) => { + let availablePrevBlocks = availableNodesType + if (!nodeType || nodeType === BlockEnum.Start) + availablePrevBlocks = [] + + let availableNextBlocks = availableNodesType + if (!nodeType || nodeType === BlockEnum.End || nodeType === BlockEnum.LoopEnd) + availableNextBlocks = [] + + return { + availablePrevBlocks: availablePrevBlocks.filter(nType => availableBlocksFilter(nType, inContainer)), + availableNextBlocks: availableNextBlocks.filter(nType => availableBlocksFilter(nType, inContainer)), + } + }, [availableNodesType]) + + return useMemo(() => { + return { + getAvailableBlocks, + availablePrevBlocks: availablePrevBlocks.filter(nType => availableBlocksFilter(nType, inContainer)), + availableNextBlocks: availableNextBlocks.filter(nType => availableBlocksFilter(nType, inContainer)), + } + }, [getAvailableBlocks, availablePrevBlocks, availableNextBlocks, inContainer]) +} diff --git a/web/app/components/workflow/hooks/use-checklist.ts b/web/app/components/workflow/hooks/use-checklist.ts index c1b0189b8b..9aebd935a5 100644 --- a/web/app/components/workflow/hooks/use-checklist.ts +++ b/web/app/components/workflow/hooks/use-checklist.ts @@ -22,7 +22,7 @@ import { } from '../constants' import type { ToolNodeType } from '../nodes/tool/types' import { useIsChatMode } from './use-workflow' -import { useNodesExtraData } from './use-nodes-data' +import { useNodesMetaData } from './use-nodes-meta-data' import { useToastContext } from '@/app/components/base/toast' import { CollectionType } from '@/app/components/tools/types' import { useGetLanguage } from '@/context/i18n' @@ -37,7 +37,7 @@ import { fetchDatasets } from '@/service/datasets' export const useChecklist = (nodes: Node[], edges: Edge[]) => { const { t } = useTranslation() const language = useGetLanguage() - const nodesExtraData = useNodesExtraData() + const { nodesMap: nodesExtraData } = useNodesMetaData() const isChatMode = useIsChatMode() const buildInTools = useStore(s => s.buildInTools) const customTools = useStore(s => s.customTools) @@ -100,7 +100,7 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => { if (node.type === CUSTOM_NODE) { const checkData = getCheckData(node.data) - const { errorMessage } = nodesExtraData[node.data.type].checkValid(checkData, t, moreDataForCheckValid) + const { errorMessage } = nodesExtraData![node.data.type].checkValid(checkData, t, moreDataForCheckValid) if (errorMessage || !validNodes.find(n => n.id === node.id)) { list.push({ @@ -148,7 +148,7 @@ export const useChecklistBeforePublish = () => { const { notify } = useToastContext() const isChatMode = useIsChatMode() const store = useStoreApi() - const nodesExtraData = useNodesExtraData() + const { nodesMap: nodesExtraData } = useNodesMetaData() const { data: strategyProviders } = useStrategyProviders() const updateDatasetsDetail = useDatasetsDetailStore(s => s.updateDatasetsDetail) const updateTime = useRef(0) @@ -228,7 +228,7 @@ export const useChecklistBeforePublish = () => { } const checkData = getCheckData(node.data, datasets) - const { errorMessage } = nodesExtraData[node.data.type as BlockEnum].checkValid(checkData, t, moreDataForCheckValid) + const { errorMessage } = nodesExtraData![node.data.type as BlockEnum].checkValid(checkData, t, moreDataForCheckValid) if (errorMessage) { notify({ type: 'error', message: `[${node.data.title}] ${errorMessage}` }) diff --git a/web/app/components/workflow/hooks/use-nodes-data.ts b/web/app/components/workflow/hooks/use-nodes-data.ts deleted file mode 100644 index aeb45ddb93..0000000000 --- a/web/app/components/workflow/hooks/use-nodes-data.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { useMemo } from 'react' -import { useTranslation } from 'react-i18next' -import produce from 'immer' -import { BlockEnum } from '../types' -import { - NODES_EXTRA_DATA, - NODES_INITIAL_DATA, -} from '../constants' -import { useIsChatMode } from './use-workflow' - -export const useNodesInitialData = () => { - const { t } = useTranslation() - - return useMemo(() => produce(NODES_INITIAL_DATA, (draft) => { - Object.keys(draft).forEach((key) => { - draft[key as BlockEnum].title = t(`workflow.blocks.${key}`) - }) - }), [t]) -} - -export const useNodesExtraData = () => { - const { t } = useTranslation() - const isChatMode = useIsChatMode() - - return useMemo(() => produce(NODES_EXTRA_DATA, (draft) => { - Object.keys(draft).forEach((key) => { - draft[key as BlockEnum].about = t(`workflow.blocksAbout.${key}`) - draft[key as BlockEnum].availablePrevNodes = draft[key as BlockEnum].getAvailablePrevNodes(isChatMode) - draft[key as BlockEnum].availableNextNodes = draft[key as BlockEnum].getAvailableNextNodes(isChatMode) - }) - }), [t, isChatMode]) -} - -export const useAvailableBlocks = (nodeType?: BlockEnum, isInIteration?: boolean, isInLoop?: boolean) => { - const nodesExtraData = useNodesExtraData() - const availablePrevBlocks = useMemo(() => { - if (!nodeType) - return [] - return nodesExtraData[nodeType].availablePrevNodes || [] - }, [nodeType, nodesExtraData]) - - const availableNextBlocks = useMemo(() => { - if (!nodeType) - return [] - - return nodesExtraData[nodeType].availableNextNodes || [] - }, [nodeType, nodesExtraData]) - - return useMemo(() => { - return { - availablePrevBlocks: availablePrevBlocks.filter((nType) => { - if (isInIteration && (nType === BlockEnum.Iteration || nType === BlockEnum.Loop || nType === BlockEnum.End)) - return false - - if (isInLoop && (nType === BlockEnum.Iteration || nType === BlockEnum.Loop || nType === BlockEnum.End)) - return false - - if (!isInLoop && nType === BlockEnum.LoopEnd) - return false - - return true - }), - availableNextBlocks: availableNextBlocks.filter((nType) => { - if (isInIteration && (nType === BlockEnum.Iteration || nType === BlockEnum.Loop || nType === BlockEnum.End)) - return false - - if (isInLoop && (nType === BlockEnum.Iteration || nType === BlockEnum.Loop || nType === BlockEnum.End)) - return false - - if (!isInLoop && nType === BlockEnum.LoopEnd) - return false - - return true - }), - } - }, [isInIteration, availablePrevBlocks, availableNextBlocks, isInLoop]) -} diff --git a/web/app/components/workflow/hooks/use-nodes-interactions.ts b/web/app/components/workflow/hooks/use-nodes-interactions.ts index 94b10c9929..b220d47a08 100644 --- a/web/app/components/workflow/hooks/use-nodes-interactions.ts +++ b/web/app/components/workflow/hooks/use-nodes-interactions.ts @@ -31,7 +31,6 @@ import { ITERATION_PADDING, LOOP_CHILDREN_Z_INDEX, LOOP_PADDING, - NODES_INITIAL_DATA, NODE_WIDTH_X_OFFSET, X_OFFSET, Y_OFFSET, @@ -60,6 +59,7 @@ import { useWorkflowReadOnly, } from './use-workflow' import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history' +import { useNodesMetaData } from './use-nodes-meta-data' export const useNodesInteractions = () => { const { t } = useTranslation() @@ -84,6 +84,7 @@ export const useNodesInteractions = () => { handleNodeLoopChildrenCopy, } = useNodeLoopInteractions() const dragNodeStartPosition = useRef({ x: 0, y: 0 } as { x: number; y: number }) + const { nodesMap: nodesMetaDataMap } = useNodesMetaData() const { saveStateToHistory, undo, redo } = useWorkflowHistory() @@ -682,6 +683,10 @@ export const useNodesInteractions = () => { } = store.getState() const nodes = getNodes() const nodesWithSameType = nodes.filter(node => node.data.type === nodeType) + const { + defaultValue, + title, + } = nodesMetaDataMap![nodeType] const { newNode, newIterationStartNode, @@ -689,8 +694,8 @@ export const useNodesInteractions = () => { } = generateNewNode({ type: getNodeCustomTypeByNodeDataType(nodeType), data: { - ...NODES_INITIAL_DATA[nodeType], - title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${nodeType}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${nodeType}`), + ...(defaultValue as any), + title: nodesWithSameType.length > 0 ? `${title} ${nodesWithSameType.length + 1}` : title, ...(toolDefaultValue || {}), selected: true, _showAddVariablePopup: (nodeType === BlockEnum.VariableAssigner || nodeType === BlockEnum.VariableAggregator) && !!prevNodeId, @@ -1093,7 +1098,7 @@ export const useNodesInteractions = () => { } handleSyncWorkflowDraft() saveStateToHistory(WorkflowHistoryEvent.NodeAdd) - }, [getNodesReadOnly, store, t, handleSyncWorkflowDraft, saveStateToHistory, workflowStore, getAfterNodesInSameBranch, checkNestedParallelLimit]) + }, [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory, workflowStore, getAfterNodesInSameBranch, checkNestedParallelLimit, nodesMetaDataMap]) const handleNodeChange = useCallback(( currentNodeId: string, @@ -1114,6 +1119,10 @@ export const useNodesInteractions = () => { const currentNode = nodes.find(node => node.id === currentNodeId)! const connectedEdges = getConnectedEdges([currentNode], edges) const nodesWithSameType = nodes.filter(node => node.data.type === nodeType) + const { + defaultValue, + title, + } = nodesMetaDataMap![nodeType] const { newNode: newCurrentNode, newIterationStartNode, @@ -1121,8 +1130,8 @@ export const useNodesInteractions = () => { } = generateNewNode({ type: getNodeCustomTypeByNodeDataType(nodeType), data: { - ...NODES_INITIAL_DATA[nodeType], - title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${nodeType}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${nodeType}`), + ...(defaultValue as any), + title: nodesWithSameType.length > 0 ? `${title} ${nodesWithSameType.length + 1}` : title, ...(toolDefaultValue || {}), _connectedSourceHandleIds: [], _connectedTargetHandleIds: [], @@ -1175,7 +1184,7 @@ export const useNodesInteractions = () => { handleSyncWorkflowDraft() saveStateToHistory(WorkflowHistoryEvent.NodeChange) - }, [getNodesReadOnly, store, t, handleSyncWorkflowDraft, saveStateToHistory]) + }, [getNodesReadOnly, store, handleSyncWorkflowDraft, saveStateToHistory, nodesMetaDataMap]) const handleNodesCancelSelected = useCallback(() => { const { @@ -1285,7 +1294,7 @@ export const useNodesInteractions = () => { } = generateNewNode({ type: nodeToPaste.type, data: { - ...NODES_INITIAL_DATA[nodeType], + ...nodesMetaDataMap![nodeType].defaultValue, ...nodeToPaste.data, selected: false, _isBundled: false, @@ -1361,7 +1370,7 @@ export const useNodesInteractions = () => { saveStateToHistory(WorkflowHistoryEvent.NodePaste) handleSyncWorkflowDraft() } - }, [getNodesReadOnly, workflowStore, store, reactflow, saveStateToHistory, handleSyncWorkflowDraft, handleNodeIterationChildrenCopy, handleNodeLoopChildrenCopy]) + }, [getNodesReadOnly, workflowStore, store, reactflow, saveStateToHistory, handleSyncWorkflowDraft, handleNodeIterationChildrenCopy, handleNodeLoopChildrenCopy, nodesMetaDataMap]) const handleNodesDuplicate = useCallback((nodeId?: string) => { if (getNodesReadOnly()) diff --git a/web/app/components/workflow/hooks/use-nodes-meta-data.ts b/web/app/components/workflow/hooks/use-nodes-meta-data.ts new file mode 100644 index 0000000000..1e140ffaf1 --- /dev/null +++ b/web/app/components/workflow/hooks/use-nodes-meta-data.ts @@ -0,0 +1,14 @@ +import { useMemo } from 'react' +import type { AvailableNodesMetaData } from '@/app/components/workflow/hooks-store' +import { useHooksStore } from '@/app/components/workflow/hooks-store' + +export const useNodesMetaData = () => { + const availableNodesMetaData = useHooksStore(s => s.availableNodesMetaData) + + return useMemo(() => { + return { + nodes: availableNodesMetaData?.nodes || [], + nodesMap: availableNodesMetaData?.nodesMap || {}, + } as AvailableNodesMetaData + }, [availableNodesMetaData]) +} diff --git a/web/app/components/workflow/hooks/use-workflow.ts b/web/app/components/workflow/hooks/use-workflow.ts index 99dce4dc15..9889c42cff 100644 --- a/web/app/components/workflow/hooks/use-workflow.ts +++ b/web/app/components/workflow/hooks/use-workflow.ts @@ -35,7 +35,7 @@ import { } from '../constants' import { CUSTOM_NOTE_NODE } from '../note-node/constants' import { findUsedVarNodes, getNodeOutputVars, updateNodeVars } from '../nodes/_base/components/variable/utils' -import { useNodesExtraData } from './use-nodes-data' +import { useAvailableBlocks } from './use-available-blocks' import { useStore as useAppStore } from '@/app/components/app/store' import { fetchAllBuiltInTools, @@ -58,7 +58,7 @@ export const useWorkflow = () => { const { t } = useTranslation() const store = useStoreApi() const workflowStore = useWorkflowStore() - const nodesExtraData = useNodesExtraData() + const { getAvailableBlocks } = useAvailableBlocks() const setPanelWidth = useCallback((width: number) => { localStorage.setItem('workflow-node-panel-width', `${width}`) workflowStore.setState({ panelWidth: width }) @@ -364,8 +364,8 @@ export const useWorkflow = () => { return false if (sourceNode && targetNode) { - const sourceNodeAvailableNextNodes = nodesExtraData[sourceNode.data.type].availableNextNodes - const targetNodeAvailablePrevNodes = [...nodesExtraData[targetNode.data.type].availablePrevNodes, BlockEnum.Start] + const sourceNodeAvailableNextNodes = getAvailableBlocks(sourceNode.data.type, !!sourceNode.parentId).availableNextBlocks + const targetNodeAvailablePrevNodes = getAvailableBlocks(targetNode.data.type, !!targetNode.parentId).availablePrevBlocks if (!sourceNodeAvailableNextNodes.includes(targetNode.data.type)) return false @@ -389,7 +389,7 @@ export const useWorkflow = () => { } return !hasCycle(targetNode) - }, [store, nodesExtraData, checkParallelLimit]) + }, [store, checkParallelLimit, getAvailableBlocks]) const getNode = useCallback((nodeId?: string) => { const { getNodes } = store.getState() diff --git a/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip.tsx b/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip.tsx index b4448855c7..dc98c4003d 100644 --- a/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip.tsx +++ b/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip.tsx @@ -17,7 +17,7 @@ const ErrorHandleTip = ({ if (type === ErrorHandleTypeEnum.defaultValue) return t('workflow.nodes.common.errorHandle.defaultValue.inLog') - }, []) + }, [t, type]) if (!type) return null diff --git a/web/app/components/workflow/nodes/_base/components/next-step/add.tsx b/web/app/components/workflow/nodes/_base/components/next-step/add.tsx index de5fbacfa5..474ddc78d5 100644 --- a/web/app/components/workflow/nodes/_base/components/next-step/add.tsx +++ b/web/app/components/workflow/nodes/_base/components/next-step/add.tsx @@ -38,7 +38,7 @@ const Add = ({ const [open, setOpen] = useState(false) const { handleNodeAdd } = useNodesInteractions() const { nodesReadOnly } = useNodesReadOnly() - const { availableNextBlocks } = useAvailableBlocks(nodeData.type, nodeData.isInIteration, nodeData.isInLoop) + const { availableNextBlocks } = useAvailableBlocks(nodeData.type, nodeData.isInIteration || nodeData.isInLoop) const { checkParallelLimit } = useWorkflow() const handleSelect = useCallback((type, toolDefaultValue) => { diff --git a/web/app/components/workflow/nodes/_base/components/next-step/operator.tsx b/web/app/components/workflow/nodes/_base/components/next-step/operator.tsx index 565db02602..c54a63d8ad 100644 --- a/web/app/components/workflow/nodes/_base/components/next-step/operator.tsx +++ b/web/app/components/workflow/nodes/_base/components/next-step/operator.tsx @@ -36,7 +36,7 @@ const ChangeItem = ({ const { availablePrevBlocks, availableNextBlocks, - } = useAvailableBlocks(data.type, data.isInIteration, data.isInLoop) + } = useAvailableBlocks(data.type, data.isInIteration || data.isInLoop) const handleSelect = useCallback((type, toolDefaultValue) => { handleNodeChange(nodeId, type, sourceHandle, toolDefaultValue) diff --git a/web/app/components/workflow/nodes/_base/components/node-handle.tsx b/web/app/components/workflow/nodes/_base/components/node-handle.tsx index 0dc099f5a0..351fe109a4 100644 --- a/web/app/components/workflow/nodes/_base/components/node-handle.tsx +++ b/web/app/components/workflow/nodes/_base/components/node-handle.tsx @@ -47,7 +47,7 @@ export const NodeTargetHandle = memo(({ const { handleNodeAdd } = useNodesInteractions() const { getNodesReadOnly } = useNodesReadOnly() const connected = data._connectedTargetHandleIds?.includes(handleId) - const { availablePrevBlocks } = useAvailableBlocks(data.type, data.isInIteration, data.isInLoop) + const { availablePrevBlocks } = useAvailableBlocks(data.type, data.isInIteration || data.isInLoop) const isConnectable = !!availablePrevBlocks.length const handleOpenChange = useCallback((v: boolean) => { @@ -129,7 +129,7 @@ export const NodeSourceHandle = memo(({ const [open, setOpen] = useState(false) const { handleNodeAdd } = useNodesInteractions() const { getNodesReadOnly } = useNodesReadOnly() - const { availableNextBlocks } = useAvailableBlocks(data.type, data.isInIteration, data.isInLoop) + const { availableNextBlocks } = useAvailableBlocks(data.type, data.isInIteration || data.isInLoop) const isConnectable = !!availableNextBlocks.length const isChatMode = useIsChatMode() const { checkParallelLimit } = useWorkflow() diff --git a/web/app/components/workflow/nodes/_base/components/panel-operator/change-block.tsx b/web/app/components/workflow/nodes/_base/components/panel-operator/change-block.tsx index 90f9dae894..d7b2188ed5 100644 --- a/web/app/components/workflow/nodes/_base/components/panel-operator/change-block.tsx +++ b/web/app/components/workflow/nodes/_base/components/panel-operator/change-block.tsx @@ -30,7 +30,7 @@ const ChangeBlock = ({ const { availablePrevBlocks, availableNextBlocks, - } = useAvailableBlocks(nodeData.type, nodeData.isInIteration, nodeData.isInLoop) + } = useAvailableBlocks(nodeData.type, nodeData.isInIteration || nodeData.isInLoop) const availableNodes = useMemo(() => { if (availablePrevBlocks.length && availableNextBlocks.length) diff --git a/web/app/components/workflow/nodes/_base/components/panel-operator/panel-operator-popup.tsx b/web/app/components/workflow/nodes/_base/components/panel-operator/panel-operator-popup.tsx index 28d7358ddb..ad0f6c636f 100644 --- a/web/app/components/workflow/nodes/_base/components/panel-operator/panel-operator-popup.tsx +++ b/web/app/components/workflow/nodes/_base/components/panel-operator/panel-operator-popup.tsx @@ -12,8 +12,8 @@ import { import { useStore } from '@/app/components/workflow/store' import { useNodeDataUpdate, - useNodesExtraData, useNodesInteractions, + useNodesMetaData, useNodesReadOnly, useNodesSyncDraft, } from '@/app/components/workflow/hooks' @@ -48,14 +48,14 @@ const PanelOperatorPopup = ({ const { handleNodeDataUpdate } = useNodeDataUpdate() const { handleSyncWorkflowDraft } = useNodesSyncDraft() const { nodesReadOnly } = useNodesReadOnly() - const nodesExtraData = useNodesExtraData() + const { nodesMap: nodesExtraData } = useNodesMetaData() const buildInTools = useStore(s => s.buildInTools) const customTools = useStore(s => s.customTools) const workflowTools = useStore(s => s.workflowTools) const edge = edges.find(edge => edge.target === id) const author = useMemo(() => { if (data.type !== BlockEnum.Tool) - return nodesExtraData[data.type].author + return nodesExtraData![data.type].author if (data.provider_type === CollectionType.builtIn) return buildInTools.find(toolWithProvider => canFindTool(toolWithProvider.id, data.provider_id))?.author @@ -68,7 +68,7 @@ const PanelOperatorPopup = ({ const about = useMemo(() => { if (data.type !== BlockEnum.Tool) - return nodesExtraData[data.type].about + return nodesExtraData![data.type].description if (data.provider_type === CollectionType.builtIn) return buildInTools.find(toolWithProvider => canFindTool(toolWithProvider.id, data.provider_id))?.description[language] diff --git a/web/app/components/workflow/nodes/_base/hooks/use-node-help-link.ts b/web/app/components/workflow/nodes/_base/hooks/use-node-help-link.ts index 3c68fbd1fd..804d031a8e 100644 --- a/web/app/components/workflow/nodes/_base/hooks/use-node-help-link.ts +++ b/web/app/components/workflow/nodes/_base/hooks/use-node-help-link.ts @@ -1,69 +1,15 @@ import { useMemo } from 'react' -import { useGetLanguage } from '@/context/i18n' -import { BlockEnum } from '@/app/components/workflow/types' +import type { BlockEnum } from '@/app/components/workflow/types' +import { useNodesMetaData } from '@/app/components/workflow/hooks' export const useNodeHelpLink = (nodeType: BlockEnum) => { - const language = useGetLanguage() - const prefixLink = useMemo(() => { - if (language === 'zh_Hans') - return 'https://docs.dify.ai/zh-hans/guides/workflow/node/' + const availableNodesMetaData = useNodesMetaData() - return 'https://docs.dify.ai/guides/workflow/node/' - }, [language]) - const linkMap = useMemo(() => { - if (language === 'zh_Hans') { - return { - [BlockEnum.Start]: 'start', - [BlockEnum.End]: 'end', - [BlockEnum.Answer]: 'answer', - [BlockEnum.LLM]: 'llm', - [BlockEnum.KnowledgeRetrieval]: 'knowledge-retrieval', - [BlockEnum.QuestionClassifier]: 'question-classifier', - [BlockEnum.IfElse]: 'ifelse', - [BlockEnum.Code]: 'code', - [BlockEnum.TemplateTransform]: 'template', - [BlockEnum.VariableAssigner]: 'variable-assigner', - [BlockEnum.VariableAggregator]: 'variable-aggregator', - [BlockEnum.Assigner]: 'variable-assigner', - [BlockEnum.Iteration]: 'iteration', - [BlockEnum.Loop]: 'loop', - [BlockEnum.ParameterExtractor]: 'parameter-extractor', - [BlockEnum.HttpRequest]: 'http-request', - [BlockEnum.Tool]: 'tools', - [BlockEnum.DocExtractor]: 'doc-extractor', - [BlockEnum.ListFilter]: 'list-operator', - [BlockEnum.Agent]: 'agent', - } - } + const link = useMemo(() => { + const result = availableNodesMetaData?.nodesMap?.[nodeType]?.helpLinkUri || '' - return { - [BlockEnum.Start]: 'start', - [BlockEnum.End]: 'end', - [BlockEnum.Answer]: 'answer', - [BlockEnum.LLM]: 'llm', - [BlockEnum.KnowledgeRetrieval]: 'knowledge-retrieval', - [BlockEnum.QuestionClassifier]: 'question-classifier', - [BlockEnum.IfElse]: 'ifelse', - [BlockEnum.Code]: 'code', - [BlockEnum.TemplateTransform]: 'template', - [BlockEnum.VariableAssigner]: 'variable-assigner', - [BlockEnum.VariableAggregator]: 'variable-aggregator', - [BlockEnum.Assigner]: 'variable-assigner', - [BlockEnum.Iteration]: 'iteration', - [BlockEnum.Loop]: 'loop', - [BlockEnum.ParameterExtractor]: 'parameter-extractor', - [BlockEnum.HttpRequest]: 'http-request', - [BlockEnum.Tool]: 'tools', - [BlockEnum.DocExtractor]: 'doc-extractor', - [BlockEnum.ListFilter]: 'list-operator', - [BlockEnum.Agent]: 'agent', - } - }, [language]) as Record + return result + }, [availableNodesMetaData, nodeType]) - const link = linkMap[nodeType] - - if (!link) - return '' - - return `${prefixLink}${link}` + return link } diff --git a/web/app/components/workflow/nodes/_base/panel.tsx b/web/app/components/workflow/nodes/_base/panel.tsx index 2ee39a3b06..e109fafcb5 100644 --- a/web/app/components/workflow/nodes/_base/panel.tsx +++ b/web/app/components/workflow/nodes/_base/panel.tsx @@ -68,7 +68,7 @@ const BasePanel: FC = ({ const { handleNodeSelect } = useNodesInteractions() const { handleSyncWorkflowDraft } = useNodesSyncDraft() const { nodesReadOnly } = useNodesReadOnly() - const { availableNextBlocks } = useAvailableBlocks(data.type, data.isInIteration, data.isInLoop) + const { availableNextBlocks } = useAvailableBlocks(data.type, data.isInIteration || data.isInLoop) const toolIcon = useToolIcon(data) const handleResize = useCallback((width: number) => { diff --git a/web/app/components/workflow/nodes/agent/default.ts b/web/app/components/workflow/nodes/agent/default.ts index 4e7b447de8..aed97fb7c3 100644 --- a/web/app/components/workflow/nodes/agent/default.ts +++ b/web/app/components/workflow/nodes/agent/default.ts @@ -1,23 +1,18 @@ import type { StrategyDetail, StrategyPluginDetail } from '@/app/components/plugins/types' -import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' import type { NodeDefault } from '../../types' import type { AgentNodeType } from './types' import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { renderI18nObject } from '@/i18n' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum } from '@/app/components/workflow/types' const nodeDefault: NodeDefault = { + ...genNodeMetaData({ + sort: 3, + type: BlockEnum.Agent, + }), defaultValue: { }, - getAvailablePrevNodes(isChatMode) { - return isChatMode - ? ALL_CHAT_AVAILABLE_BLOCKS - : ALL_COMPLETION_AVAILABLE_BLOCKS - }, - getAvailableNextNodes(isChatMode) { - return isChatMode - ? ALL_CHAT_AVAILABLE_BLOCKS - : ALL_COMPLETION_AVAILABLE_BLOCKS - }, checkValid(payload, t, moreDataForCheckValid: { strategyProvider?: StrategyPluginDetail, strategy?: StrategyDetail diff --git a/web/app/components/workflow/nodes/answer/default.ts b/web/app/components/workflow/nodes/answer/default.ts index 4ff6e49d7e..a1f0b47c5a 100644 --- a/web/app/components/workflow/nodes/answer/default.ts +++ b/web/app/components/workflow/nodes/answer/default.ts @@ -1,25 +1,17 @@ -import { BlockEnum } from '../../types' import type { NodeDefault } from '../../types' import type { AnswerNodeType } from './types' -import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum } from '@/app/components/workflow/types' const nodeDefault: NodeDefault = { + ...genNodeMetaData({ + sort: 2.1, + type: BlockEnum.Answer, + }), defaultValue: { variables: [], answer: '', }, - getAvailablePrevNodes(isChatMode: boolean) { - const nodes = isChatMode - ? ALL_CHAT_AVAILABLE_BLOCKS - : ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End) - return nodes - }, - getAvailableNextNodes(isChatMode: boolean) { - const nodes = isChatMode - ? ALL_CHAT_AVAILABLE_BLOCKS - : ALL_COMPLETION_AVAILABLE_BLOCKS - return nodes - }, checkValid(payload: AnswerNodeType, t: any) { let errorMessages = '' const { answer } = payload diff --git a/web/app/components/workflow/nodes/assigner/default.ts b/web/app/components/workflow/nodes/assigner/default.ts index f443ae1d3b..1a43d15c49 100644 --- a/web/app/components/workflow/nodes/assigner/default.ts +++ b/web/app/components/workflow/nodes/assigner/default.ts @@ -1,24 +1,21 @@ -import { BlockEnum } from '../../types' import type { NodeDefault } from '../../types' import { type AssignerNodeType, WriteMode } from './types' -import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum } from '@/app/components/workflow/types' +import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' const i18nPrefix = 'workflow.errorMsg' const nodeDefault: NodeDefault = { + ...genNodeMetaData({ + classification: BlockClassificationEnum.Transform, + sort: 5, + type: BlockEnum.Assigner, + helpLinkUri: 'variable-assigner', + }), defaultValue: { version: '2', items: [], }, - getAvailablePrevNodes(isChatMode: boolean) { - const nodes = isChatMode - ? ALL_CHAT_AVAILABLE_BLOCKS - : ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End) - return nodes - }, - getAvailableNextNodes(isChatMode: boolean) { - const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS - return nodes - }, checkValid(payload: AssignerNodeType, t: any) { let errorMessages = '' const { diff --git a/web/app/components/workflow/nodes/code/default.ts b/web/app/components/workflow/nodes/code/default.ts index 5f90c18716..c66215fa99 100644 --- a/web/app/components/workflow/nodes/code/default.ts +++ b/web/app/components/workflow/nodes/code/default.ts @@ -1,27 +1,23 @@ -import { BlockEnum } from '../../types' import type { NodeDefault } from '../../types' import { CodeLanguage, type CodeNodeType } from './types' -import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum } from '@/app/components/workflow/types' +import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' const i18nPrefix = 'workflow.errorMsg' const nodeDefault: NodeDefault = { + ...genNodeMetaData({ + classification: BlockClassificationEnum.Transform, + sort: 1, + type: BlockEnum.Code, + }), defaultValue: { code: '', code_language: CodeLanguage.python3, variables: [], outputs: {}, }, - getAvailablePrevNodes(isChatMode: boolean) { - const nodes = isChatMode - ? ALL_CHAT_AVAILABLE_BLOCKS - : ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End) - return nodes - }, - getAvailableNextNodes(isChatMode: boolean) { - const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS - return nodes - }, checkValid(payload: CodeNodeType, t: any) { let errorMessages = '' const { code, variables } = payload diff --git a/web/app/components/workflow/nodes/document-extractor/default.ts b/web/app/components/workflow/nodes/document-extractor/default.ts index e141844d19..caeb37656a 100644 --- a/web/app/components/workflow/nodes/document-extractor/default.ts +++ b/web/app/components/workflow/nodes/document-extractor/default.ts @@ -1,24 +1,21 @@ -import { BlockEnum } from '../../types' import type { NodeDefault } from '../../types' import type { DocExtractorNodeType } from './types' -import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum } from '@/app/components/workflow/types' +import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' const i18nPrefix = 'workflow.errorMsg' const nodeDefault: NodeDefault = { + ...genNodeMetaData({ + classification: BlockClassificationEnum.Transform, + sort: 4, + type: BlockEnum.DocExtractor, + helpLinkUri: 'doc-extractor', + }), defaultValue: { variable_selector: [], is_array_file: false, }, - getAvailablePrevNodes(isChatMode: boolean) { - const nodes = isChatMode - ? ALL_CHAT_AVAILABLE_BLOCKS - : ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End) - return nodes - }, - getAvailableNextNodes(isChatMode: boolean) { - const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS - return nodes - }, checkValid(payload: DocExtractorNodeType, t: any) { let errorMessages = '' const { variable_selector: variable } = payload diff --git a/web/app/components/workflow/nodes/end/default.ts b/web/app/components/workflow/nodes/end/default.ts index e8fbb94bd0..e61d84d648 100644 --- a/web/app/components/workflow/nodes/end/default.ts +++ b/web/app/components/workflow/nodes/end/default.ts @@ -1,21 +1,16 @@ -import { BlockEnum } from '../../types' import type { NodeDefault } from '../../types' import type { EndNodeType } from './types' -import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum } from '@/app/components/workflow/types' const nodeDefault: NodeDefault = { + ...genNodeMetaData({ + sort: 2.1, + type: BlockEnum.End, + }), defaultValue: { outputs: [], }, - getAvailablePrevNodes(isChatMode: boolean) { - const nodes = isChatMode - ? ALL_CHAT_AVAILABLE_BLOCKS - : ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End) - return nodes - }, - getAvailableNextNodes() { - return [] - }, checkValid() { return { isValid: true, diff --git a/web/app/components/workflow/nodes/http/default.ts b/web/app/components/workflow/nodes/http/default.ts index 1bd584eeb9..b5dca4549e 100644 --- a/web/app/components/workflow/nodes/http/default.ts +++ b/web/app/components/workflow/nodes/http/default.ts @@ -1,13 +1,16 @@ -import { BlockEnum } from '../../types' import type { NodeDefault } from '../../types' import { AuthorizationType, BodyType, Method } from './types' import type { BodyPayload, HttpNodeType } from './types' -import { - ALL_CHAT_AVAILABLE_BLOCKS, - ALL_COMPLETION_AVAILABLE_BLOCKS, -} from '@/app/components/workflow/blocks' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum } from '@/app/components/workflow/types' +import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' const nodeDefault: NodeDefault = { + ...genNodeMetaData({ + classification: BlockClassificationEnum.Utilities, + sort: 1, + type: BlockEnum.HttpRequest, + }), defaultValue: { variables: [], method: Method.get, @@ -33,16 +36,6 @@ const nodeDefault: NodeDefault = { retry_interval: 100, }, }, - getAvailablePrevNodes(isChatMode: boolean) { - const nodes = isChatMode - ? ALL_CHAT_AVAILABLE_BLOCKS - : ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End) - return nodes - }, - getAvailableNextNodes(isChatMode: boolean) { - const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS - return nodes - }, checkValid(payload: HttpNodeType, t: any) { let errorMessages = '' diff --git a/web/app/components/workflow/nodes/if-else/default.ts b/web/app/components/workflow/nodes/if-else/default.ts index 1be80592e5..1f82c0fe29 100644 --- a/web/app/components/workflow/nodes/if-else/default.ts +++ b/web/app/components/workflow/nodes/if-else/default.ts @@ -1,10 +1,18 @@ -import { BlockEnum, type NodeDefault } from '../../types' +import type { NodeDefault } from '../../types' import { type IfElseNodeType, LogicalOperator } from './types' import { isEmptyRelatedOperator } from './utils' -import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum } from '@/app/components/workflow/types' +import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' const i18nPrefix = 'workflow.errorMsg' const nodeDefault: NodeDefault = { + ...genNodeMetaData({ + classification: BlockClassificationEnum.Logic, + sort: 1, + type: BlockEnum.IfElse, + helpLinkUri: 'ifelse', + }), defaultValue: { _targetBranches: [ { @@ -24,16 +32,6 @@ const nodeDefault: NodeDefault = { }, ], }, - getAvailablePrevNodes(isChatMode: boolean) { - const nodes = isChatMode - ? ALL_CHAT_AVAILABLE_BLOCKS - : ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End) - return nodes - }, - getAvailableNextNodes(isChatMode: boolean) { - const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS - return nodes - }, checkValid(payload: IfElseNodeType, t: any) { let errorMessages = '' const { cases } = payload diff --git a/web/app/components/workflow/nodes/iteration-start/default.ts b/web/app/components/workflow/nodes/iteration-start/default.ts index c93b472259..3bd42894f1 100644 --- a/web/app/components/workflow/nodes/iteration-start/default.ts +++ b/web/app/components/workflow/nodes/iteration-start/default.ts @@ -1,16 +1,14 @@ import type { NodeDefault } from '../../types' import type { IterationStartNodeType } from './types' -import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum } from '@/app/components/workflow/types' const nodeDefault: NodeDefault = { + ...genNodeMetaData({ + sort: -1, + type: BlockEnum.IterationStart, + }), defaultValue: {}, - getAvailablePrevNodes() { - return [] - }, - getAvailableNextNodes(isChatMode: boolean) { - const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS - return nodes - }, checkValid() { return { isValid: true, diff --git a/web/app/components/workflow/nodes/iteration/default.ts b/web/app/components/workflow/nodes/iteration/default.ts index 0ef8382abe..3cc51dd340 100644 --- a/web/app/components/workflow/nodes/iteration/default.ts +++ b/web/app/components/workflow/nodes/iteration/default.ts @@ -1,13 +1,17 @@ -import { BlockEnum, ErrorHandleMode } from '../../types' +import { ErrorHandleMode } from '../../types' import type { NodeDefault } from '../../types' import type { IterationNodeType } from './types' -import { - ALL_CHAT_AVAILABLE_BLOCKS, - ALL_COMPLETION_AVAILABLE_BLOCKS, -} from '@/app/components/workflow/blocks' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum } from '@/app/components/workflow/types' +import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' const i18nPrefix = 'workflow' const nodeDefault: NodeDefault = { + ...genNodeMetaData({ + classification: BlockClassificationEnum.Logic, + sort: 2, + type: BlockEnum.Iteration, + }), defaultValue: { start_node_id: '', iterator_selector: [], @@ -18,20 +22,6 @@ const nodeDefault: NodeDefault = { parallel_nums: 10, error_handle_mode: ErrorHandleMode.Terminated, }, - getAvailablePrevNodes(isChatMode: boolean) { - const nodes = isChatMode - ? ALL_CHAT_AVAILABLE_BLOCKS - : ALL_COMPLETION_AVAILABLE_BLOCKS.filter( - type => type !== BlockEnum.End, - ) - return nodes - }, - getAvailableNextNodes(isChatMode: boolean) { - const nodes = isChatMode - ? ALL_CHAT_AVAILABLE_BLOCKS - : ALL_COMPLETION_AVAILABLE_BLOCKS - return nodes - }, checkValid(payload: IterationNodeType, t: any) { let errorMessages = '' diff --git a/web/app/components/workflow/nodes/iteration/use-interactions.ts b/web/app/components/workflow/nodes/iteration/use-interactions.ts index c294cfd6aa..b4e0540b2c 100644 --- a/web/app/components/workflow/nodes/iteration/use-interactions.ts +++ b/web/app/components/workflow/nodes/iteration/use-interactions.ts @@ -12,13 +12,14 @@ import { } from '../../utils' import { ITERATION_PADDING, - NODES_INITIAL_DATA, } from '../../constants' import { CUSTOM_ITERATION_START_NODE } from '../iteration-start/constants' +import { useNodesMetaData } from '@/app/components/workflow/hooks' export const useNodeIterationInteractions = () => { const { t } = useTranslation() const store = useStoreApi() + const { nodesMap: nodesMetaDataMap } = useNodesMetaData() const handleNodeIterationRerender = useCallback((nodeId: string) => { const { @@ -120,7 +121,7 @@ export const useNodeIterationInteractions = () => { const { newNode } = generateNewNode({ type: getNodeCustomTypeByNodeDataType(childNodeType), data: { - ...NODES_INITIAL_DATA[childNodeType], + ...nodesMetaDataMap![childNodeType].defaultValue, ...child.data, selected: false, _isBundled: false, diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/default.ts b/web/app/components/workflow/nodes/knowledge-retrieval/default.ts index 09da8dd789..bbb2ed6387 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/default.ts +++ b/web/app/components/workflow/nodes/knowledge-retrieval/default.ts @@ -1,13 +1,17 @@ -import { BlockEnum } from '../../types' import type { NodeDefault } from '../../types' import type { KnowledgeRetrievalNodeType } from './types' import { checkoutRerankModelConfigedInRetrievalSettings } from './utils' -import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' import { DATASET_DEFAULT } from '@/config' import { RETRIEVE_TYPE } from '@/types/app' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum } from '@/app/components/workflow/types' const i18nPrefix = 'workflow' const nodeDefault: NodeDefault = { + ...genNodeMetaData({ + sort: 2, + type: BlockEnum.KnowledgeRetrieval, + }), defaultValue: { query_variable_selector: [], dataset_ids: [], @@ -18,16 +22,6 @@ const nodeDefault: NodeDefault = { reranking_enable: false, }, }, - getAvailablePrevNodes(isChatMode: boolean) { - const nodes = isChatMode - ? ALL_CHAT_AVAILABLE_BLOCKS - : ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End) - return nodes - }, - getAvailableNextNodes(isChatMode: boolean) { - const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS - return nodes - }, checkValid(payload: KnowledgeRetrievalNodeType, t: any) { let errorMessages = '' if (!errorMessages && (!payload.query_variable_selector || payload.query_variable_selector.length === 0)) diff --git a/web/app/components/workflow/nodes/list-operator/default.ts b/web/app/components/workflow/nodes/list-operator/default.ts index 0256cb8673..a9898ac2a8 100644 --- a/web/app/components/workflow/nodes/list-operator/default.ts +++ b/web/app/components/workflow/nodes/list-operator/default.ts @@ -1,11 +1,18 @@ -import { BlockEnum, VarType } from '../../types' +import { VarType } from '../../types' import type { NodeDefault } from '../../types' import { comparisonOperatorNotRequireValue } from '../if-else/utils' import { type ListFilterNodeType, OrderBy } from './types' -import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum } from '@/app/components/workflow/types' +import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' const i18nPrefix = 'workflow.errorMsg' const nodeDefault: NodeDefault = { + ...genNodeMetaData({ + classification: BlockClassificationEnum.Utilities, + sort: 2, + type: BlockEnum.ListFilter, + }), defaultValue: { variable: [], filter_by: { @@ -26,16 +33,6 @@ const nodeDefault: NodeDefault = { size: 10, }, }, - getAvailablePrevNodes(isChatMode: boolean) { - const nodes = isChatMode - ? ALL_CHAT_AVAILABLE_BLOCKS - : ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End) - return nodes - }, - getAvailableNextNodes(isChatMode: boolean) { - const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS - return nodes - }, checkValid(payload: ListFilterNodeType, t: any) { let errorMessages = '' const { variable, var_type, filter_by } = payload diff --git a/web/app/components/workflow/nodes/llm/default.ts b/web/app/components/workflow/nodes/llm/default.ts index 92377f74b8..34b6085c9d 100644 --- a/web/app/components/workflow/nodes/llm/default.ts +++ b/web/app/components/workflow/nodes/llm/default.ts @@ -1,11 +1,16 @@ -import { BlockEnum, EditionType } from '../../types' +import { EditionType } from '../../types' import { type NodeDefault, type PromptItem, PromptRole } from '../../types' import type { LLMNodeType } from './types' -import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum } from '@/app/components/workflow/types' const i18nPrefix = 'workflow.errorMsg' const nodeDefault: NodeDefault = { + ...genNodeMetaData({ + sort: 1, + type: BlockEnum.LLM, + }), defaultValue: { model: { provider: '', @@ -27,16 +32,6 @@ const nodeDefault: NodeDefault = { enabled: false, }, }, - getAvailablePrevNodes(isChatMode: boolean) { - const nodes = isChatMode - ? ALL_CHAT_AVAILABLE_BLOCKS - : ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End) - return nodes - }, - getAvailableNextNodes(isChatMode: boolean) { - const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS - return nodes - }, checkValid(payload: LLMNodeType, t: any) { let errorMessages = '' if (!errorMessages && !payload.model.provider) diff --git a/web/app/components/workflow/nodes/loop-end/default.ts b/web/app/components/workflow/nodes/loop-end/default.ts index c136704123..7e314ab277 100644 --- a/web/app/components/workflow/nodes/loop-end/default.ts +++ b/web/app/components/workflow/nodes/loop-end/default.ts @@ -1,18 +1,18 @@ import type { NodeDefault } from '../../types' -import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' import type { SimpleNodeType, } from '@/app/components/workflow/simple-node/types' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum } from '@/app/components/workflow/types' +import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' const nodeDefault: NodeDefault = { + ...genNodeMetaData({ + classification: BlockClassificationEnum.Logic, + sort: 2, + type: BlockEnum.LoopEnd, + }), defaultValue: {}, - getAvailablePrevNodes(isChatMode: boolean) { - const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS - return nodes - }, - getAvailableNextNodes() { - return [] - }, checkValid() { return { isValid: true, diff --git a/web/app/components/workflow/nodes/loop-start/default.ts b/web/app/components/workflow/nodes/loop-start/default.ts index 685f227838..17f1669111 100644 --- a/web/app/components/workflow/nodes/loop-start/default.ts +++ b/web/app/components/workflow/nodes/loop-start/default.ts @@ -1,16 +1,14 @@ import type { NodeDefault } from '../../types' import type { LoopStartNodeType } from './types' -import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum } from '@/app/components/workflow/types' const nodeDefault: NodeDefault = { + ...genNodeMetaData({ + sort: -1, + type: BlockEnum.LoopStart, + }), defaultValue: {}, - getAvailablePrevNodes() { - return [] - }, - getAvailableNextNodes(isChatMode: boolean) { - const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS - return nodes - }, checkValid() { return { isValid: true, diff --git a/web/app/components/workflow/nodes/loop/add-block.tsx b/web/app/components/workflow/nodes/loop/add-block.tsx index accee12665..a9c1429269 100644 --- a/web/app/components/workflow/nodes/loop/add-block.tsx +++ b/web/app/components/workflow/nodes/loop/add-block.tsx @@ -32,7 +32,7 @@ const AddBlock = ({ const { t } = useTranslation() const { nodesReadOnly } = useNodesReadOnly() const { handleNodeAdd } = useNodesInteractions() - const { availableNextBlocks } = useAvailableBlocks(BlockEnum.Start, false, true) + const { availableNextBlocks } = useAvailableBlocks(BlockEnum.Start, true) const handleSelect = useCallback((type, toolDefaultValue) => { handleNodeAdd( diff --git a/web/app/components/workflow/nodes/loop/default.ts b/web/app/components/workflow/nodes/loop/default.ts index b446432458..1e54b31122 100644 --- a/web/app/components/workflow/nodes/loop/default.ts +++ b/web/app/components/workflow/nodes/loop/default.ts @@ -1,13 +1,20 @@ -import { BlockEnum } from '../../types' import type { NodeDefault } from '../../types' import { ComparisonOperator, LogicalOperator, type LoopNodeType } from './types' import { isEmptyRelatedOperator } from './utils' import { TransferMethod } from '@/types/app' -import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' import { LOOP_NODE_MAX_COUNT } from '@/config' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum } from '@/app/components/workflow/types' +import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' const i18nPrefix = 'workflow.errorMsg' const nodeDefault: NodeDefault = { + ...genNodeMetaData({ + classification: BlockClassificationEnum.Logic, + sort: 3, + type: BlockEnum.Loop, + // author: 'AICT-Team', + }), defaultValue: { start_node_id: '', break_conditions: [], @@ -15,16 +22,6 @@ const nodeDefault: NodeDefault = { _children: [], logical_operator: LogicalOperator.and, }, - getAvailablePrevNodes(isChatMode: boolean) { - const nodes = isChatMode - ? ALL_CHAT_AVAILABLE_BLOCKS - : ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End) - return nodes - }, - getAvailableNextNodes(isChatMode: boolean) { - const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS - return nodes - }, checkValid(payload: LoopNodeType, t: any) { let errorMessages = '' diff --git a/web/app/components/workflow/nodes/loop/use-interactions.ts b/web/app/components/workflow/nodes/loop/use-interactions.ts index 83a32106db..6687acb0c4 100644 --- a/web/app/components/workflow/nodes/loop/use-interactions.ts +++ b/web/app/components/workflow/nodes/loop/use-interactions.ts @@ -1,6 +1,5 @@ import { useCallback } from 'react' import produce from 'immer' -import { useTranslation } from 'react-i18next' import { useStoreApi } from 'reactflow' import type { BlockEnum, @@ -12,13 +11,13 @@ import { } from '../../utils' import { LOOP_PADDING, - NODES_INITIAL_DATA, } from '../../constants' import { CUSTOM_LOOP_START_NODE } from '../loop-start/constants' +import { useNodesMetaData } from '@/app/components/workflow/hooks' export const useNodeLoopInteractions = () => { - const { t } = useTranslation() const store = useStoreApi() + const { nodesMap: nodesMetaDataMap } = useNodesMetaData() const handleNodeLoopRerender = useCallback((nodeId: string) => { const { @@ -115,17 +114,21 @@ export const useNodeLoopInteractions = () => { return childrenNodes.map((child, index) => { const childNodeType = child.data.type as BlockEnum + const { + defaultValue, + title, + } = nodesMetaDataMap![childNodeType] const nodesWithSameType = nodes.filter(node => node.data.type === childNodeType) const { newNode } = generateNewNode({ type: getNodeCustomTypeByNodeDataType(childNodeType), data: { - ...NODES_INITIAL_DATA[childNodeType], + ...defaultValue, ...child.data, selected: false, _isBundled: false, _connectedSourceHandleIds: [], _connectedTargetHandleIds: [], - title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${childNodeType}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${childNodeType}`), + title: nodesWithSameType.length > 0 ? `${title} ${nodesWithSameType.length + 1}` : title, loop_id: newNodeId, }, @@ -138,7 +141,7 @@ export const useNodeLoopInteractions = () => { newNode.id = `${newNodeId}${newNode.id + index}` return newNode }) - }, [store, t]) + }, [store, nodesMetaDataMap]) return { handleNodeLoopRerender, diff --git a/web/app/components/workflow/nodes/parameter-extractor/default.ts b/web/app/components/workflow/nodes/parameter-extractor/default.ts index 0e3b707d30..c2c0140940 100644 --- a/web/app/components/workflow/nodes/parameter-extractor/default.ts +++ b/web/app/components/workflow/nodes/parameter-extractor/default.ts @@ -1,10 +1,16 @@ -import { BlockEnum } from '../../types' import type { NodeDefault } from '../../types' import { type ParameterExtractorNodeType, ReasoningModeType } from './types' -import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum } from '@/app/components/workflow/types' +import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' const i18nPrefix = 'workflow' const nodeDefault: NodeDefault = { + ...genNodeMetaData({ + classification: BlockClassificationEnum.Transform, + sort: 6, + type: BlockEnum.ParameterExtractor, + }), defaultValue: { query: [], model: { @@ -20,16 +26,6 @@ const nodeDefault: NodeDefault = { enabled: false, }, }, - getAvailablePrevNodes(isChatMode: boolean) { - const nodes = isChatMode - ? ALL_CHAT_AVAILABLE_BLOCKS - : ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End) - return nodes - }, - getAvailableNextNodes(isChatMode: boolean) { - const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS - return nodes - }, checkValid(payload: ParameterExtractorNodeType, t: any) { let errorMessages = '' if (!errorMessages && (!payload.query || payload.query.length === 0)) diff --git a/web/app/components/workflow/nodes/question-classifier/default.ts b/web/app/components/workflow/nodes/question-classifier/default.ts index 2729c53f29..c404a6698b 100644 --- a/web/app/components/workflow/nodes/question-classifier/default.ts +++ b/web/app/components/workflow/nodes/question-classifier/default.ts @@ -1,11 +1,17 @@ import type { NodeDefault } from '../../types' -import { BlockEnum } from '../../types' import type { QuestionClassifierNodeType } from './types' -import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum } from '@/app/components/workflow/types' +import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' const i18nPrefix = 'workflow' const nodeDefault: NodeDefault = { + ...genNodeMetaData({ + classification: BlockClassificationEnum.QuestionUnderstand, + sort: 1, + type: BlockEnum.QuestionClassifier, + }), defaultValue: { query_variable_selector: [], model: { @@ -40,16 +46,6 @@ const nodeDefault: NodeDefault = { enabled: false, }, }, - getAvailablePrevNodes(isChatMode: boolean) { - const nodes = isChatMode - ? ALL_CHAT_AVAILABLE_BLOCKS - : ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End) - return nodes - }, - getAvailableNextNodes(isChatMode: boolean) { - const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS - return nodes - }, checkValid(payload: QuestionClassifierNodeType, t: any) { let errorMessages = '' if (!errorMessages && (!payload.query_variable_selector || payload.query_variable_selector.length === 0)) diff --git a/web/app/components/workflow/nodes/start/default.ts b/web/app/components/workflow/nodes/start/default.ts index 98f24c5d98..53ad1761a1 100644 --- a/web/app/components/workflow/nodes/start/default.ts +++ b/web/app/components/workflow/nodes/start/default.ts @@ -1,18 +1,16 @@ import type { NodeDefault } from '../../types' import type { StartNodeType } from './types' -import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum } from '@/app/components/workflow/types' const nodeDefault: NodeDefault = { + ...genNodeMetaData({ + sort: 0.1, + type: BlockEnum.Start, + }), defaultValue: { variables: [], }, - getAvailablePrevNodes() { - return [] - }, - getAvailableNextNodes(isChatMode: boolean) { - const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS - return nodes - }, checkValid() { return { isValid: true, diff --git a/web/app/components/workflow/nodes/template-transform/default.ts b/web/app/components/workflow/nodes/template-transform/default.ts index c698680342..f68d81663a 100644 --- a/web/app/components/workflow/nodes/template-transform/default.ts +++ b/web/app/components/workflow/nodes/template-transform/default.ts @@ -1,23 +1,21 @@ -import { BlockEnum } from '../../types' import type { NodeDefault } from '../../types' import type { TemplateTransformNodeType } from './types' -import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum } from '@/app/components/workflow/types' +import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' const i18nPrefix = 'workflow.errorMsg' const nodeDefault: NodeDefault = { + ...genNodeMetaData({ + classification: BlockClassificationEnum.Transform, + sort: 2, + type: BlockEnum.TemplateTransform, + helpLinkUri: 'template', + }), defaultValue: { + template: '', variables: [], }, - getAvailablePrevNodes(isChatMode: boolean) { - const nodes = isChatMode - ? ALL_CHAT_AVAILABLE_BLOCKS - : ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End) - return nodes - }, - getAvailableNextNodes(isChatMode: boolean) { - const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS - return nodes - }, checkValid(payload: TemplateTransformNodeType, t: any) { let errorMessages = '' const { template, variables } = payload diff --git a/web/app/components/workflow/nodes/tool/default.ts b/web/app/components/workflow/nodes/tool/default.ts index f245929684..891857d6ec 100644 --- a/web/app/components/workflow/nodes/tool/default.ts +++ b/web/app/components/workflow/nodes/tool/default.ts @@ -1,26 +1,21 @@ -import { BlockEnum } from '../../types' import type { NodeDefault } from '../../types' import type { ToolNodeType } from './types' import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' -import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum } from '@/app/components/workflow/types' const i18nPrefix = 'workflow.errorMsg' const nodeDefault: NodeDefault = { + ...genNodeMetaData({ + sort: -1, + type: BlockEnum.Tool, + helpLinkUri: 'tools', + }), defaultValue: { tool_parameters: {}, tool_configurations: {}, }, - getAvailablePrevNodes(isChatMode: boolean) { - const nodes = isChatMode - ? ALL_CHAT_AVAILABLE_BLOCKS - : ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End) - return nodes - }, - getAvailableNextNodes(isChatMode: boolean) { - const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS - return nodes - }, checkValid(payload: ToolNodeType, t: any, moreDataForCheckValid: any) { const { toolInputsSchema, toolSettingSchema, language, notAuthed } = moreDataForCheckValid let errorMessages = '' diff --git a/web/app/components/workflow/nodes/variable-assigner/default.ts b/web/app/components/workflow/nodes/variable-assigner/default.ts index 60c7c27969..f7813c9258 100644 --- a/web/app/components/workflow/nodes/variable-assigner/default.ts +++ b/web/app/components/workflow/nodes/variable-assigner/default.ts @@ -1,25 +1,21 @@ import { type NodeDefault, VarType } from '../../types' -import { BlockEnum } from '../../types' import type { VariableAssignerNodeType } from './types' -import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum } from '@/app/components/workflow/types' +import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' const i18nPrefix = 'workflow' const nodeDefault: NodeDefault = { + ...genNodeMetaData({ + classification: BlockClassificationEnum.Transform, + sort: 3, + type: BlockEnum.VariableAggregator, + }), defaultValue: { output_type: VarType.any, variables: [], }, - getAvailablePrevNodes(isChatMode: boolean) { - const nodes = isChatMode - ? ALL_CHAT_AVAILABLE_BLOCKS - : ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End) - return nodes - }, - getAvailableNextNodes(isChatMode: boolean) { - const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS - return nodes - }, checkValid(payload: VariableAssignerNodeType, t: any) { let errorMessages = '' const { variables, advanced_settings } = payload diff --git a/web/app/components/workflow/operator/add-block.tsx b/web/app/components/workflow/operator/add-block.tsx index d35a5be8b4..7e6a6598e4 100644 --- a/web/app/components/workflow/operator/add-block.tsx +++ b/web/app/components/workflow/operator/add-block.tsx @@ -13,10 +13,10 @@ import { } from '../utils' import { useAvailableBlocks, + useNodesMetaData, useNodesReadOnly, usePanelInteractions, } from '../hooks' -import { NODES_INITIAL_DATA } from '../constants' import { useWorkflowStore } from '../store' import TipPopup from './tip-popup' import cn from '@/utils/classnames' @@ -43,6 +43,7 @@ const AddBlock = ({ const { handlePaneContextmenuCancel } = usePanelInteractions() const [open, setOpen] = useState(false) const { availableNextBlocks } = useAvailableBlocks(BlockEnum.Start, false) + const { nodesMap: nodesMetaDataMap } = useNodesMetaData() const handleOpenChange = useCallback((open: boolean) => { setOpen(open) @@ -56,11 +57,15 @@ const AddBlock = ({ } = store.getState() const nodes = getNodes() const nodesWithSameType = nodes.filter(node => node.data.type === type) + const { + defaultValue, + title, + } = nodesMetaDataMap![type] const { newNode } = generateNewNode({ type: getNodeCustomTypeByNodeDataType(type), data: { - ...NODES_INITIAL_DATA[type], - title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${type}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${type}`), + ...(defaultValue as any), + title: nodesWithSameType.length > 0 ? `${title} ${nodesWithSameType.length + 1}` : title, ...(toolDefaultValue || {}), _isCandidate: true, }, @@ -72,7 +77,7 @@ const AddBlock = ({ workflowStore.setState({ candidateNode: newNode, }) - }, [store, workflowStore, t]) + }, [store, workflowStore, nodesMetaDataMap]) const renderTriggerElement = useCallback((open: boolean) => { return ( diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index 884bdfbd10..eded5030e2 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 { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' export enum BlockEnum { Start = 'start', @@ -285,17 +286,15 @@ export type NodeOutPutVar = { isLoop?: boolean } -export type Block = { - classification?: string +export type NodeDefault = { + classification: BlockClassificationEnum + sort: number type: BlockEnum title: string + author: string description?: string -} - -export type NodeDefault = { + helpLinkUri?: string defaultValue: Partial - getAvailablePrevNodes: (isChatMode: boolean) => BlockEnum[] - getAvailableNextNodes: (isChatMode: boolean) => BlockEnum[] checkValid: (payload: T, t: any, moreDataForCheckValid?: any) => { isValid: boolean; errorMessage?: string } } diff --git a/web/app/components/workflow/utils/gen-node-meta-data.ts b/web/app/components/workflow/utils/gen-node-meta-data.ts new file mode 100644 index 0000000000..e7271cfcc4 --- /dev/null +++ b/web/app/components/workflow/utils/gen-node-meta-data.ts @@ -0,0 +1,28 @@ +import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types' +import type { BlockEnum } from '@/app/components/workflow/types' + +export type GenNodeMetaDataParams = { + classification?: BlockClassificationEnum + sort: number + type: BlockEnum + title?: string + author?: string + helpLinkUri?: string +} +export const genNodeMetaData = ({ + classification = BlockClassificationEnum.Default, + sort, + type, + title = '', + author = 'Dify', + helpLinkUri, +}: GenNodeMetaDataParams) => { + return { + classification, + sort, + type, + title, + author, + helpLinkUri: helpLinkUri || type, + } +} diff --git a/web/app/components/workflow/utils/index.ts b/web/app/components/workflow/utils/index.ts index 4a1da760d4..62f609e4b5 100644 --- a/web/app/components/workflow/utils/index.ts +++ b/web/app/components/workflow/utils/index.ts @@ -6,3 +6,4 @@ export * from './common' export * from './tool' export * from './workflow' export * from './variable' +export * from './gen-node-meta-data'