From 2ff4af8ce32136d3d1f399375497c48fdc092154 Mon Sep 17 00:00:00 2001 From: hjlarry Date: Sun, 28 Sep 2025 16:37:15 +0800 Subject: [PATCH] add debug run schedule node --- api/controllers/console/app/workflow.py | 63 +++++++++++++++++++ api/services/app_generate_service.py | 4 +- .../workflow-app/components/workflow-main.tsx | 3 + .../workflow-app/hooks/use-workflow-run.ts | 46 +++++++++++--- .../hooks/use-workflow-start-run.tsx | 45 +++++++++++++ .../components/workflow/header/run-mode.tsx | 14 ++++- .../components/workflow/hooks-store/store.ts | 5 +- .../workflow/hooks/use-workflow-start-run.tsx | 2 + 8 files changed, 170 insertions(+), 12 deletions(-) diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index dfc2f539a6..524119c0bd 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -1151,3 +1151,66 @@ class DraftWorkflowTriggerRunApi(Resource): } ), 500 + +@console_ns.route("/apps//workflows/draft/trigger/schedule/run") +class DraftWorkflowTriggerScheduleRunApi(Resource): + """ + Full workflow debug when the start node is a schedule trigger + Path: /apps//workflows/draft/trigger/schedule/run + """ + + @api.doc("draft_workflow_trigger_schedule_run") + @api.doc(description="Full workflow debug when the start node is a schedule trigger") + @api.doc(params={"app_id": "Application ID"}) + @api.expect( + api.model( + "DraftWorkflowTriggerScheduleRunRequest", + { + "node_id": fields.String(required=True, description="Node ID"), + } + ) + ) + @api.response(200, "Workflow executed successfully") + @api.response(403, "Permission denied") + @api.response(500, "Internal server error") + @setup_required + @login_required + @account_initialization_required + @get_app_model(mode=[AppMode.WORKFLOW]) + def post(self, app_model: App): + """ + Full workflow debug when the start node is a schedule trigger + """ + if not isinstance(current_user, Account) or not current_user.has_edit_permission: + raise Forbidden() + + parser = reqparse.RequestParser() + parser.add_argument("node_id", type=str, required=True, location="json", nullable=False) + args = parser.parse_args() + node_id = args["node_id"] + + workflow_args = { + "inputs": {}, + "query": "", + "files": [], + } + + try: + response = AppGenerateService.generate( + app_model=app_model, + user=current_user, + args=workflow_args, + invoke_from=InvokeFrom.DEBUGGER, + streaming=True, + root_node_id=node_id + ) + return helper.compact_generate_response(response) + except InvokeRateLimitError as ex: + raise InvokeRateLimitHttpError(ex.description) + except Exception: + logger.exception("Error running draft workflow trigger schedule run") + return jsonable_encoder( + { + "status": "error", + } + ), 500 diff --git a/api/services/app_generate_service.py b/api/services/app_generate_service.py index 8911da4728..74ee93d19d 100644 --- a/api/services/app_generate_service.py +++ b/api/services/app_generate_service.py @@ -1,6 +1,6 @@ import uuid from collections.abc import Generator, Mapping -from typing import Any, Union +from typing import Any, Optional, Union from openai._exceptions import RateLimitError @@ -32,6 +32,7 @@ class AppGenerateService: args: Mapping[str, Any], invoke_from: InvokeFrom, streaming: bool = True, + root_node_id: Optional[str] = None, ): """ App Content Generate @@ -115,6 +116,7 @@ class AppGenerateService: args=args, invoke_from=invoke_from, streaming=streaming, + root_node_id=root_node_id, call_depth=0, ), ), diff --git a/web/app/components/workflow-app/components/workflow-main.tsx b/web/app/components/workflow-app/components/workflow-main.tsx index f979a12f26..7b6b803685 100644 --- a/web/app/components/workflow-app/components/workflow-main.tsx +++ b/web/app/components/workflow-app/components/workflow-main.tsx @@ -66,6 +66,7 @@ const WorkflowMain = ({ handleStartWorkflowRun, handleWorkflowStartRunInChatflow, handleWorkflowStartRunInWorkflow, + handleWorkflowTriggerScheduleRunInWorkflow, } = useWorkflowStartRun() const availableNodesMetaData = useAvailableNodesMetaData() const { getWorkflowRunAndTraceUrl } = useGetRunAndTraceUrl() @@ -108,6 +109,7 @@ const WorkflowMain = ({ handleStartWorkflowRun, handleWorkflowStartRunInChatflow, handleWorkflowStartRunInWorkflow, + handleWorkflowTriggerScheduleRunInWorkflow, availableNodesMetaData, getWorkflowRunAndTraceUrl, exportCheck, @@ -141,6 +143,7 @@ const WorkflowMain = ({ handleStartWorkflowRun, handleWorkflowStartRunInChatflow, handleWorkflowStartRunInWorkflow, + handleWorkflowTriggerScheduleRunInWorkflow, 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 72dc7fea2f..e11bb9c30d 100644 --- a/web/app/components/workflow-app/hooks/use-workflow-run.ts +++ b/web/app/components/workflow-app/hooks/use-workflow-run.ts @@ -23,6 +23,13 @@ import { useInvalidAllLastRun } from '@/service/use-workflow' import { useSetWorkflowVarsWithValue } from '../../workflow/hooks/use-fetch-workflow-inspect-vars' import { useConfigsMap } from './use-configs-map' +type HandleRunMode = 'default' | 'schedule' + +type HandleRunOptions = { + mode?: HandleRunMode + scheduleNodeId?: string +} + export const useWorkflowRun = () => { const store = useStoreApi() const workflowStore = useWorkflowStore() @@ -111,7 +118,10 @@ export const useWorkflowRun = () => { const handleRun = useCallback(async ( params: any, callback?: IOtherOptions, + options?: HandleRunOptions, ) => { + const runMode: HandleRunMode = options?.mode ?? 'default' + const resolvedParams = params ?? {} const { getNodes, setNodes, @@ -153,11 +163,31 @@ export const useWorkflowRun = () => { const isInWorkflowDebug = appDetail?.mode === 'workflow' let url = '' - if (appDetail?.mode === 'advanced-chat') + if (runMode === 'schedule') { + if (!appDetail?.id) { + console.error('handleRun: missing app id for schedule trigger run') + return + } + url = `/apps/${appDetail.id}/workflows/draft/trigger/schedule/run` + } + else if (appDetail?.mode === 'advanced-chat') { url = `/apps/${appDetail.id}/advanced-chat/workflows/draft/run` - - if (isInWorkflowDebug) + } + else if (isInWorkflowDebug && appDetail?.id) { url = `/apps/${appDetail.id}/workflows/draft/run` + } + + const requestBody = runMode === 'schedule' + ? { node_id: options?.scheduleNodeId } + : resolvedParams + + if (!url) + return + + if (runMode === 'schedule' && !options?.scheduleNodeId) { + console.error('handleRun: schedule trigger run requires node id') + return + } const { setWorkflowRunningData, @@ -172,22 +202,22 @@ export const useWorkflowRun = () => { let ttsUrl = '' let ttsIsPublic = false - if (params.token) { + if (resolvedParams.token) { ttsUrl = '/text-to-audio' ttsIsPublic = true } - else if (params.appId) { + else if (resolvedParams.appId) { if (pathname.search('explore/installed') > -1) - ttsUrl = `/installed-apps/${params.appId}/text-to-audio` + ttsUrl = `/installed-apps/${resolvedParams.appId}/text-to-audio` else - ttsUrl = `/apps/${params.appId}/text-to-audio` + ttsUrl = `/apps/${resolvedParams.appId}/text-to-audio` } const player = AudioPlayerManager.getInstance().getAudioPlayer(ttsUrl, ttsIsPublic, uuidV4(), 'none', 'none', noop) ssePost( url, { - body: params, + body: requestBody, }, { onWorkflowStarted: (params) => { 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 3f5ea1c1df..f16d817359 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 @@ -61,6 +61,50 @@ export const useWorkflowStartRun = () => { } }, [store, workflowStore, featuresStore, handleCancelDebugAndPreviewPanel, handleRun, doSyncWorkflowDraft]) + const handleWorkflowTriggerScheduleRunInWorkflow = useCallback(async (nodeId?: string) => { + if (!nodeId) + return + + const { + workflowRunningData, + showDebugAndPreviewPanel, + setShowDebugAndPreviewPanel, + setShowInputsPanel, + setShowEnvPanel, + } = workflowStore.getState() + + if (workflowRunningData?.result.status === WorkflowRunningStatus.Running) + return + + const { getNodes } = store.getState() + const nodes = getNodes() + const scheduleNode = nodes.find(node => node.id === nodeId && node.data.type === BlockEnum.TriggerSchedule) + + if (!scheduleNode) { + console.warn('handleWorkflowTriggerScheduleRunInWorkflow: schedule node not found', nodeId) + return + } + + setShowEnvPanel(false) + + if (showDebugAndPreviewPanel) { + handleCancelDebugAndPreviewPanel() + return + } + + await doSyncWorkflowDraft() + handleRun( + {}, + undefined, + { + mode: 'schedule', + scheduleNodeId: nodeId, + }, + ) + setShowDebugAndPreviewPanel(true) + setShowInputsPanel(false) + }, [store, workflowStore, handleCancelDebugAndPreviewPanel, handleRun, doSyncWorkflowDraft]) + const handleWorkflowStartRunInChatflow = useCallback(async () => { const { showDebugAndPreviewPanel, @@ -92,5 +136,6 @@ export const useWorkflowStartRun = () => { handleStartWorkflowRun, handleWorkflowStartRunInWorkflow, handleWorkflowStartRunInChatflow, + handleWorkflowTriggerScheduleRunInWorkflow, } } diff --git a/web/app/components/workflow/header/run-mode.tsx b/web/app/components/workflow/header/run-mode.tsx index a6612ec8df..afb7dbd86a 100644 --- a/web/app/components/workflow/header/run-mode.tsx +++ b/web/app/components/workflow/header/run-mode.tsx @@ -20,7 +20,10 @@ const RunMode = ({ text, }: RunModeProps) => { const { t } = useTranslation() - const { handleWorkflowStartRunInWorkflow } = useWorkflowStartRun() + const { + handleWorkflowStartRunInWorkflow, + handleWorkflowTriggerScheduleRunInWorkflow, + } = useWorkflowStartRun() const { handleStopRun } = useWorkflowRun() const { validateBeforeRun } = useWorkflowRunValidation() const workflowRunningData = useStore(s => s.workflowRunningData) @@ -53,11 +56,18 @@ const RunMode = ({ if (option.type === 'user_input') { handleWorkflowStartRunInWorkflow() } + else if (option.type === 'schedule') { + handleWorkflowTriggerScheduleRunInWorkflow(option.nodeId) + } 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) } - }, [validateBeforeRun, handleWorkflowStartRunInWorkflow]) + }, [ + validateBeforeRun, + handleWorkflowStartRunInWorkflow, + handleWorkflowTriggerScheduleRunInWorkflow, + ]) const { eventEmitter } = useEventEmitterContextContext() eventEmitter?.useSubscription((v: any) => { diff --git a/web/app/components/workflow/hooks-store/store.ts b/web/app/components/workflow/hooks-store/store.ts index 562faee06b..8ae3739f8a 100644 --- a/web/app/components/workflow/hooks-store/store.ts +++ b/web/app/components/workflow/hooks-store/store.ts @@ -40,11 +40,12 @@ export type CommonHooksFnMap = { handleBackupDraft: () => void handleLoadBackupDraft: () => void handleRestoreFromPublishedWorkflow: (...args: any[]) => void - handleRun: (params: any, callback?: IOtherOptions,) => void + handleRun: (params: any, callback?: IOtherOptions, options?: any) => void handleStopRun: (...args: any[]) => void handleStartWorkflowRun: () => void handleWorkflowStartRunInWorkflow: () => void handleWorkflowStartRunInChatflow: () => void + handleWorkflowTriggerScheduleRunInWorkflow: (nodeId?: string) => void availableNodesMetaData?: AvailableNodesMetaData getWorkflowRunAndTraceUrl: (runId?: string) => { runUrl: string; traceUrl: string } exportCheck?: () => Promise @@ -87,6 +88,7 @@ export const createHooksStore = ({ handleStartWorkflowRun = noop, handleWorkflowStartRunInWorkflow = noop, handleWorkflowStartRunInChatflow = noop, + handleWorkflowTriggerScheduleRunInWorkflow = noop, availableNodesMetaData = { nodes: [], }, @@ -125,6 +127,7 @@ export const createHooksStore = ({ handleStartWorkflowRun, handleWorkflowStartRunInWorkflow, handleWorkflowStartRunInChatflow, + handleWorkflowTriggerScheduleRunInWorkflow, availableNodesMetaData, getWorkflowRunAndTraceUrl, exportCheck, 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 0f4e68fe95..3e07a9aae4 100644 --- a/web/app/components/workflow/hooks/use-workflow-start-run.tsx +++ b/web/app/components/workflow/hooks/use-workflow-start-run.tsx @@ -4,10 +4,12 @@ export const useWorkflowStartRun = () => { const handleStartWorkflowRun = useHooksStore(s => s.handleStartWorkflowRun) const handleWorkflowStartRunInWorkflow = useHooksStore(s => s.handleWorkflowStartRunInWorkflow) const handleWorkflowStartRunInChatflow = useHooksStore(s => s.handleWorkflowStartRunInChatflow) + const handleWorkflowTriggerScheduleRunInWorkflow = useHooksStore(s => s.handleWorkflowTriggerScheduleRunInWorkflow) return { handleStartWorkflowRun, handleWorkflowStartRunInWorkflow, handleWorkflowStartRunInChatflow, + handleWorkflowTriggerScheduleRunInWorkflow, } }