@marktoflow/gui 2.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/.turbo/turbo-build.log +26 -0
  2. package/.turbo/turbo-test.log +22 -0
  3. package/README.md +179 -0
  4. package/dist/client/assets/index-DwTI8opO.js +608 -0
  5. package/dist/client/assets/index-DwTI8opO.js.map +1 -0
  6. package/dist/client/assets/index-RoEdL6gO.css +1 -0
  7. package/dist/client/index.html +20 -0
  8. package/dist/client/vite.svg +9 -0
  9. package/dist/server/index.d.ts +3 -0
  10. package/dist/server/index.d.ts.map +1 -0
  11. package/dist/server/index.js +56 -0
  12. package/dist/server/index.js.map +1 -0
  13. package/dist/server/routes/ai.js +50 -0
  14. package/dist/server/routes/ai.js.map +1 -0
  15. package/dist/server/routes/execute.js +62 -0
  16. package/dist/server/routes/execute.js.map +1 -0
  17. package/dist/server/routes/workflows.js +99 -0
  18. package/dist/server/routes/workflows.js.map +1 -0
  19. package/dist/server/server/index.js +95 -0
  20. package/dist/server/server/index.js.map +1 -0
  21. package/dist/server/server/routes/ai.js +87 -0
  22. package/dist/server/server/routes/ai.js.map +1 -0
  23. package/dist/server/server/routes/execute.js +63 -0
  24. package/dist/server/server/routes/execute.js.map +1 -0
  25. package/dist/server/server/routes/tools.js +518 -0
  26. package/dist/server/server/routes/tools.js.map +1 -0
  27. package/dist/server/server/routes/workflows.js +99 -0
  28. package/dist/server/server/routes/workflows.js.map +1 -0
  29. package/dist/server/server/services/AIService.js +69 -0
  30. package/dist/server/server/services/AIService.js.map +1 -0
  31. package/dist/server/server/services/FileWatcher.js +60 -0
  32. package/dist/server/server/services/FileWatcher.js.map +1 -0
  33. package/dist/server/server/services/WorkflowService.js +363 -0
  34. package/dist/server/server/services/WorkflowService.js.map +1 -0
  35. package/dist/server/server/services/agents/claude-code-provider.js +250 -0
  36. package/dist/server/server/services/agents/claude-code-provider.js.map +1 -0
  37. package/dist/server/server/services/agents/claude-provider.js +204 -0
  38. package/dist/server/server/services/agents/claude-provider.js.map +1 -0
  39. package/dist/server/server/services/agents/copilot-provider.js +227 -0
  40. package/dist/server/server/services/agents/copilot-provider.js.map +1 -0
  41. package/dist/server/server/services/agents/demo-provider.js +167 -0
  42. package/dist/server/server/services/agents/demo-provider.js.map +1 -0
  43. package/dist/server/server/services/agents/index.js +31 -0
  44. package/dist/server/server/services/agents/index.js.map +1 -0
  45. package/dist/server/server/services/agents/ollama-provider.js +220 -0
  46. package/dist/server/server/services/agents/ollama-provider.js.map +1 -0
  47. package/dist/server/server/services/agents/prompts.js +436 -0
  48. package/dist/server/server/services/agents/prompts.js.map +1 -0
  49. package/dist/server/server/services/agents/registry.js +242 -0
  50. package/dist/server/server/services/agents/registry.js.map +1 -0
  51. package/dist/server/server/services/agents/types.js +6 -0
  52. package/dist/server/server/services/agents/types.js.map +1 -0
  53. package/dist/server/server/websocket/index.js +85 -0
  54. package/dist/server/server/websocket/index.js.map +1 -0
  55. package/dist/server/services/AIService.d.ts +30 -0
  56. package/dist/server/services/AIService.d.ts.map +1 -0
  57. package/dist/server/services/AIService.js +216 -0
  58. package/dist/server/services/AIService.js.map +1 -0
  59. package/dist/server/services/FileWatcher.d.ts +10 -0
  60. package/dist/server/services/FileWatcher.d.ts.map +1 -0
  61. package/dist/server/services/FileWatcher.js +62 -0
  62. package/dist/server/services/FileWatcher.js.map +1 -0
  63. package/dist/server/services/WorkflowService.d.ts +54 -0
  64. package/dist/server/services/WorkflowService.d.ts.map +1 -0
  65. package/dist/server/services/WorkflowService.js +323 -0
  66. package/dist/server/services/WorkflowService.js.map +1 -0
  67. package/dist/server/shared/constants.js +175 -0
  68. package/dist/server/shared/constants.js.map +1 -0
  69. package/dist/server/shared/types.js +3 -0
  70. package/dist/server/shared/types.js.map +1 -0
  71. package/dist/server/websocket/index.d.ts +10 -0
  72. package/dist/server/websocket/index.d.ts.map +1 -0
  73. package/dist/server/websocket/index.js +85 -0
  74. package/dist/server/websocket/index.js.map +1 -0
  75. package/index.html +19 -0
  76. package/package.json +96 -0
  77. package/playwright.config.ts +27 -0
  78. package/postcss.config.js +6 -0
  79. package/public/vite.svg +9 -0
  80. package/src/client/App.tsx +520 -0
  81. package/src/client/components/Canvas/Canvas.tsx +405 -0
  82. package/src/client/components/Canvas/ExecutionOverlay.tsx +847 -0
  83. package/src/client/components/Canvas/NodeContextMenu.tsx +188 -0
  84. package/src/client/components/Canvas/OutputNode.tsx +111 -0
  85. package/src/client/components/Canvas/StepNode.tsx +106 -0
  86. package/src/client/components/Canvas/SubWorkflowNode.tsx +141 -0
  87. package/src/client/components/Canvas/Toolbar.tsx +189 -0
  88. package/src/client/components/Canvas/TriggerNode.tsx +128 -0
  89. package/src/client/components/Editor/InputsEditor.tsx +458 -0
  90. package/src/client/components/Editor/NewStepWizard.tsx +344 -0
  91. package/src/client/components/Editor/StepEditor.tsx +532 -0
  92. package/src/client/components/Editor/YamlEditor.tsx +160 -0
  93. package/src/client/components/Panels/PropertiesPanel.tsx +589 -0
  94. package/src/client/components/Prompt/ChangePreview.tsx +281 -0
  95. package/src/client/components/Prompt/PromptHistoryPanel.tsx +209 -0
  96. package/src/client/components/Prompt/PromptInput.tsx +108 -0
  97. package/src/client/components/Sidebar/Sidebar.tsx +343 -0
  98. package/src/client/components/common/Breadcrumb.tsx +40 -0
  99. package/src/client/components/common/Button.tsx +68 -0
  100. package/src/client/components/common/ContextMenu.tsx +202 -0
  101. package/src/client/components/common/KeyboardShortcuts.tsx +143 -0
  102. package/src/client/components/common/Modal.tsx +93 -0
  103. package/src/client/components/common/Tabs.tsx +57 -0
  104. package/src/client/components/common/ThemeToggle.tsx +63 -0
  105. package/src/client/components/index.ts +32 -0
  106. package/src/client/hooks/index.ts +4 -0
  107. package/src/client/hooks/useAIPrompt.ts +108 -0
  108. package/src/client/hooks/useCanvas.ts +247 -0
  109. package/src/client/hooks/useWebSocket.ts +164 -0
  110. package/src/client/hooks/useWorkflow.ts +138 -0
  111. package/src/client/main.tsx +10 -0
  112. package/src/client/stores/canvasStore.ts +348 -0
  113. package/src/client/stores/editorStore.ts +133 -0
  114. package/src/client/stores/executionStore.ts +440 -0
  115. package/src/client/stores/index.ts +4 -0
  116. package/src/client/stores/layoutStore.ts +103 -0
  117. package/src/client/stores/navigationStore.ts +49 -0
  118. package/src/client/stores/promptStore.ts +113 -0
  119. package/src/client/stores/themeStore.ts +75 -0
  120. package/src/client/stores/workflowStore.ts +177 -0
  121. package/src/client/styles/globals.css +346 -0
  122. package/src/client/utils/cn.ts +9 -0
  123. package/src/client/utils/index.ts +4 -0
  124. package/src/client/utils/serviceIcons.tsx +64 -0
  125. package/src/client/utils/stepValidation.ts +155 -0
  126. package/src/client/utils/workflowToGraph.ts +299 -0
  127. package/src/server/index.ts +114 -0
  128. package/src/server/routes/ai.ts +91 -0
  129. package/src/server/routes/execute.ts +71 -0
  130. package/src/server/routes/tools.ts +564 -0
  131. package/src/server/routes/workflows.ts +106 -0
  132. package/src/server/services/AIService.ts +105 -0
  133. package/src/server/services/FileWatcher.ts +69 -0
  134. package/src/server/services/WorkflowService.ts +441 -0
  135. package/src/server/services/agents/claude-code-provider.ts +320 -0
  136. package/src/server/services/agents/claude-provider.ts +248 -0
  137. package/src/server/services/agents/copilot-provider.ts +311 -0
  138. package/src/server/services/agents/demo-provider.ts +184 -0
  139. package/src/server/services/agents/index.ts +31 -0
  140. package/src/server/services/agents/ollama-provider.ts +267 -0
  141. package/src/server/services/agents/prompts.ts +482 -0
  142. package/src/server/services/agents/registry.ts +289 -0
  143. package/src/server/services/agents/types.ts +146 -0
  144. package/src/server/websocket/index.ts +104 -0
  145. package/src/shared/constants.ts +180 -0
  146. package/src/shared/types.ts +179 -0
  147. package/tailwind.config.ts +73 -0
  148. package/tests/e2e/app.spec.ts +90 -0
  149. package/tests/e2e/canvas.spec.ts +128 -0
  150. package/tests/e2e/workflow.spec.ts +185 -0
  151. package/tests/integration/api.test.ts +250 -0
  152. package/tests/integration/testApp.ts +31 -0
  153. package/tests/setup.ts +37 -0
  154. package/tests/unit/canvasStore.test.ts +502 -0
  155. package/tests/unit/components.test.tsx +151 -0
  156. package/tests/unit/executionStore.test.ts +527 -0
  157. package/tests/unit/layoutStore.test.ts +194 -0
  158. package/tests/unit/navigationStore.test.ts +152 -0
  159. package/tests/unit/stepValidation.test.ts +226 -0
  160. package/tests/unit/themeStore.test.ts +141 -0
  161. package/tests/unit/workflowToGraph.test.ts +289 -0
  162. package/tsconfig.json +29 -0
  163. package/tsconfig.server.json +28 -0
  164. package/vite.config.ts +31 -0
  165. package/vitest.config.ts +26 -0
