11"use client" ;
22
3- import { useAtom , useSetAtom } from "jotai" ;
3+ import { useAtom , useAtomValue , useSetAtom } from "jotai" ;
44import { ChevronLeft , ChevronRight } from "lucide-react" ;
55import Link from "next/link" ;
66import { useSearchParams } from "next/navigation" ;
@@ -10,6 +10,12 @@ import { Button } from "@/components/ui/button";
1010import { NodeConfigPanel } from "@/components/workflow/node-config-panel" ;
1111import { useIsMobile } from "@/hooks/use-mobile" ;
1212import { api } from "@/lib/api-client" ;
13+ import {
14+ integrationsAtom ,
15+ integrationsLoadedAtom ,
16+ integrationsVersionAtom ,
17+ } from "@/lib/integrations-store" ;
18+ import type { IntegrationType } from "@/lib/types/integration" ;
1319import {
1420 currentWorkflowIdAtom ,
1521 currentWorkflowNameAtom ,
@@ -25,18 +31,77 @@ import {
2531 nodesAtom ,
2632 rightPanelWidthAtom ,
2733 selectedExecutionIdAtom ,
28- selectedNodeAtom ,
2934 triggerExecuteAtom ,
3035 updateNodeDataAtom ,
3136 type WorkflowNode ,
3237 type WorkflowVisibility ,
3338 workflowNotFoundAtom ,
3439} from "@/lib/workflow-store" ;
40+ import { findActionById } from "@/plugins" ;
3541
3642type WorkflowPageProps = {
3743 params : Promise < { workflowId : string } > ;
3844} ;
3945
46+ // System actions that need integrations (not in plugin registry)
47+ const SYSTEM_ACTION_INTEGRATIONS : Record < string , IntegrationType > = {
48+ "Database Query" : "database" ,
49+ } ;
50+
51+ // Helper to get required integration type for an action
52+ function getRequiredIntegrationType (
53+ actionType : string
54+ ) : IntegrationType | undefined {
55+ const action = findActionById ( actionType ) ;
56+ return (
57+ ( action ?. integration as IntegrationType | undefined ) ||
58+ SYSTEM_ACTION_INTEGRATIONS [ actionType ]
59+ ) ;
60+ }
61+
62+ // Helper to check and fix a single node's integration
63+ type IntegrationFixResult = {
64+ nodeId : string ;
65+ newIntegrationId : string | undefined ;
66+ } ;
67+
68+ function checkNodeIntegration (
69+ node : WorkflowNode ,
70+ allIntegrations : { id : string ; type : string } [ ] ,
71+ validIntegrationIds : Set < string >
72+ ) : IntegrationFixResult | null {
73+ const actionType = node . data . config ?. actionType as string | undefined ;
74+ if ( ! actionType ) {
75+ return null ;
76+ }
77+
78+ const integrationType = getRequiredIntegrationType ( actionType ) ;
79+ if ( ! integrationType ) {
80+ return null ;
81+ }
82+
83+ const currentIntegrationId = node . data . config ?. integrationId as
84+ | string
85+ | undefined ;
86+ const hasValidIntegration =
87+ currentIntegrationId && validIntegrationIds . has ( currentIntegrationId ) ;
88+
89+ if ( hasValidIntegration ) {
90+ return null ;
91+ }
92+
93+ // Find available integrations of this type
94+ const available = allIntegrations . filter ( ( i ) => i . type === integrationType ) ;
95+
96+ if ( available . length === 1 ) {
97+ return { nodeId : node . id , newIntegrationId : available [ 0 ] . id } ;
98+ }
99+ if ( available . length === 0 && currentIntegrationId ) {
100+ return { nodeId : node . id , newIntegrationId : undefined } ;
101+ }
102+ return null ;
103+ }
104+
40105const WorkflowEditor = ( { params } : WorkflowPageProps ) => {
41106 const { workflowId } = use ( params ) ;
42107 const searchParams = useSearchParams ( ) ;
@@ -52,7 +117,6 @@ const WorkflowEditor = ({ params }: WorkflowPageProps) => {
52117 const setCurrentWorkflowId = useSetAtom ( currentWorkflowIdAtom ) ;
53118 const setCurrentWorkflowName = useSetAtom ( currentWorkflowNameAtom ) ;
54119 const updateNodeData = useSetAtom ( updateNodeDataAtom ) ;
55- const setSelectedNodeId = useSetAtom ( selectedNodeAtom ) ;
56120 const setHasUnsavedChanges = useSetAtom ( hasUnsavedChangesAtom ) ;
57121 const [ workflowNotFound , setWorkflowNotFound ] = useAtom ( workflowNotFoundAtom ) ;
58122 const setTriggerExecute = useSetAtom ( triggerExecuteAtom ) ;
@@ -66,6 +130,9 @@ const WorkflowEditor = ({ params }: WorkflowPageProps) => {
66130 currentWorkflowVisibilityAtom
67131 ) ;
68132 const setIsWorkflowOwner = useSetAtom ( isWorkflowOwnerAtom ) ;
133+ const setGlobalIntegrations = useSetAtom ( integrationsAtom ) ;
134+ const setIntegrationsLoaded = useSetAtom ( integrationsLoadedAtom ) ;
135+ const integrationsVersion = useAtomValue ( integrationsVersionAtom ) ;
69136
70137 // Panel width state for resizing
71138 const [ panelWidth , setPanelWidth ] = useState ( 30 ) ; // default percentage
@@ -236,17 +303,14 @@ const WorkflowEditor = ({ params }: WorkflowPageProps) => {
236303 try {
237304 const workflowData = await api . ai . generate ( prompt ) ;
238305
239- setNodes ( workflowData . nodes || [ ] ) ;
306+ // Clear selection on all nodes
307+ const nodesWithoutSelection = ( workflowData . nodes || [ ] ) . map (
308+ ( node : WorkflowNode ) => ( { ...node , selected : false } )
309+ ) ;
310+ setNodes ( nodesWithoutSelection ) ;
240311 setEdges ( workflowData . edges || [ ] ) ;
241312 setCurrentWorkflowName ( workflowData . name || "AI Generated Workflow" ) ;
242313
243- const selectedNode = workflowData . nodes ?. find (
244- ( n : { selected ?: boolean } ) => n . selected
245- ) ;
246- if ( selectedNode ) {
247- setSelectedNodeId ( selectedNode . id ) ;
248- }
249-
250314 await api . workflow . update ( workflowId , {
251315 name : workflowData . name ,
252316 description : workflowData . description ,
@@ -267,7 +331,6 @@ const WorkflowEditor = ({ params }: WorkflowPageProps) => {
267331 setCurrentWorkflowName ,
268332 setNodes ,
269333 setEdges ,
270- setSelectedNodeId ,
271334 ]
272335 ) ;
273336
@@ -281,9 +344,10 @@ const WorkflowEditor = ({ params }: WorkflowPageProps) => {
281344 return ;
282345 }
283346
284- // Reset all node statuses to idle when loading from database
347+ // Reset node statuses to idle and clear selection when loading from database
285348 const nodesWithIdleStatus = workflow . nodes . map ( ( node : WorkflowNode ) => ( {
286349 ...node ,
350+ selected : false ,
287351 data : {
288352 ...node . data ,
289353 status : "idle" as const ,
@@ -300,11 +364,6 @@ const WorkflowEditor = ({ params }: WorkflowPageProps) => {
300364 setIsWorkflowOwner ( workflow . isOwner !== false ) ; // Default to true if not set
301365 setHasUnsavedChanges ( false ) ;
302366 setWorkflowNotFound ( false ) ;
303-
304- const selectedNode = workflow . nodes . find ( ( n : WorkflowNode ) => n . selected ) ;
305- if ( selectedNode ) {
306- setSelectedNodeId ( selectedNode . id ) ;
307- }
308367 } catch ( error ) {
309368 console . error ( "Failed to load workflow:" , error ) ;
310369 toast . error ( "Failed to load workflow" ) ;
@@ -319,9 +378,13 @@ const WorkflowEditor = ({ params }: WorkflowPageProps) => {
319378 setIsWorkflowOwner ,
320379 setHasUnsavedChanges ,
321380 setWorkflowNotFound ,
322- setSelectedNodeId ,
323381 ] ) ;
324382
383+ // Track if we've already auto-fixed integrations for this workflow+version
384+ const lastAutoFixRef = useRef < { workflowId : string ; version : number } | null > (
385+ null
386+ ) ;
387+
325388 useEffect ( ( ) => {
326389 const loadWorkflowData = async ( ) => {
327390 const isGeneratingParam = searchParams ?. get ( "generating" ) === "true" ;
@@ -357,6 +420,72 @@ const WorkflowEditor = ({ params }: WorkflowPageProps) => {
357420 loadExistingWorkflow ,
358421 ] ) ;
359422
423+ // Auto-fix invalid/missing integrations on workflow load or when integrations change
424+ useEffect ( ( ) => {
425+ // Skip if no nodes or no workflow
426+ if ( nodes . length === 0 || ! currentWorkflowId ) {
427+ return ;
428+ }
429+
430+ // Skip if already checked for this workflow+version combination
431+ const lastFix = lastAutoFixRef . current ;
432+ if (
433+ lastFix &&
434+ lastFix . workflowId === currentWorkflowId &&
435+ lastFix . version === integrationsVersion
436+ ) {
437+ return ;
438+ }
439+
440+ const autoFixIntegrations = async ( ) => {
441+ try {
442+ const allIntegrations = await api . integration . getAll ( ) ;
443+ setGlobalIntegrations ( allIntegrations ) ;
444+ setIntegrationsLoaded ( true ) ;
445+
446+ const validIds = new Set ( allIntegrations . map ( ( i ) => i . id ) ) ;
447+ const fixes = nodes
448+ . map ( ( node ) => checkNodeIntegration ( node , allIntegrations , validIds ) )
449+ . filter ( ( fix ) : fix is IntegrationFixResult => fix !== null ) ;
450+
451+ for ( const fix of fixes ) {
452+ const node = nodes . find ( ( n ) => n . id === fix . nodeId ) ;
453+ if ( node ) {
454+ updateNodeData ( {
455+ id : fix . nodeId ,
456+ data : {
457+ config : {
458+ ...node . data . config ,
459+ integrationId : fix . newIntegrationId ,
460+ } ,
461+ } ,
462+ } ) ;
463+ }
464+ }
465+
466+ lastAutoFixRef . current = {
467+ workflowId : currentWorkflowId ,
468+ version : integrationsVersion ,
469+ } ;
470+ if ( fixes . length > 0 ) {
471+ setHasUnsavedChanges ( true ) ;
472+ }
473+ } catch ( error ) {
474+ console . error ( "Failed to auto-fix integrations:" , error ) ;
475+ }
476+ } ;
477+
478+ autoFixIntegrations ( ) ;
479+ } , [
480+ nodes ,
481+ currentWorkflowId ,
482+ integrationsVersion ,
483+ updateNodeData ,
484+ setGlobalIntegrations ,
485+ setIntegrationsLoaded ,
486+ setHasUnsavedChanges ,
487+ ] ) ;
488+
360489 // Keyboard shortcuts
361490 const handleSave = useCallback ( async ( ) => {
362491 if ( ! currentWorkflowId || isGenerating ) {
0 commit comments