@@ -16,16 +16,15 @@ import {
1616 edgesAtom ,
1717 hasSidebarBeenShownAtom ,
1818 hasUnsavedChangesAtom ,
19- isExecutingAtom ,
2019 isGeneratingAtom ,
2120 isPanelAnimatingAtom ,
2221 isSavingAtom ,
2322 isSidebarCollapsedAtom ,
2423 nodesAtom ,
25- propertiesPanelActiveTabAtom ,
2624 rightPanelWidthAtom ,
2725 selectedExecutionIdAtom ,
2826 selectedNodeAtom ,
27+ triggerExecuteAtom ,
2928 updateNodeDataAtom ,
3029 type WorkflowNode ,
3130 workflowNotFoundAtom ,
@@ -40,14 +39,11 @@ const WorkflowEditor = ({ params }: WorkflowPageProps) => {
4039 const searchParams = useSearchParams ( ) ;
4140 const isMobile = useIsMobile ( ) ;
4241 const [ isGenerating , setIsGenerating ] = useAtom ( isGeneratingAtom ) ;
43- const [ isExecuting , setIsExecuting ] = useAtom ( isExecutingAtom ) ;
4442 const [ _isSaving , setIsSaving ] = useAtom ( isSavingAtom ) ;
4543 const [ nodes ] = useAtom ( nodesAtom ) ;
4644 const [ edges ] = useAtom ( edgesAtom ) ;
4745 const [ currentWorkflowId ] = useAtom ( currentWorkflowIdAtom ) ;
48- const [ selectedExecutionId , setSelectedExecutionId ] = useAtom (
49- selectedExecutionIdAtom
50- ) ;
46+ const [ selectedExecutionId ] = useAtom ( selectedExecutionIdAtom ) ;
5147 const setNodes = useSetAtom ( nodesAtom ) ;
5248 const setEdges = useSetAtom ( edgesAtom ) ;
5349 const setCurrentWorkflowId = useSetAtom ( currentWorkflowIdAtom ) ;
@@ -56,7 +52,7 @@ const WorkflowEditor = ({ params }: WorkflowPageProps) => {
5652 const setSelectedNodeId = useSetAtom ( selectedNodeAtom ) ;
5753 const setHasUnsavedChanges = useSetAtom ( hasUnsavedChangesAtom ) ;
5854 const [ workflowNotFound , setWorkflowNotFound ] = useAtom ( workflowNotFoundAtom ) ;
59- const setActiveTab = useSetAtom ( propertiesPanelActiveTabAtom ) ;
55+ const setTriggerExecute = useSetAtom ( triggerExecuteAtom ) ;
6056 const setRightPanelWidth = useSetAtom ( rightPanelWidthAtom ) ;
6157 const setIsPanelAnimating = useSetAtom ( isPanelAnimatingAtom ) ;
6258 const [ hasSidebarBeenShown , setHasSidebarBeenShown ] = useAtom (
@@ -372,144 +368,6 @@ const WorkflowEditor = ({ params }: WorkflowPageProps) => {
372368 setHasUnsavedChanges ,
373369 ] ) ;
374370
375- // Helper to update node statuses
376- const updateAllNodeStatuses = useCallback (
377- ( status : "idle" | "error" | "success" ) => {
378- for ( const node of nodes ) {
379- updateNodeData ( { id : node . id , data : { status } } ) ;
380- }
381- } ,
382- [ nodes , updateNodeData ]
383- ) ;
384-
385- // Helper to poll execution status
386- const pollExecutionStatus = useCallback (
387- async ( executionId : string ) => {
388- try {
389- const statusData = await api . workflow . getExecutionStatus ( executionId ) ;
390-
391- // Update node statuses based on the execution logs
392- for ( const nodeStatus of statusData . nodeStatuses ) {
393- updateNodeData ( {
394- id : nodeStatus . nodeId ,
395- data : {
396- status : nodeStatus . status as
397- | "idle"
398- | "running"
399- | "success"
400- | "error" ,
401- } ,
402- } ) ;
403- }
404-
405- // Return status and whether execution is complete
406- return {
407- isComplete : statusData . status !== "running" ,
408- status : statusData . status ,
409- } ;
410- } catch ( error ) {
411- console . error ( "Failed to poll execution status:" , error ) ;
412- return { isComplete : false , status : "running" } ;
413- }
414- } ,
415- [ updateNodeData ]
416- ) ;
417-
418- const handleRun = useCallback ( async ( ) => {
419- if (
420- isExecuting ||
421- nodes . length === 0 ||
422- isGenerating ||
423- ! currentWorkflowId
424- ) {
425- return ;
426- }
427-
428- // Switch to Runs tab when starting a test run
429- setActiveTab ( "runs" ) ;
430-
431- // Deselect all nodes and edges
432- setNodes ( nodes . map ( ( node ) => ( { ...node , selected : false } ) ) ) ;
433- setEdges ( edges . map ( ( edge ) => ( { ...edge , selected : false } ) ) ) ;
434- setSelectedNodeId ( null ) ;
435-
436- setIsExecuting ( true ) ;
437-
438- // Set all nodes to idle first
439- updateAllNodeStatuses ( "idle" ) ;
440-
441- // Immediately set trigger nodes to running for instant visual feedback
442- for ( const node of nodes ) {
443- if ( node . data . type === "trigger" ) {
444- updateNodeData ( { id : node . id , data : { status : "running" } } ) ;
445- }
446- }
447-
448- try {
449- // Start the execution via API
450- const response = await fetch (
451- `/api/workflow/${ currentWorkflowId } /execute` ,
452- {
453- method : "POST" ,
454- headers : {
455- "Content-Type" : "application/json" ,
456- } ,
457- body : JSON . stringify ( { input : { } } ) ,
458- }
459- ) ;
460-
461- if ( ! response . ok ) {
462- throw new Error ( "Failed to execute workflow" ) ;
463- }
464-
465- const result = await response . json ( ) ;
466-
467- // Select the new execution
468- setSelectedExecutionId ( result . executionId ) ;
469-
470- // Poll for execution status updates
471- const pollInterval = setInterval ( async ( ) => {
472- const { isComplete } = await pollExecutionStatus ( result . executionId ) ;
473-
474- if ( isComplete ) {
475- if ( executionPollingIntervalRef . current ) {
476- clearInterval ( executionPollingIntervalRef . current ) ;
477- executionPollingIntervalRef . current = null ;
478- }
479-
480- setIsExecuting ( false ) ;
481-
482- // Don't reset node statuses - let them show the final state
483- // The user can click another run or deselect to reset
484- }
485- } , 500 ) ; // Poll every 500ms
486-
487- executionPollingIntervalRef . current = pollInterval ;
488- } catch ( error ) {
489- console . error ( "Failed to execute workflow:" , error ) ;
490- toast . error (
491- error instanceof Error ? error . message : "Failed to execute workflow"
492- ) ;
493- updateAllNodeStatuses ( "error" ) ;
494- setIsExecuting ( false ) ;
495- }
496- } , [
497- isExecuting ,
498- nodes ,
499- edges ,
500- isGenerating ,
501- currentWorkflowId ,
502- setIsExecuting ,
503- updateAllNodeStatuses ,
504- pollExecutionStatus ,
505- updateNodeData ,
506- setActiveTab ,
507- setNodes ,
508- setEdges ,
509- setSelectedNodeId ,
510- setSelectedExecutionId ,
511- ] ) ;
512-
513371 // Helper to check if target is an input element
514372 const isInputElement = useCallback (
515373 ( target : HTMLElement ) =>
@@ -538,19 +396,22 @@ const WorkflowEditor = ({ params }: WorkflowPageProps) => {
538396 ) ;
539397
540398 // Helper to handle run shortcut
399+ // Uses triggerExecuteAtom to share the same execute flow as the Run button
400+ // This ensures keyboard shortcut goes through the same checks (e.g., missing integrations)
541401 const handleRunShortcut = useCallback (
542402 ( e : KeyboardEvent , target : HTMLElement ) => {
543403 if ( ( e . metaKey || e . ctrlKey ) && e . key === "Enter" ) {
544404 if ( ! ( isInputElement ( target ) || isInMonacoEditor ( target ) ) ) {
545405 e . preventDefault ( ) ;
546406 e . stopPropagation ( ) ;
547- handleRun ( ) ;
407+ // Trigger execute via atom - the toolbar will handle it
408+ setTriggerExecute ( true ) ;
548409 }
549410 return true ;
550411 }
551412 return false ;
552413 } ,
553- [ handleRun , isInputElement , isInMonacoEditor ]
414+ [ setTriggerExecute , isInputElement , isInMonacoEditor ]
554415 ) ;
555416
556417 useEffect ( ( ) => {
0 commit comments