From 8f7b9e2de44f11dae4f838275b4a0152526c98e9 Mon Sep 17 00:00:00 2001 From: zhsama Date: Thu, 29 Jan 2026 18:56:41 +0800 Subject: [PATCH] feat: Trigger single run in sub-graph after modal opens --- .../sub-graph/components/sub-graph-main.tsx | 38 ++++++++++++++++++- web/app/components/sub-graph/index.tsx | 9 +++++ web/app/components/sub-graph/types.ts | 3 ++ .../mixed-variable-text-input/index.tsx | 36 +++++------------- 4 files changed, 59 insertions(+), 27 deletions(-) diff --git a/web/app/components/sub-graph/components/sub-graph-main.tsx b/web/app/components/sub-graph/components/sub-graph-main.tsx index 52f1e64bbc..7f874f8151 100644 --- a/web/app/components/sub-graph/components/sub-graph-main.tsx +++ b/web/app/components/sub-graph/components/sub-graph-main.tsx @@ -4,11 +4,13 @@ import type { SyncWorkflowDraft, SyncWorkflowDraftCallback } from '../types' import type { Shape as HooksStoreShape } from '@/app/components/workflow/hooks-store' import type { NestedNodeConfig } from '@/app/components/workflow/nodes/_base/types' import type { Edge, Node } from '@/app/components/workflow/types' -import { useCallback, useMemo } from 'react' +import { useCallback, useEffect, useMemo } from 'react' import { useStoreApi } from 'reactflow' import { InteractionMode, WorkflowWithInnerContext } from '@/app/components/workflow' +import { useNodesInteractions } from '@/app/components/workflow/hooks' import { useSetWorkflowVarsWithValue } from '@/app/components/workflow/hooks/use-fetch-workflow-inspect-vars' import { useInspectVarsCrudCommon } from '@/app/components/workflow/hooks/use-inspect-vars-crud-common' +import { useWorkflowStore } from '@/app/components/workflow/store' import { BlockEnum } from '@/app/components/workflow/types' import { FlowType } from '@/types/common' import { useAvailableNodesMetaData } from '../hooks' @@ -24,6 +26,9 @@ type SubGraphMainBaseProps = { selectableNodeTypes?: BlockEnum[] onSave?: (nodes: Node[], edges: Edge[]) => void onSyncWorkflowDraft?: SyncWorkflowDraft + isOpen: boolean + pendingSingleRun?: boolean + onPendingSingleRunHandled?: () => void } type SubGraphMainProps @@ -50,8 +55,13 @@ const SubGraphMain: FC = (props) => { selectableNodeTypes, onSave, onSyncWorkflowDraft, + isOpen, + pendingSingleRun, + onPendingSingleRunHandled, } = props const reactFlowStore = useStoreApi() + const workflowStore = useWorkflowStore() + const { handleNodeSelect } = useNodesInteractions() const availableNodesMetaData = useAvailableNodesMetaData() const flowType = configsMap?.flowType ?? FlowType.appFlow const flowId = configsMap?.flowId ?? '' @@ -91,6 +101,32 @@ const SubGraphMain: FC = (props) => { } }, [handleSyncSubGraphDraft, onSyncWorkflowDraft]) + useEffect(() => { + if (!isOpen || !pendingSingleRun) + return + + const { getNodes } = reactFlowStore.getState() + const currentNodes = getNodes() + const hasExtractorNode = currentNodes.some(node => node.id === extractorNodeId) + if (!hasExtractorNode) + return + + // NodePanel listens for pendingSingleRun only when the extractor node is selected in this subgraph. + handleNodeSelect(extractorNodeId, false, true) + + // Defer run until the selection is applied and the panel is ready. + const frame = requestAnimationFrame(() => { + const store = workflowStore.getState() + store.setPendingSingleRun({ + nodeId: extractorNodeId, + action: 'run', + }) + onPendingSingleRunHandled?.() + }) + + return () => cancelAnimationFrame(frame) + }, [extractorNodeId, handleNodeSelect, isOpen, onPendingSingleRunHandled, pendingSingleRun, reactFlowStore, workflowStore]) + const resolvedSelectableTypes = useMemo(() => { if (selectableNodeTypes && selectableNodeTypes.length > 0) return selectableNodeTypes diff --git a/web/app/components/sub-graph/index.tsx b/web/app/components/sub-graph/index.tsx index 9996d18064..97df309767 100644 --- a/web/app/components/sub-graph/index.tsx +++ b/web/app/components/sub-graph/index.tsx @@ -32,6 +32,7 @@ const defaultViewport: Viewport = { const SubGraphContent: FC = (props) => { const { + isOpen, toolNodeId, paramKey, toolParamValue, @@ -41,6 +42,8 @@ const SubGraphContent: FC = (props) => { selectableNodeTypes, onSave, onSyncWorkflowDraft, + pendingSingleRun, + onPendingSingleRunHandled, } = props const isAgentVariant = props.variant === 'agent' @@ -230,6 +233,9 @@ const SubGraphContent: FC = (props) => { title={sourceTitle} extractorNodeId={`${toolNodeId}_ext_${paramKey}`} configsMap={configsMap} + isOpen={isOpen} + pendingSingleRun={pendingSingleRun} + onPendingSingleRunHandled={onPendingSingleRunHandled} nestedNodeConfig={props.nestedNodeConfig} onNestedNodeConfigChange={props.onNestedNodeConfigChange} selectableNodeTypes={selectableNodeTypes} @@ -253,6 +259,9 @@ const SubGraphContent: FC = (props) => { title={sourceTitle} extractorNodeId={`${toolNodeId}_ext_${paramKey}`} configsMap={configsMap} + isOpen={isOpen} + pendingSingleRun={pendingSingleRun} + onPendingSingleRunHandled={onPendingSingleRunHandled} nestedNodeConfig={props.nestedNodeConfig} onNestedNodeConfigChange={props.onNestedNodeConfigChange} selectableNodeTypes={selectableNodeTypes} diff --git a/web/app/components/sub-graph/types.ts b/web/app/components/sub-graph/types.ts index 3a0d081b29..635476b12c 100644 --- a/web/app/components/sub-graph/types.ts +++ b/web/app/components/sub-graph/types.ts @@ -19,6 +19,7 @@ export type SyncWorkflowDraft = ( export type SubGraphVariant = 'agent' | 'assemble' type BaseSubGraphProps = { + isOpen: boolean toolNodeId: string paramKey: string configsMap?: HooksStoreShape['configsMap'] @@ -28,6 +29,8 @@ type BaseSubGraphProps = { selectableNodeTypes?: BlockEnum[] onSave?: (nodes: Node[], edges: Edge[]) => void onSyncWorkflowDraft?: SyncWorkflowDraft + pendingSingleRun?: boolean + onPendingSingleRunHandled?: () => void } export type AgentSubGraphProps = BaseSubGraphProps & { 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 fdee5c3e79..9c71d3469e 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,7 +13,6 @@ import type { import { memo, useCallback, - useEffect, useMemo, useRef, useState, @@ -26,7 +25,7 @@ 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, useWorkflowStore } from '@/app/components/workflow/store' +import { useStore } from '@/app/components/workflow/store' import { BlockEnum } from '@/app/components/workflow/types' import { useGetLanguage } from '@/context/i18n' import { useStrategyProviders } from '@/service/use-strategy' @@ -99,7 +98,6 @@ const MixedVariableTextInput = ({ const [isContextGenerateModalOpen, setIsContextGenerateModalOpen] = useState(false) const contextGenerateModalRef = useRef(null) const [pendingRunAfterSubGraphOpen, setPendingRunAfterSubGraphOpen] = useState(false) - const workflowStore = useWorkflowStore() const nodesByIdMap = useMemo(() => { return availableNodes.reduce((acc, node) => { @@ -359,35 +357,19 @@ const MixedVariableTextInput = ({ setPendingRunAfterSubGraphOpen(false) }, []) + const handlePendingSingleRunHandled = useCallback(() => { + setPendingRunAfterSubGraphOpen(false) + }, []) + const handleCloseContextGenerateModal = useCallback(() => { setIsContextGenerateModalOpen(false) }, []) const handleOpenInternalViewAndRun = useCallback(() => { - setIsSubGraphModalOpen(true) + if (!isSubGraphModalOpen) + 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]) + }, [isSubGraphModalOpen]) const sourceVariable: ValueSelector | undefined = detectedAgentFromValue ? [detectedAgentFromValue.nodeId, 'context'] @@ -465,6 +447,8 @@ const MixedVariableTextInput = ({