@@ -0,0 +1,188 @@
1
+ import {
2
+ ContextMenu,
3
+ ContextMenuContent,
4
+ ContextMenuItem,
5
+ ContextMenuSeparator,
6
+ ContextMenuSub,
7
+ ContextMenuSubContent,
8
+ ContextMenuSubTrigger,
9
+ ContextMenuShortcut,
10
+ } from '../common/ContextMenu';
11
+ import {
12
+ Edit,
13
+ Code,
14
+ FileText,
15
+ Copy,
16
+ Trash2,
17
+ Plus,
18
+ FolderOpen,
19
+ Play,
20
+ AlertTriangle,
21
+ } from 'lucide-react';
22
+ import type { Node } from '@xyflow/react';
23
+
24
+ interface NodeContextMenuProps {
25
+ children: React.ReactNode;
26
+ node: Node;
27
+ onEdit: () => void;
28
+ onViewYaml: () => void;
29
+ onViewDocs: () => void;
30
+ onDuplicate: () => void;
31
+ onDelete: () => void;
32
+ onAddStepBefore: () => void;
33
+ onAddStepAfter: () => void;
34
+ onConvertToSubworkflow: () => void;
35
+ onExecuteFrom: () => void;
36
+ }
37
+
38
+ export function NodeContextMenu({
39
+ children,
40
+ node,
41
+ onEdit,
42
+ onViewYaml,
43
+ onViewDocs,
44
+ onDuplicate,
45
+ onDelete,
46
+ onAddStepBefore,
47
+ onAddStepAfter,
48
+ onConvertToSubworkflow,
49
+ onExecuteFrom,
50
+ }: NodeContextMenuProps) {
51
+ const isSubworkflow = node.type === 'subworkflow';
52
+ const hasError = node.data?.status === 'failed';
53
+
54
+ return (
55
+ <ContextMenu>
56
+ {children}
57
+ <ContextMenuContent>
58
+ <ContextMenuItem onClick={onEdit}>
59
+ <Edit className="w-4 h-4 mr-2" />
60
+ Edit Step
61
+ <ContextMenuShortcut>E</ContextMenuShortcut>
62
+ </ContextMenuItem>
63
+
64
+ <ContextMenuItem onClick={onViewYaml}>
65
+ <Code className="w-4 h-4 mr-2" />
66
+ View YAML
67
+ <ContextMenuShortcut>Y</ContextMenuShortcut>
68
+ </ContextMenuItem>
69
+
70
+ <ContextMenuItem onClick={onViewDocs}>
71
+ <FileText className="w-4 h-4 mr-2" />
72
+ View Documentation
73
+ </ContextMenuItem>
74
+
75
+ <ContextMenuSeparator />
76
+
77
+ <ContextMenuItem onClick={onDuplicate}>
78
+ <Copy className="w-4 h-4 mr-2" />
79
+ Duplicate
80
+ <ContextMenuShortcut>⌘D</ContextMenuShortcut>
81
+ </ContextMenuItem>
82
+
83
+ <ContextMenuSub>
84
+ <ContextMenuSubTrigger>
85
+ <Plus className="w-4 h-4 mr-2" />
86
+ Add Step
87
+ </ContextMenuSubTrigger>
88
+ <ContextMenuSubContent>
89
+ <ContextMenuItem onClick={onAddStepBefore}>
90
+ Before this step
91
+ </ContextMenuItem>
92
+ <ContextMenuItem onClick={onAddStepAfter}>
93
+ After this step
94
+ </ContextMenuItem>
95
+ </ContextMenuSubContent>
96
+ </ContextMenuSub>
97
+
98
+ {!isSubworkflow && (
99
+ <ContextMenuItem onClick={onConvertToSubworkflow}>
100
+ <FolderOpen className="w-4 h-4 mr-2" />
101
+ Convert to Sub-workflow
102
+ </ContextMenuItem>
103
+ )}
104
+
105
+ <ContextMenuSeparator />
106
+
107
+ <ContextMenuItem onClick={onExecuteFrom}>
108
+ <Play className="w-4 h-4 mr-2" />
109
+ Execute from here
110
+ </ContextMenuItem>
111
+
112
+ {hasError && (
113
+ <ContextMenuItem className="text-error">
114
+ <AlertTriangle className="w-4 h-4 mr-2" />
115
+ View Error Details
116
+ </ContextMenuItem>
117
+ )}
118
+
119
+ <ContextMenuSeparator />
120
+
121
+ <ContextMenuItem destructive onClick={onDelete}>
122
+ <Trash2 className="w-4 h-4 mr-2" />
123
+ Delete
124
+ <ContextMenuShortcut>⌫</ContextMenuShortcut>
125
+ </ContextMenuItem>
126
+ </ContextMenuContent>
127
+ </ContextMenu>
128
+ );
129
+ }
130
+
131
+ // Canvas context menu (right-click on empty space)
132
+ interface CanvasContextMenuProps {
133
+ children: React.ReactNode;
134
+ onAddStep: () => void;
135
+ onAddSubworkflow: () => void;
136
+ onPaste: () => void;
137
+ onAutoLayout: () => void;
138
+ onFitView: () => void;
139
+ canPaste: boolean;
140
+ }
141
+
142
+ export function CanvasContextMenu({
143
+ children,
144
+ onAddStep,
145
+ onAddSubworkflow,
146
+ onPaste,
147
+ onAutoLayout,
148
+ onFitView,
149
+ canPaste,
150
+ }: CanvasContextMenuProps) {
151
+ return (
152
+ <ContextMenu>
153
+ {children}
154
+ <ContextMenuContent>
155
+ <ContextMenuItem onClick={onAddStep}>
156
+ <Plus className="w-4 h-4 mr-2" />
157
+ Add Step
158
+ <ContextMenuShortcut>N</ContextMenuShortcut>
159
+ </ContextMenuItem>
160
+
161
+ <ContextMenuItem onClick={onAddSubworkflow}>
162
+ <FolderOpen className="w-4 h-4 mr-2" />
163
+ Add Sub-workflow
164
+ </ContextMenuItem>
165
+
166
+ <ContextMenuSeparator />
167
+
168
+ <ContextMenuItem onClick={onPaste} disabled={!canPaste}>
169
+ <Copy className="w-4 h-4 mr-2" />
170
+ Paste
171
+ <ContextMenuShortcut>⌘V</ContextMenuShortcut>
172
+ </ContextMenuItem>
173
+
174
+ <ContextMenuSeparator />
175
+
176
+ <ContextMenuItem onClick={onAutoLayout}>
177
+ Auto-layout
178
+ <ContextMenuShortcut>⌘L</ContextMenuShortcut>
179
+ </ContextMenuItem>
180
+
181
+ <ContextMenuItem onClick={onFitView}>
182
+ Fit to View
183
+ <ContextMenuShortcut>⌘0</ContextMenuShortcut>
184
+ </ContextMenuItem>
185
+ </ContextMenuContent>
186
+ </ContextMenu>
187
+ );
188
+ }
@@ -0,0 +1,111 @@
1
+ import { memo } from 'react';
2
+ import { Handle, Position, type Node, type NodeProps } from '@xyflow/react';
3
+ import { Flag, CheckCircle, XCircle, Clock } from 'lucide-react';
4
+
5
+ export interface OutputNodeData extends Record<string, unknown> {
6
+ id: string;
7
+ name?: string;
8
+ description?: string;
9
+ variables?: string[];
10
+ status?: 'pending' | 'completed' | 'failed';
11
+ result?: unknown;
12
+ }
13
+
14
+ export type OutputNodeType = Node<OutputNodeData, 'output'>;
15
+
16
+ function OutputNodeComponent({ data, selected }: NodeProps<OutputNodeType>) {
17
+ const statusConfig = {
18
+ pending: {
19
+ icon: Clock,
20
+ color: 'text-gray-400',
21
+ borderColor: 'border-gray-500',
22
+ bgColor: 'bg-gray-500/10',
23
+ },
24
+ completed: {
25
+ icon: CheckCircle,
26
+ color: 'text-success',
27
+ borderColor: 'border-success',
28
+ bgColor: 'bg-success/10',
29
+ },
30
+ failed: {
31
+ icon: XCircle,
32
+ color: 'text-error',
33
+ borderColor: 'border-error',
34
+ bgColor: 'bg-error/10',
35
+ },
36
+ };
37
+
38
+ const status = data.status || 'pending';
39
+ const config = statusConfig[status];
40
+ const StatusIcon = config.icon;
41
+
42
+ return (
43
+ <div
44
+ className={`min-w-[160px] rounded-lg border-2 ${config.borderColor} ${config.bgColor} ${
45
+ selected ? 'ring-2 ring-primary ring-offset-2 ring-offset-canvas-bg' : ''
46
+ } transition-all duration-200`}
47
+ >
48
+ {/* Input handle */}
49
+ <Handle
50
+ type="target"
51
+ position={Position.Top}
52
+ className="!w-3 !h-3 !bg-primary !border-2 !border-canvas-bg"
53
+ />
54
+
55
+ {/* Header */}
56
+ <div className="px-3 py-2 flex items-center gap-2">
57
+ <div className={`w-8 h-8 rounded-full ${config.bgColor} flex items-center justify-center`}>
58
+ <Flag className={`w-4 h-4 ${config.color}`} />
59
+ </div>
60
+ <div className="flex-1 min-w-0">
61
+ <div className="text-xs font-medium text-gray-400 uppercase tracking-wider">
62
+ Output
63
+ </div>
64
+ <div className="text-sm font-medium text-white truncate">
65
+ {data.name || 'Workflow End'}
66
+ </div>
67
+ </div>
68
+ <StatusIcon className={`w-4 h-4 ${config.color}`} />
69
+ </div>
70
+
71
+ {/* Variables */}
72
+ {data.variables && data.variables.length > 0 && (
73
+ <div className="px-3 py-2 border-t border-white/10">
74
+ <div className="text-xs text-gray-500 mb-1">Output Variables</div>
75
+ <div className="flex flex-wrap gap-1">
76
+ {data.variables.map((variable) => (
77
+ <code
78
+ key={variable}
79
+ className="px-1.5 py-0.5 bg-white/5 text-primary text-xs rounded"
80
+ >
81
+ {variable}
82
+ </code>
83
+ ))}
84
+ </div>
85
+ </div>
86
+ )}
87
+
88
+ {/* Description */}
89
+ {data.description && (
90
+ <div className="px-3 py-2 border-t border-white/10">
91
+ <div className="text-xs text-gray-400">{data.description}</div>
92
+ </div>
93
+ )}
94
+
95
+ {/* Result preview */}
96
+ {status === 'completed' && data.result !== undefined && (
97
+ <div className="px-3 py-2 border-t border-white/10">
98
+ <div className="text-xs text-gray-500 mb-1">Result</div>
99
+ <pre className="text-xs text-success font-mono bg-black/20 rounded p-1.5 overflow-x-auto max-h-20">
100
+ {typeof data.result === 'string'
101
+ ? data.result.slice(0, 100)
102
+ : JSON.stringify(data.result, null, 2).slice(0, 100)}
103
+ {(typeof data.result === 'string' ? data.result : JSON.stringify(data.result)).length > 100 && '...'}
104
+ </pre>
105
+ </div>
106
+ )}
107
+ </div>
108
+ );
109
+ }
110
+
111
+ export const OutputNode = memo(OutputNodeComponent);
@@ -0,0 +1,106 @@
1
+ import { memo } from 'react';
2
+ import { Handle, Position, type Node, type NodeProps } from '@xyflow/react';
3
+ import { Play, CheckCircle, XCircle, Clock, AlertCircle } from 'lucide-react';
4
+ import { getServiceIcon } from '../../utils/serviceIcons';
5
+
6
+ export interface StepNodeData extends Record<string, unknown> {
7
+ id: string;
8
+ name?: string;
9
+ action: string;
10
+ status?: 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
11
+ retryCount?: number;
12
+ error?: string;
13
+ }
14
+
15
+ export type StepNodeType = Node<StepNodeData, 'step'>;
16
+
17
+ function StepNodeComponent({ data, selected }: NodeProps<StepNodeType>) {
18
+ const serviceName = data.action?.split('.')[0] || 'unknown';
19
+ const methodName = data.action?.split('.').slice(1).join('.') || data.action;
20
+ const ServiceIcon = getServiceIcon(serviceName);
21
+
22
+ const statusConfig: Record<
23
+ NonNullable<StepNodeData['status']>,
24
+ { icon: typeof Clock; color: string; bgColor: string; animate?: boolean }
25
+ > = {
26
+ pending: { icon: Clock, color: 'text-gray-400', bgColor: 'bg-gray-400/10' },
27
+ running: {
28
+ icon: Play,
29
+ color: 'text-warning',
30
+ bgColor: 'bg-warning/10',
31
+ animate: true,
32
+ },
33
+ completed: {
34
+ icon: CheckCircle,
35
+ color: 'text-success',
36
+ bgColor: 'bg-success/10',
37
+ },
38
+ failed: { icon: XCircle, color: 'text-error', bgColor: 'bg-error/10' },
39
+ skipped: {
40
+ icon: AlertCircle,
41
+ color: 'text-gray-500',
42
+ bgColor: 'bg-gray-500/10',
43
+ },
44
+ };
45
+
46
+ const status = data.status || 'pending';
47
+ const config = statusConfig[status];
48
+ const StatusIcon = config.icon;
49
+
50
+ return (
51
+ <div
52
+ className={`step-node p-0 ${selected ? 'selected' : ''} ${status === 'running' ? 'running' : ''} ${status === 'completed' ? 'completed' : ''} ${status === 'failed' ? 'failed' : ''}`}
53
+ >
54
+ {/* Input handle */}
55
+ <Handle
56
+ type="target"
57
+ position={Position.Top}
58
+ className="!w-3 !h-3 !bg-primary !border-2 !border-node-bg"
59
+ />
60
+
61
+ {/* Node header */}
62
+ <div className="flex items-center gap-3 p-3 border-b border-node-border">
63
+ <div className="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center">
64
+ <ServiceIcon className="w-5 h-5 text-primary" />
65
+ </div>
66
+ <div className="flex-1 min-w-0">
67
+ <div className="text-sm font-medium text-white truncate">
68
+ {data.name || data.id}
69
+ </div>
70
+ <div className="text-xs text-gray-400 truncate">{serviceName}</div>
71
+ </div>
72
+ <div
73
+ className={`w-6 h-6 rounded-full ${config.bgColor} flex items-center justify-center`}
74
+ >
75
+ <StatusIcon
76
+ className={`w-4 h-4 ${config.color} ${config.animate ? 'animate-pulse' : ''}`}
77
+ />
78
+ </div>
79
+ </div>
80
+
81
+ {/* Node body */}
82
+ <div className="p-3">
83
+ <div className="text-xs text-gray-300 font-mono truncate">
84
+ {methodName}
85
+ </div>
86
+ {data.retryCount && data.retryCount > 0 && (
87
+ <div className="mt-2 text-xs text-warning">
88
+ Retry #{data.retryCount}
89
+ </div>
90
+ )}
91
+ {data.error && (
92
+ <div className="mt-2 text-xs text-error truncate">{data.error}</div>
93
+ )}
94
+ </div>
95
+
96
+ {/* Output handle */}
97
+ <Handle
98
+ type="source"
99
+ position={Position.Bottom}
100
+ className="!w-3 !h-3 !bg-primary !border-2 !border-node-bg"
101
+ />
102
+ </div>
103
+ );
104
+ }
105
+
106
+ export const StepNode = memo(StepNodeComponent);
@@ -0,0 +1,141 @@
1
+ import { memo, useState, useCallback } from 'react';
2
+ import { Handle, Position, type Node, type NodeProps } from '@xyflow/react';
3
+ import {
4
+ FolderOpen,
5
+ ChevronDown,
6
+ ChevronRight,
7
+ ExternalLink,
8
+ ArrowRight,
9
+ } from 'lucide-react';
10
+ import { useNavigationStore } from '../../stores/navigationStore';
11
+ import { useWorkflowStore } from '../../stores/workflowStore';
12
+
13
+ export interface SubWorkflowNodeData extends Record<string, unknown> {
14
+ id: string;
15
+ name?: string;
16
+ workflowPath: string;
17
+ stepCount?: number;
18
+ status?: 'pending' | 'running' | 'completed' | 'failed';
19
+ }
20
+
21
+ export type SubWorkflowNodeType = Node<SubWorkflowNodeData, 'subworkflow'>;
22
+
23
+ function SubWorkflowNodeComponent({
24
+ data,
25
+ selected,
26
+ }: NodeProps<SubWorkflowNodeType>) {
27
+ const [expanded, setExpanded] = useState(false);
28
+ const { pushWorkflow, breadcrumbs, setRootWorkflow } = useNavigationStore();
29
+ const { loadWorkflow, selectedWorkflow, currentWorkflow } = useWorkflowStore();
30
+
31
+ // Handle drilling down into sub-workflow
32
+ const handleDrillDown = useCallback(() => {
33
+ // If this is the first navigation, set the current workflow as root
34
+ if (breadcrumbs.length === 0 && selectedWorkflow && currentWorkflow) {
35
+ setRootWorkflow({
36
+ id: selectedWorkflow,
37
+ name: currentWorkflow.metadata?.name || 'Main Workflow',
38
+ path: selectedWorkflow,
39
+ });
40
+ }
41
+
42
+ // Push the sub-workflow onto the navigation stack
43
+ pushWorkflow({
44
+ id: data.id,
45
+ name: data.name || data.id,
46
+ path: data.workflowPath,
47
+ });
48
+
49
+ // Load the sub-workflow
50
+ loadWorkflow(data.workflowPath);
51
+ }, [data, pushWorkflow, breadcrumbs, setRootWorkflow, selectedWorkflow, currentWorkflow, loadWorkflow]);
52
+
53
+ const statusColors = {
54
+ pending: 'border-node-border',
55
+ running: 'border-warning',
56
+ completed: 'border-success',
57
+ failed: 'border-error',
58
+ };
59
+
60
+ const status = data.status || 'pending';
61
+
62
+ return (
63
+ <div
64
+ className={`step-node p-0 min-w-[250px] ${selected ? 'selected' : ''} ${statusColors[status]}`}
65
+ >
66
+ {/* Input handle */}
67
+ <Handle
68
+ type="target"
69
+ position={Position.Top}
70
+ className="!w-3 !h-3 !bg-info !border-2 !border-node-bg"
71
+ />
72
+
73
+ {/* Node header */}
74
+ <div className="flex items-center gap-3 p-3 border-b border-node-border bg-info/5">
75
+ <button
76
+ onClick={() => setExpanded(!expanded)}
77
+ className="w-6 h-6 rounded flex items-center justify-center hover:bg-white/10 transition-colors"
78
+ >
79
+ {expanded ? (
80
+ <ChevronDown className="w-4 h-4 text-info" />
81
+ ) : (
82
+ <ChevronRight className="w-4 h-4 text-info" />
83
+ )}
84
+ </button>
85
+ <div className="w-8 h-8 rounded-lg bg-info/10 flex items-center justify-center">
86
+ <FolderOpen className="w-5 h-5 text-info" />
87
+ </div>
88
+ <div className="flex-1 min-w-0">
89
+ <div className="text-sm font-medium text-white truncate">
90
+ {data.name || data.id}
91
+ </div>
92
+ <div className="text-xs text-gray-400">Sub-workflow</div>
93
+ </div>
94
+ <button
95
+ onClick={handleDrillDown}
96
+ className="w-6 h-6 rounded flex items-center justify-center hover:bg-info/20 transition-colors"
97
+ title="Drill into sub-workflow"
98
+ >
99
+ <ArrowRight className="w-4 h-4 text-info" />
100
+ </button>
101
+ </div>
102
+
103
+ {/* Node body */}
104
+ <div className="p-3">
105
+ <div className="text-xs text-gray-400 font-mono truncate">
106
+ {data.workflowPath}
107
+ </div>
108
+ {data.stepCount !== undefined && (
109
+ <div className="mt-2 flex items-center gap-2">
110
+ <span className="text-xs px-2 py-0.5 rounded-full bg-info/10 text-info">
111
+ {data.stepCount} steps
112
+ </span>
113
+ </div>
114
+ )}
115
+ </div>
116
+
117
+ {/* Expanded content */}
118
+ {expanded && (
119
+ <div className="border-t border-node-border p-3 bg-black/20">
120
+ <button
121
+ onClick={handleDrillDown}
122
+ className="w-full flex items-center justify-center gap-2 px-3 py-2 bg-info/10 hover:bg-info/20 rounded text-sm text-info transition-colors"
123
+ >
124
+ <FolderOpen className="w-4 h-4" />
125
+ Open Sub-workflow
126
+ <ArrowRight className="w-4 h-4" />
127
+ </button>
128
+ </div>
129
+ )}
130
+
131
+ {/* Output handle */}
132
+ <Handle
133
+ type="source"
134
+ position={Position.Bottom}
135
+ className="!w-3 !h-3 !bg-info !border-2 !border-node-bg"
136
+ />
137
+ </div>
138
+ );
139
+ }
140
+
141
+ export const SubWorkflowNode = memo(SubWorkflowNodeComponent);