@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.
- package/.turbo/turbo-build.log +26 -0
- package/.turbo/turbo-test.log +22 -0
- package/README.md +179 -0
- package/dist/client/assets/index-DwTI8opO.js +608 -0
- package/dist/client/assets/index-DwTI8opO.js.map +1 -0
- package/dist/client/assets/index-RoEdL6gO.css +1 -0
- package/dist/client/index.html +20 -0
- package/dist/client/vite.svg +9 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +56 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/routes/ai.js +50 -0
- package/dist/server/routes/ai.js.map +1 -0
- package/dist/server/routes/execute.js +62 -0
- package/dist/server/routes/execute.js.map +1 -0
- package/dist/server/routes/workflows.js +99 -0
- package/dist/server/routes/workflows.js.map +1 -0
- package/dist/server/server/index.js +95 -0
- package/dist/server/server/index.js.map +1 -0
- package/dist/server/server/routes/ai.js +87 -0
- package/dist/server/server/routes/ai.js.map +1 -0
- package/dist/server/server/routes/execute.js +63 -0
- package/dist/server/server/routes/execute.js.map +1 -0
- package/dist/server/server/routes/tools.js +518 -0
- package/dist/server/server/routes/tools.js.map +1 -0
- package/dist/server/server/routes/workflows.js +99 -0
- package/dist/server/server/routes/workflows.js.map +1 -0
- package/dist/server/server/services/AIService.js +69 -0
- package/dist/server/server/services/AIService.js.map +1 -0
- package/dist/server/server/services/FileWatcher.js +60 -0
- package/dist/server/server/services/FileWatcher.js.map +1 -0
- package/dist/server/server/services/WorkflowService.js +363 -0
- package/dist/server/server/services/WorkflowService.js.map +1 -0
- package/dist/server/server/services/agents/claude-code-provider.js +250 -0
- package/dist/server/server/services/agents/claude-code-provider.js.map +1 -0
- package/dist/server/server/services/agents/claude-provider.js +204 -0
- package/dist/server/server/services/agents/claude-provider.js.map +1 -0
- package/dist/server/server/services/agents/copilot-provider.js +227 -0
- package/dist/server/server/services/agents/copilot-provider.js.map +1 -0
- package/dist/server/server/services/agents/demo-provider.js +167 -0
- package/dist/server/server/services/agents/demo-provider.js.map +1 -0
- package/dist/server/server/services/agents/index.js +31 -0
- package/dist/server/server/services/agents/index.js.map +1 -0
- package/dist/server/server/services/agents/ollama-provider.js +220 -0
- package/dist/server/server/services/agents/ollama-provider.js.map +1 -0
- package/dist/server/server/services/agents/prompts.js +436 -0
- package/dist/server/server/services/agents/prompts.js.map +1 -0
- package/dist/server/server/services/agents/registry.js +242 -0
- package/dist/server/server/services/agents/registry.js.map +1 -0
- package/dist/server/server/services/agents/types.js +6 -0
- package/dist/server/server/services/agents/types.js.map +1 -0
- package/dist/server/server/websocket/index.js +85 -0
- package/dist/server/server/websocket/index.js.map +1 -0
- package/dist/server/services/AIService.d.ts +30 -0
- package/dist/server/services/AIService.d.ts.map +1 -0
- package/dist/server/services/AIService.js +216 -0
- package/dist/server/services/AIService.js.map +1 -0
- package/dist/server/services/FileWatcher.d.ts +10 -0
- package/dist/server/services/FileWatcher.d.ts.map +1 -0
- package/dist/server/services/FileWatcher.js +62 -0
- package/dist/server/services/FileWatcher.js.map +1 -0
- package/dist/server/services/WorkflowService.d.ts +54 -0
- package/dist/server/services/WorkflowService.d.ts.map +1 -0
- package/dist/server/services/WorkflowService.js +323 -0
- package/dist/server/services/WorkflowService.js.map +1 -0
- package/dist/server/shared/constants.js +175 -0
- package/dist/server/shared/constants.js.map +1 -0
- package/dist/server/shared/types.js +3 -0
- package/dist/server/shared/types.js.map +1 -0
- package/dist/server/websocket/index.d.ts +10 -0
- package/dist/server/websocket/index.d.ts.map +1 -0
- package/dist/server/websocket/index.js +85 -0
- package/dist/server/websocket/index.js.map +1 -0
- package/index.html +19 -0
- package/package.json +96 -0
- package/playwright.config.ts +27 -0
- package/postcss.config.js +6 -0
- package/public/vite.svg +9 -0
- package/src/client/App.tsx +520 -0
- package/src/client/components/Canvas/Canvas.tsx +405 -0
- package/src/client/components/Canvas/ExecutionOverlay.tsx +847 -0
- package/src/client/components/Canvas/NodeContextMenu.tsx +188 -0
- package/src/client/components/Canvas/OutputNode.tsx +111 -0
- package/src/client/components/Canvas/StepNode.tsx +106 -0
- package/src/client/components/Canvas/SubWorkflowNode.tsx +141 -0
- package/src/client/components/Canvas/Toolbar.tsx +189 -0
- package/src/client/components/Canvas/TriggerNode.tsx +128 -0
- package/src/client/components/Editor/InputsEditor.tsx +458 -0
- package/src/client/components/Editor/NewStepWizard.tsx +344 -0
- package/src/client/components/Editor/StepEditor.tsx +532 -0
- package/src/client/components/Editor/YamlEditor.tsx +160 -0
- package/src/client/components/Panels/PropertiesPanel.tsx +589 -0
- package/src/client/components/Prompt/ChangePreview.tsx +281 -0
- package/src/client/components/Prompt/PromptHistoryPanel.tsx +209 -0
- package/src/client/components/Prompt/PromptInput.tsx +108 -0
- package/src/client/components/Sidebar/Sidebar.tsx +343 -0
- package/src/client/components/common/Breadcrumb.tsx +40 -0
- package/src/client/components/common/Button.tsx +68 -0
- package/src/client/components/common/ContextMenu.tsx +202 -0
- package/src/client/components/common/KeyboardShortcuts.tsx +143 -0
- package/src/client/components/common/Modal.tsx +93 -0
- package/src/client/components/common/Tabs.tsx +57 -0
- package/src/client/components/common/ThemeToggle.tsx +63 -0
- package/src/client/components/index.ts +32 -0
- package/src/client/hooks/index.ts +4 -0
- package/src/client/hooks/useAIPrompt.ts +108 -0
- package/src/client/hooks/useCanvas.ts +247 -0
- package/src/client/hooks/useWebSocket.ts +164 -0
- package/src/client/hooks/useWorkflow.ts +138 -0
- package/src/client/main.tsx +10 -0
- package/src/client/stores/canvasStore.ts +348 -0
- package/src/client/stores/editorStore.ts +133 -0
- package/src/client/stores/executionStore.ts +440 -0
- package/src/client/stores/index.ts +4 -0
- package/src/client/stores/layoutStore.ts +103 -0
- package/src/client/stores/navigationStore.ts +49 -0
- package/src/client/stores/promptStore.ts +113 -0
- package/src/client/stores/themeStore.ts +75 -0
- package/src/client/stores/workflowStore.ts +177 -0
- package/src/client/styles/globals.css +346 -0
- package/src/client/utils/cn.ts +9 -0
- package/src/client/utils/index.ts +4 -0
- package/src/client/utils/serviceIcons.tsx +64 -0
- package/src/client/utils/stepValidation.ts +155 -0
- package/src/client/utils/workflowToGraph.ts +299 -0
- package/src/server/index.ts +114 -0
- package/src/server/routes/ai.ts +91 -0
- package/src/server/routes/execute.ts +71 -0
- package/src/server/routes/tools.ts +564 -0
- package/src/server/routes/workflows.ts +106 -0
- package/src/server/services/AIService.ts +105 -0
- package/src/server/services/FileWatcher.ts +69 -0
- package/src/server/services/WorkflowService.ts +441 -0
- package/src/server/services/agents/claude-code-provider.ts +320 -0
- package/src/server/services/agents/claude-provider.ts +248 -0
- package/src/server/services/agents/copilot-provider.ts +311 -0
- package/src/server/services/agents/demo-provider.ts +184 -0
- package/src/server/services/agents/index.ts +31 -0
- package/src/server/services/agents/ollama-provider.ts +267 -0
- package/src/server/services/agents/prompts.ts +482 -0
- package/src/server/services/agents/registry.ts +289 -0
- package/src/server/services/agents/types.ts +146 -0
- package/src/server/websocket/index.ts +104 -0
- package/src/shared/constants.ts +180 -0
- package/src/shared/types.ts +179 -0
- package/tailwind.config.ts +73 -0
- package/tests/e2e/app.spec.ts +90 -0
- package/tests/e2e/canvas.spec.ts +128 -0
- package/tests/e2e/workflow.spec.ts +185 -0
- package/tests/integration/api.test.ts +250 -0
- package/tests/integration/testApp.ts +31 -0
- package/tests/setup.ts +37 -0
- package/tests/unit/canvasStore.test.ts +502 -0
- package/tests/unit/components.test.tsx +151 -0
- package/tests/unit/executionStore.test.ts +527 -0
- package/tests/unit/layoutStore.test.ts +194 -0
- package/tests/unit/navigationStore.test.ts +152 -0
- package/tests/unit/stepValidation.test.ts +226 -0
- package/tests/unit/themeStore.test.ts +141 -0
- package/tests/unit/workflowToGraph.test.ts +289 -0
- package/tsconfig.json +29 -0
- package/tsconfig.server.json +28 -0
- package/vite.config.ts +31 -0
- 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);
|