diff --git a/web/app/components/workflow-app/components/workflow-main.tsx b/web/app/components/workflow-app/components/workflow-main.tsx index b9399ba986..e90b2904c9 100644 --- a/web/app/components/workflow-app/components/workflow-main.tsx +++ b/web/app/components/workflow-app/components/workflow-main.tsx @@ -69,6 +69,7 @@ const WorkflowMain = ({ handleWorkflowTriggerScheduleRunInWorkflow, handleWorkflowTriggerWebhookRunInWorkflow, handleWorkflowTriggerPluginRunInWorkflow, + handleWorkflowRunAllTriggersInWorkflow, } = useWorkflowStartRun() const availableNodesMetaData = useAvailableNodesMetaData() const { getWorkflowRunAndTraceUrl } = useGetRunAndTraceUrl() @@ -114,6 +115,7 @@ const WorkflowMain = ({ handleWorkflowTriggerScheduleRunInWorkflow, handleWorkflowTriggerWebhookRunInWorkflow, handleWorkflowTriggerPluginRunInWorkflow, + handleWorkflowRunAllTriggersInWorkflow, availableNodesMetaData, getWorkflowRunAndTraceUrl, exportCheck, @@ -150,6 +152,7 @@ const WorkflowMain = ({ handleWorkflowTriggerScheduleRunInWorkflow, handleWorkflowTriggerWebhookRunInWorkflow, handleWorkflowTriggerPluginRunInWorkflow, + handleWorkflowRunAllTriggersInWorkflow, availableNodesMetaData, getWorkflowRunAndTraceUrl, exportCheck, diff --git a/web/app/components/workflow-app/hooks/use-workflow-run.ts b/web/app/components/workflow-app/hooks/use-workflow-run.ts index 1a8a9568e6..016769cf2b 100644 --- a/web/app/components/workflow-app/hooks/use-workflow-run.ts +++ b/web/app/components/workflow-app/hooks/use-workflow-run.ts @@ -25,13 +25,15 @@ import { useSetWorkflowVarsWithValue } from '../../workflow/hooks/use-fetch-work import { useConfigsMap } from './use-configs-map' import { API_PREFIX } from '@/config' import { ContentType, getAccessToken, getBaseOptions } from '@/service/fetch' +import { TriggerType } from '@/app/components/workflow/header/test-run-menu' -type HandleRunMode = 'default' | 'schedule' | 'webhook' | 'plugin' +type HandleRunMode = TriggerType type HandleRunOptions = { mode?: HandleRunMode scheduleNodeId?: string webhookNodeId?: string pluginNodeId?: string + allNodeIds?: string[] } export const useWorkflowRun = () => { @@ -126,7 +128,7 @@ export const useWorkflowRun = () => { callback?: IOtherOptions, options?: HandleRunOptions, ) => { - const runMode: HandleRunMode = options?.mode ?? 'default' + const runMode: HandleRunMode = options?.mode ?? TriggerType.UserInput const resolvedParams = params ?? {} const { getNodes, @@ -170,13 +172,20 @@ export const useWorkflowRun = () => { const isInWorkflowDebug = appDetail?.mode === 'workflow' let url = '' - if (runMode === 'plugin' || runMode === 'webhook' || runMode === 'schedule') { + if (runMode === TriggerType.Plugin || runMode === TriggerType.Webhook || runMode === TriggerType.Schedule) { if (!appDetail?.id) { console.error('handleRun: missing app id for trigger plugin run') return } url = `/apps/${appDetail.id}/workflows/draft/trigger/run` } + else if (runMode === TriggerType.All) { + if (!appDetail?.id) { + console.error('handleRun: missing app id for trigger run all') + return + } + url = `/apps/${appDetail.id}/workflows/draft/trigger/run-all` + } else if (appDetail?.mode === 'advanced-chat') { url = `/apps/${appDetail.id}/advanced-chat/workflows/draft/run` } @@ -186,36 +195,44 @@ export const useWorkflowRun = () => { let requestBody = {} - if (runMode === 'schedule') + if (runMode === TriggerType.Schedule) requestBody = { node_id: options?.scheduleNodeId } - else if (runMode === 'webhook') + else if (runMode === TriggerType.Webhook) requestBody = { node_id: options?.webhookNodeId } - else if (runMode === 'plugin') + else if (runMode === TriggerType.Plugin) requestBody = { node_id: options?.pluginNodeId } + else if (runMode === TriggerType.All) + requestBody = { node_ids: options?.allNodeIds } + else requestBody = resolvedParams if (!url) return - if (runMode === 'schedule' && !options?.scheduleNodeId) { + if (runMode === TriggerType.Schedule && !options?.scheduleNodeId) { console.error('handleRun: schedule trigger run requires node id') return } - if (runMode === 'webhook' && !options?.webhookNodeId) { + if (runMode === TriggerType.Webhook && !options?.webhookNodeId) { console.error('handleRun: webhook trigger run requires node id') return } - if (runMode === 'plugin' && !options?.pluginNodeId) { + if (runMode === TriggerType.Plugin && !options?.pluginNodeId) { console.error('handleRun: plugin trigger run requires node id') return } + if (runMode === TriggerType.All && !options?.allNodeIds && options?.allNodeIds?.length === 0) { + console.error('handleRun: all trigger run requires node ids') + return + } + abortControllerRef.current?.abort() abortControllerRef.current = null @@ -227,7 +244,7 @@ export const useWorkflowRun = () => { setListeningTriggerNodeId, } = workflowStore.getState() - if (runMode === 'webhook' || runMode === 'plugin') { + if (runMode === TriggerType.Webhook || runMode === TriggerType.Plugin || runMode === TriggerType.All) { setIsListening(true) setShowVariableInspectPanel(true) setWorkflowRunningData({ @@ -430,7 +447,7 @@ export const useWorkflowRun = () => { }, { once: true }) }) - const runTriggerDebug = async (debugType: 'webhook' | 'plugin') => { + const runTriggerDebug = async (debugType: TriggerType.Webhook | TriggerType.Plugin | TriggerType.All) => { const urlWithPrefix = (url.startsWith('http://') || url.startsWith('https://')) ? url : `${API_PREFIX}${url.startsWith('/') ? url : `/${url}`}` @@ -438,13 +455,13 @@ export const useWorkflowRun = () => { const controller = new AbortController() abortControllerRef.current = controller - const controllerKey = debugType === 'webhook' + const controllerKey = debugType === TriggerType.Webhook ? '__webhookDebugAbortController' : '__pluginDebugAbortController' ;(window as any)[controllerKey] = controller - const debugLabel = debugType === 'webhook' ? 'Webhook' : 'Plugin' + const debugLabel = debugType === TriggerType.Webhook ? 'Webhook' : debugType === TriggerType.Plugin ? 'Plugin' : 'All' const poll = async (): Promise => { try { @@ -559,13 +576,18 @@ export const useWorkflowRun = () => { await poll() } - if (runMode === 'webhook') { - await runTriggerDebug('webhook') + if (runMode === TriggerType.Webhook) { + await runTriggerDebug(TriggerType.Webhook) return } - if (runMode === 'plugin') { - await runTriggerDebug('plugin') + if (runMode === TriggerType.Plugin) { + await runTriggerDebug(TriggerType.Plugin) + return + } + + if (runMode === TriggerType.All) { + await runTriggerDebug(TriggerType.All) return } diff --git a/web/app/components/workflow-app/hooks/use-workflow-start-run.tsx b/web/app/components/workflow-app/hooks/use-workflow-start-run.tsx index 2a1df5191d..8ccb08cae9 100644 --- a/web/app/components/workflow-app/hooks/use-workflow-start-run.tsx +++ b/web/app/components/workflow-app/hooks/use-workflow-start-run.tsx @@ -12,6 +12,7 @@ import { useNodesSyncDraft, useWorkflowRun, } from '.' +import { TriggerType } from '@/app/components/workflow/header/test-run-menu' export const useWorkflowStartRun = () => { const store = useStoreApi() @@ -102,7 +103,7 @@ export const useWorkflowStartRun = () => { {}, undefined, { - mode: 'schedule', + mode: TriggerType.Schedule, scheduleNodeId: nodeId, }, ) @@ -150,7 +151,7 @@ export const useWorkflowStartRun = () => { { node_id: nodeId }, undefined, { - mode: 'webhook', + mode: TriggerType.Webhook, webhookNodeId: nodeId, }, ) @@ -195,12 +196,43 @@ export const useWorkflowStartRun = () => { { node_id: nodeId }, undefined, { - mode: 'plugin', + mode: TriggerType.Plugin, pluginNodeId: nodeId, }, ) }, [store, workflowStore, handleRun, doSyncWorkflowDraft]) + const handleWorkflowRunAllTriggersInWorkflow = useCallback(async (nodeIds: string[]) => { + if (!nodeIds.length) + return + const { + workflowRunningData, + showDebugAndPreviewPanel, + setShowDebugAndPreviewPanel, + setShowInputsPanel, + setShowEnvPanel, + } = workflowStore.getState() + + if (workflowRunningData?.result.status === WorkflowRunningStatus.Running) + return + + setShowEnvPanel(false) + setShowInputsPanel(false) + + if (!showDebugAndPreviewPanel) + setShowDebugAndPreviewPanel(true) + + await doSyncWorkflowDraft() + handleRun( + { node_ids: nodeIds }, + undefined, + { + mode: TriggerType.All, + allNodeIds: nodeIds, + }, + ) + }, [store, workflowStore, handleRun, doSyncWorkflowDraft]) + const handleWorkflowStartRunInChatflow = useCallback(async () => { const { showDebugAndPreviewPanel, @@ -235,5 +267,6 @@ export const useWorkflowStartRun = () => { handleWorkflowTriggerScheduleRunInWorkflow, handleWorkflowTriggerWebhookRunInWorkflow, handleWorkflowTriggerPluginRunInWorkflow, + handleWorkflowRunAllTriggersInWorkflow, } } diff --git a/web/app/components/workflow/header/run-mode.tsx b/web/app/components/workflow/header/run-mode.tsx index e5fcd8f52a..f715008c59 100644 --- a/web/app/components/workflow/header/run-mode.tsx +++ b/web/app/components/workflow/header/run-mode.tsx @@ -10,7 +10,7 @@ import cn from '@/utils/classnames' import { RiLoader2Line, RiPlayLargeLine } from '@remixicon/react' import { StopCircle } from '@/app/components/base/icons/src/vender/line/mediaAndDevices' import { useDynamicTestRunOptions } from '../hooks/use-dynamic-test-run-options' -import TestRunMenu, { type TestRunMenuRef, type TriggerOption } from './test-run-menu' +import TestRunMenu, { type TestRunMenuRef, type TriggerOption, TriggerType } from './test-run-menu' type RunModeProps = { text?: string @@ -25,6 +25,7 @@ const RunMode = ({ handleWorkflowTriggerScheduleRunInWorkflow, handleWorkflowTriggerWebhookRunInWorkflow, handleWorkflowTriggerPluginRunInWorkflow, + handleWorkflowRunAllTriggersInWorkflow, } = useWorkflowStartRun() const { handleStopRun } = useWorkflowRun() const { validateBeforeRun } = useWorkflowRunValidation() @@ -57,20 +58,25 @@ const RunMode = ({ if (!validateBeforeRun()) return - if (option.type === 'user_input') { + if (option.type === TriggerType.UserInput) { handleWorkflowStartRunInWorkflow() } - else if (option.type === 'schedule') { + else if (option.type === TriggerType.Schedule) { handleWorkflowTriggerScheduleRunInWorkflow(option.nodeId) } - else if (option.type === 'webhook') { + else if (option.type === TriggerType.Webhook) { if (option.nodeId) handleWorkflowTriggerWebhookRunInWorkflow({ nodeId: option.nodeId }) } - else if (option.type === 'plugin') { + else if (option.type === TriggerType.Plugin) { if (option.nodeId) handleWorkflowTriggerPluginRunInWorkflow(option.nodeId) } + else if (option.type === TriggerType.All) { + const targetNodeIds = option.relatedNodeIds?.filter(Boolean) + if (targetNodeIds && targetNodeIds.length > 0) + handleWorkflowRunAllTriggersInWorkflow(targetNodeIds) + } else { // Placeholder for trigger-specific execution logic for schedule, webhook, plugin types console.log('TODO: Handle trigger execution for type:', option.type, 'nodeId:', option.nodeId) @@ -80,6 +86,8 @@ const RunMode = ({ handleWorkflowStartRunInWorkflow, handleWorkflowTriggerScheduleRunInWorkflow, handleWorkflowTriggerWebhookRunInWorkflow, + handleWorkflowTriggerPluginRunInWorkflow, + handleWorkflowRunAllTriggersInWorkflow, ]) const { eventEmitter } = useEventEmitterContextContext() diff --git a/web/app/components/workflow/header/test-run-menu.tsx b/web/app/components/workflow/header/test-run-menu.tsx index c5fbf98414..40aabab6f8 100644 --- a/web/app/components/workflow/header/test-run-menu.tsx +++ b/web/app/components/workflow/header/test-run-menu.tsx @@ -19,12 +19,21 @@ import { } from '@/app/components/base/portal-to-follow-elem' import ShortcutsName from '../shortcuts-name' +export enum TriggerType { + UserInput = 'user_input', + Schedule = 'schedule', + Webhook = 'webhook', + Plugin = 'plugin', + All = 'all', +} + export type TriggerOption = { id: string - type: 'user_input' | 'schedule' | 'webhook' | 'plugin' | 'all' + type: TriggerType name: string icon: React.ReactNode nodeId?: string + relatedNodeIds?: string[] enabled: boolean } diff --git a/web/app/components/workflow/hooks-store/store.ts b/web/app/components/workflow/hooks-store/store.ts index df9a44b2be..4b2cb71009 100644 --- a/web/app/components/workflow/hooks-store/store.ts +++ b/web/app/components/workflow/hooks-store/store.ts @@ -48,6 +48,7 @@ export type CommonHooksFnMap = { handleWorkflowTriggerScheduleRunInWorkflow: (nodeId?: string) => void handleWorkflowTriggerWebhookRunInWorkflow: (params: { nodeId: string }) => void handleWorkflowTriggerPluginRunInWorkflow: (nodeId?: string) => void + handleWorkflowRunAllTriggersInWorkflow: (nodeIds: string[]) => void availableNodesMetaData?: AvailableNodesMetaData getWorkflowRunAndTraceUrl: (runId?: string) => { runUrl: string; traceUrl: string } exportCheck?: () => Promise @@ -93,6 +94,7 @@ export const createHooksStore = ({ handleWorkflowTriggerScheduleRunInWorkflow = noop, handleWorkflowTriggerWebhookRunInWorkflow = noop, handleWorkflowTriggerPluginRunInWorkflow = noop, + handleWorkflowRunAllTriggersInWorkflow = noop, availableNodesMetaData = { nodes: [], }, @@ -134,6 +136,7 @@ export const createHooksStore = ({ handleWorkflowTriggerScheduleRunInWorkflow, handleWorkflowTriggerWebhookRunInWorkflow, handleWorkflowTriggerPluginRunInWorkflow, + handleWorkflowRunAllTriggersInWorkflow, availableNodesMetaData, getWorkflowRunAndTraceUrl, exportCheck, diff --git a/web/app/components/workflow/hooks/use-dynamic-test-run-options.tsx b/web/app/components/workflow/hooks/use-dynamic-test-run-options.tsx index c2ed15fb7c..3e35ff0168 100644 --- a/web/app/components/workflow/hooks/use-dynamic-test-run-options.tsx +++ b/web/app/components/workflow/hooks/use-dynamic-test-run-options.tsx @@ -3,7 +3,7 @@ import { useNodes } from 'reactflow' import { useTranslation } from 'react-i18next' import { BlockEnum, type CommonNodeType } from '../types' import { getWorkflowEntryNode } from '../utils/workflow-entry' -import type { TestRunOptions, TriggerOption } from '../header/test-run-menu' +import { type TestRunOptions, type TriggerOption, TriggerType } from '../header/test-run-menu' import { TriggerAll } from '@/app/components/base/icons/src/vender/workflow' import BlockIcon from '../block-icon' import { useStore } from '../store' @@ -30,7 +30,7 @@ export const useDynamicTestRunOptions = (): TestRunOptions => { if (nodeData.type === BlockEnum.Start) { userInput = { id: node.id, - type: 'user_input', + type: TriggerType.UserInput, name: nodeData.title || t('workflow.blocks.start'), icon: ( { else if (nodeData.type === BlockEnum.TriggerSchedule) { allTriggers.push({ id: node.id, - type: 'schedule', + type: TriggerType.Schedule, name: nodeData.title || t('workflow.blocks.trigger-schedule'), icon: ( { else if (nodeData.type === BlockEnum.TriggerWebhook) { allTriggers.push({ id: node.id, - type: 'webhook', + type: TriggerType.Webhook, name: nodeData.title || t('workflow.blocks.trigger-webhook'), icon: ( { allTriggers.push({ id: node.id, - type: 'plugin', + type: TriggerType.Plugin, name: nodeData.title || (nodeData as any).plugin_name || t('workflow.blocks.trigger-plugin'), icon, nodeId: node.id, @@ -104,7 +104,7 @@ export const useDynamicTestRunOptions = (): TestRunOptions => { if (startNode && startNode.data?.type === BlockEnum.Start) { userInput = { id: startNode.id, - type: 'user_input', + type: TriggerType.UserInput, name: (startNode.data as CommonNodeType)?.title || t('workflow.blocks.start'), icon: ( { } } - const runAll: TriggerOption | undefined = allTriggers.length > 1 ? { + const triggerNodeIds = allTriggers + .map(trigger => trigger.nodeId) + .filter((nodeId): nodeId is string => Boolean(nodeId)) + + const runAll: TriggerOption | undefined = triggerNodeIds.length > 1 ? { id: 'run-all', - type: 'all', + type: TriggerType.All, name: t('workflow.common.runAllTriggers'), icon: (
), + relatedNodeIds: triggerNodeIds, enabled: true, } : undefined diff --git a/web/app/components/workflow/hooks/use-workflow-start-run.tsx b/web/app/components/workflow/hooks/use-workflow-start-run.tsx index 8cb1a45e2a..46fe5649c8 100644 --- a/web/app/components/workflow/hooks/use-workflow-start-run.tsx +++ b/web/app/components/workflow/hooks/use-workflow-start-run.tsx @@ -7,7 +7,7 @@ export const useWorkflowStartRun = () => { const handleWorkflowTriggerScheduleRunInWorkflow = useHooksStore(s => s.handleWorkflowTriggerScheduleRunInWorkflow) const handleWorkflowTriggerWebhookRunInWorkflow = useHooksStore(s => s.handleWorkflowTriggerWebhookRunInWorkflow) const handleWorkflowTriggerPluginRunInWorkflow = useHooksStore(s => s.handleWorkflowTriggerPluginRunInWorkflow) - + const handleWorkflowRunAllTriggersInWorkflow = useHooksStore(s => s.handleWorkflowRunAllTriggersInWorkflow) return { handleStartWorkflowRun, handleWorkflowStartRunInWorkflow, @@ -15,5 +15,6 @@ export const useWorkflowStartRun = () => { handleWorkflowTriggerScheduleRunInWorkflow, handleWorkflowTriggerWebhookRunInWorkflow, handleWorkflowTriggerPluginRunInWorkflow, + handleWorkflowRunAllTriggersInWorkflow, } }