From e47f690cd29790425d724ccbe269bda82a4d8802 Mon Sep 17 00:00:00 2001 From: zhsama Date: Thu, 29 Jan 2026 18:35:06 +0800 Subject: [PATCH] refactore: Replace hardcoded null strategy strings with constant --- .../sub-graph/components/config-panel.tsx | 7 ++-- .../_base/components/form-input-item.tsx | 3 +- .../workflow/nodes/_base/constants.ts | 6 ++++ .../components/workflow/nodes/_base/types.ts | 3 +- .../context-generate-modal/index.tsx | 25 +++++++++---- .../hooks/use-mixed-variable-extractor.ts | 3 +- .../mixed-variable-text-input/index.tsx | 36 +++++++++++++++++-- .../tool/components/sub-graph-modal/index.tsx | 19 ++++++++-- .../tool/components/sub-graph-modal/types.ts | 2 ++ 9 files changed, 87 insertions(+), 17 deletions(-) create mode 100644 web/app/components/workflow/nodes/_base/constants.ts diff --git a/web/app/components/sub-graph/components/config-panel.tsx b/web/app/components/sub-graph/components/config-panel.tsx index 094f48e656..1f2a121cff 100644 --- a/web/app/components/sub-graph/components/config-panel.tsx +++ b/web/app/components/sub-graph/components/config-panel.tsx @@ -15,6 +15,7 @@ import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/ import Field from '@/app/components/workflow/nodes/_base/components/field' import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker' import Tab, { TabType } from '@/app/components/workflow/nodes/_base/components/workflow-panel/tab' +import { NULL_STRATEGY } from '@/app/components/workflow/nodes/_base/constants' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' import { cn } from '@/utils/classnames' @@ -62,12 +63,12 @@ const ConfigPanel: FC = ({ const [nullStrategyOpen, setNullStrategyOpen] = useState(false) const whenOutputNoneOptions = useMemo(() => ([ { - value: 'raise_error' as const, + value: NULL_STRATEGY.RAISE_ERROR, label: t('subGraphModal.whenOutputNone.error', { ns: 'workflow' }), description: t('subGraphModal.whenOutputNone.errorDesc', { ns: 'workflow' }), }, { - value: 'use_default' as const, + value: NULL_STRATEGY.USE_DEFAULT, label: t('subGraphModal.whenOutputNone.default', { ns: 'workflow' }), description: t('subGraphModal.whenOutputNone.defaultDesc', { ns: 'workflow' }), }, @@ -194,7 +195,7 @@ const ConfigPanel: FC = ({ {selectedWhenOutputNoneOption.description} )} - {nestedNodeConfig.null_strategy === 'use_default' && ( + {nestedNodeConfig.null_strategy === NULL_STRATEGY.USE_DEFAULT && (
= ({ ? (nestedNodeConfig ?? varInput?.nested_node_config ?? { extractor_node_id: nodeId && variable ? `${nodeId}_ext_${variable}` : '', output_selector: ['result'], - null_strategy: 'use_default', + null_strategy: NULL_STRATEGY.RAISE_ERROR, default_value: '', }) : undefined diff --git a/web/app/components/workflow/nodes/_base/constants.ts b/web/app/components/workflow/nodes/_base/constants.ts new file mode 100644 index 0000000000..b506699e62 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/constants.ts @@ -0,0 +1,6 @@ +export const NULL_STRATEGY = { + RAISE_ERROR: 'raise_error', + USE_DEFAULT: 'use_default', +} as const + +export type NullStrategy = typeof NULL_STRATEGY[keyof typeof NULL_STRATEGY] diff --git a/web/app/components/workflow/nodes/_base/types.ts b/web/app/components/workflow/nodes/_base/types.ts index 3f24fe4e1b..4b1a4e280f 100644 --- a/web/app/components/workflow/nodes/_base/types.ts +++ b/web/app/components/workflow/nodes/_base/types.ts @@ -1,3 +1,4 @@ +import type { NullStrategy } from '@/app/components/workflow/nodes/_base/constants' import type { ValueSelector } from '@/app/components/workflow/types' // Generic variable types for all resource forms @@ -11,7 +12,7 @@ export enum VarKindType { export type NestedNodeConfig = { extractor_node_id: string output_selector: ValueSelector - null_strategy: 'raise_error' | 'use_default' + null_strategy: NullStrategy default_value: unknown } diff --git a/web/app/components/workflow/nodes/tool/components/context-generate-modal/index.tsx b/web/app/components/workflow/nodes/tool/components/context-generate-modal/index.tsx index 06d763b65e..45da9a4bf2 100644 --- a/web/app/components/workflow/nodes/tool/components/context-generate-modal/index.tsx +++ b/web/app/components/workflow/nodes/tool/components/context-generate-modal/index.tsx @@ -23,6 +23,7 @@ type Props = { codeNodeId: string availableVars?: NodeOutPutVar[] availableNodes?: Node[] + onOpenInternalViewAndRun?: () => void } export type ContextGenerateModalHandle = { @@ -59,6 +60,7 @@ const ContextGenerateModal = forwardRef(({ codeNodeId, availableVars, availableNodes, + onOpenInternalViewAndRun, }, ref) => { const configsMap = useHooksStore(s => s.configsMap) const nodes = useStore(s => s.nodes) @@ -179,13 +181,22 @@ const ContextGenerateModal = forwardRef(({ return if (current) applyToNode(false) - const store = workflowStore.getState() - store.setInitShowLastRunTab(true) - store.setPendingSingleRun({ - nodeId: codeNodeId, - action: 'run', - }) - }, [applyToNode, codeNodeId, current, workflowStore]) + + if (onOpenInternalViewAndRun) { + // Close this modal and open internal view, then run + handleCloseModal() + onOpenInternalViewAndRun() + } + else { + // Fallback: direct run (for cases without internal view) + const store = workflowStore.getState() + store.setInitShowLastRunTab(true) + store.setPendingSingleRun({ + nodeId: codeNodeId, + action: 'run', + }) + } + }, [applyToNode, codeNodeId, current, handleCloseModal, onOpenInternalViewAndRun, workflowStore]) const isRunning = useMemo(() => { const target = nodes.find(node => node.id === codeNodeId) diff --git a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/hooks/use-mixed-variable-extractor.ts b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/hooks/use-mixed-variable-extractor.ts index 3ce0df6323..fe3c030699 100644 --- a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/hooks/use-mixed-variable-extractor.ts +++ b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/hooks/use-mixed-variable-extractor.ts @@ -10,6 +10,7 @@ import type { Node as WorkflowNode, } from '@/app/components/workflow/types' import { useCallback, useMemo } from 'react' +import { NULL_STRATEGY } from '@/app/components/workflow/nodes/_base/constants' import { Type } from '@/app/components/workflow/nodes/llm/types' import { BlockEnum, EditionType, isPromptMessageContext, PromptRole, VarType } from '@/app/components/workflow/types' import { generateNewNode, getNodeCustomTypeByNodeDataType, mergeNodeDefaultData } from '@/app/components/workflow/utils' @@ -46,7 +47,7 @@ export const buildAssembleNestedNodeConfig = ( return { extractor_node_id: extractorNodeId, output_selector: defaultOutputKey ? [defaultOutputKey] : [], - null_strategy: 'use_default', + null_strategy: NULL_STRATEGY.RAISE_ERROR, default_value: '', } } diff --git a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx index d7403b8283..fdee5c3e79 100644 --- a/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx +++ b/web/app/components/workflow/nodes/tool/components/mixed-variable-text-input/index.tsx @@ -13,6 +13,7 @@ import type { import { memo, useCallback, + useEffect, useMemo, useRef, useState, @@ -22,9 +23,10 @@ import { useNodes, useStoreApi } from 'reactflow' import PromptEditor from '@/app/components/base/prompt-editor' import { useNodesMetaData, useNodesSyncDraft } from '@/app/components/workflow/hooks' import { useHooksStore } from '@/app/components/workflow/hooks-store' +import { NULL_STRATEGY } from '@/app/components/workflow/nodes/_base/constants' import { VarKindType as VarKindTypeEnum } from '@/app/components/workflow/nodes/_base/types' import { Type } from '@/app/components/workflow/nodes/llm/types' -import { useStore } from '@/app/components/workflow/store' +import { useStore, useWorkflowStore } from '@/app/components/workflow/store' import { BlockEnum } from '@/app/components/workflow/types' import { useGetLanguage } from '@/context/i18n' import { useStrategyProviders } from '@/service/use-strategy' @@ -46,7 +48,7 @@ type WorkflowNodesMap = NonNullable(null) + const [pendingRunAfterSubGraphOpen, setPendingRunAfterSubGraphOpen] = useState(false) + const workflowStore = useWorkflowStore() const nodesByIdMap = useMemo(() => { return availableNodes.reduce((acc, node) => { @@ -352,12 +356,39 @@ const MixedVariableTextInput = ({ const handleCloseSubGraphModal = useCallback(() => { setIsSubGraphModalOpen(false) + setPendingRunAfterSubGraphOpen(false) }, []) const handleCloseContextGenerateModal = useCallback(() => { setIsContextGenerateModalOpen(false) }, []) + const handleOpenInternalViewAndRun = useCallback(() => { + setIsSubGraphModalOpen(true) + setPendingRunAfterSubGraphOpen(true) + }, []) + + useEffect(() => { + if (!isSubGraphModalOpen || !pendingRunAfterSubGraphOpen) + return + + const extractorNodeId = assembleExtractorNodeId || (toolNodeId && paramKey ? `${toolNodeId}_ext_${paramKey}` : '') + if (!extractorNodeId) + return + + const timer = setTimeout(() => { + const store = workflowStore() + store.setInitShowLastRunTab(true) + store.setPendingSingleRun({ + nodeId: extractorNodeId, + action: 'run', + }) + setPendingRunAfterSubGraphOpen(false) + }, 300) + + return () => clearTimeout(timer) + }, [isSubGraphModalOpen, pendingRunAfterSubGraphOpen, assembleExtractorNodeId, toolNodeId, paramKey, workflowStore]) + const sourceVariable: ValueSelector | undefined = detectedAgentFromValue ? [detectedAgentFromValue.nodeId, 'context'] : undefined @@ -462,6 +493,7 @@ const MixedVariableTextInput = ({ codeNodeId={assembleExtractorNodeId || `${toolNodeId}_ext_${paramKey}`} availableVars={nodesOutputVars} availableNodes={availableNodes} + onOpenInternalViewAndRun={handleOpenInternalViewAndRun} /> )}
diff --git a/web/app/components/workflow/nodes/tool/components/sub-graph-modal/index.tsx b/web/app/components/workflow/nodes/tool/components/sub-graph-modal/index.tsx index 5ff96a01a2..497e9b73c2 100644 --- a/web/app/components/workflow/nodes/tool/components/sub-graph-modal/index.tsx +++ b/web/app/components/workflow/nodes/tool/components/sub-graph-modal/index.tsx @@ -16,6 +16,7 @@ import { AssembleVariablesAlt } from '@/app/components/base/icons/src/vender/lin import { Agent } from '@/app/components/base/icons/src/vender/workflow' import { useIsChatMode, useNodesSyncDraft, useWorkflow, useWorkflowVariables } from '@/app/components/workflow/hooks' import { useHooksStore } from '@/app/components/workflow/hooks-store' +import { NULL_STRATEGY } from '@/app/components/workflow/nodes/_base/constants' import { VarKindType } from '@/app/components/workflow/nodes/_base/types' import { useStore as useWorkflowStore } from '@/app/components/workflow/store' import { EditionType, isPromptMessageContext, PromptRole, VarType } from '@/app/components/workflow/types' @@ -23,7 +24,15 @@ import SubGraphCanvas from './sub-graph-canvas' const SubGraphModal: FC = (props) => { const { t } = useTranslation() - const { isOpen, onClose, variant, toolNodeId, paramKey } = props + const { + isOpen, + onClose, + variant, + toolNodeId, + paramKey, + pendingSingleRun, + onPendingSingleRunHandled, + } = props const isAgentVariant = variant === 'agent' const resolvedAgentNodeId = isAgentVariant ? props.agentNodeId : '' const agentName = isAgentVariant ? props.agentName : '' @@ -93,7 +102,7 @@ const SubGraphModal: FC = (props) => { return { extractor_node_id: current?.extractor_node_id || extractorNodeId, output_selector: outputSelector.length > 0 ? outputSelector : defaultOutputSelector, - null_strategy: current?.null_strategy || 'use_default', + null_strategy: current?.null_strategy || NULL_STRATEGY.RAISE_ERROR, default_value: current?.default_value ?? '', } }, [extractorNodeId, isAgentVariant, paramKey, toolParam?.nested_node_config]) @@ -273,6 +282,9 @@ const SubGraphModal: FC = (props) => { variant="agent" toolNodeId={toolNodeId} paramKey={paramKey} + isOpen={isOpen} + pendingSingleRun={pendingSingleRun} + onPendingSingleRunHandled={onPendingSingleRunHandled} sourceVariable={props.sourceVariable} agentNodeId={props.agentNodeId} agentName={props.agentName} @@ -292,6 +304,9 @@ const SubGraphModal: FC = (props) => { variant="assemble" toolNodeId={toolNodeId} paramKey={paramKey} + isOpen={isOpen} + pendingSingleRun={pendingSingleRun} + onPendingSingleRunHandled={onPendingSingleRunHandled} title={props.title} configsMap={configsMap} nestedNodeConfig={nestedNodeConfig} diff --git a/web/app/components/workflow/nodes/tool/components/sub-graph-modal/types.ts b/web/app/components/workflow/nodes/tool/components/sub-graph-modal/types.ts index 8a29b402d1..dc81d9ae8f 100644 --- a/web/app/components/workflow/nodes/tool/components/sub-graph-modal/types.ts +++ b/web/app/components/workflow/nodes/tool/components/sub-graph-modal/types.ts @@ -6,6 +6,8 @@ type BaseSubGraphModalProps = { onClose: () => void toolNodeId: string paramKey: string + pendingSingleRun?: boolean + onPendingSingleRunHandled?: () => void } type AgentSubGraphModalProps = BaseSubGraphModalProps & {