@marktoflow/gui 2.0.0-alpha.12 → 2.0.0-alpha.13

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 (121) hide show
  1. package/package.json +1 -3
  2. package/.marktoflow/state/workflow-state.db +0 -0
  3. package/.marktoflow/state/workflow-state.db-shm +0 -0
  4. package/.marktoflow/state/workflow-state.db-wal +0 -0
  5. package/.turbo/turbo-build.log +0 -42
  6. package/.turbo/turbo-test.log +0 -38
  7. package/marktoflow-gui-2.0.0-alpha.12.tgz +0 -0
  8. package/playwright.config.ts +0 -27
  9. package/postcss.config.js +0 -6
  10. package/src/client/App.tsx +0 -520
  11. package/src/client/components/Canvas/Canvas.tsx +0 -425
  12. package/src/client/components/Canvas/ExecutionOverlay.tsx +0 -935
  13. package/src/client/components/Canvas/ForEachNode.tsx +0 -152
  14. package/src/client/components/Canvas/IfElseNode.tsx +0 -141
  15. package/src/client/components/Canvas/NodeContextMenu.tsx +0 -192
  16. package/src/client/components/Canvas/OutputNode.tsx +0 -111
  17. package/src/client/components/Canvas/ParallelNode.tsx +0 -157
  18. package/src/client/components/Canvas/StepNode.tsx +0 -106
  19. package/src/client/components/Canvas/SubWorkflowNode.tsx +0 -141
  20. package/src/client/components/Canvas/SwitchNode.tsx +0 -185
  21. package/src/client/components/Canvas/Toolbar.tsx +0 -227
  22. package/src/client/components/Canvas/TransformNode.tsx +0 -194
  23. package/src/client/components/Canvas/TriggerNode.tsx +0 -128
  24. package/src/client/components/Canvas/TryCatchNode.tsx +0 -164
  25. package/src/client/components/Canvas/WhileNode.tsx +0 -161
  26. package/src/client/components/Canvas/index.ts +0 -24
  27. package/src/client/components/Debug/VariableInspector.tsx +0 -148
  28. package/src/client/components/Editor/InputsEditor.tsx +0 -458
  29. package/src/client/components/Editor/NewStepWizard.tsx +0 -344
  30. package/src/client/components/Editor/StepEditor.tsx +0 -532
  31. package/src/client/components/Editor/YamlEditor.tsx +0 -160
  32. package/src/client/components/Panels/PropertiesPanel.tsx +0 -589
  33. package/src/client/components/Prompt/ChangePreview.tsx +0 -281
  34. package/src/client/components/Prompt/PromptHistoryPanel.tsx +0 -209
  35. package/src/client/components/Prompt/PromptInput.tsx +0 -110
  36. package/src/client/components/Settings/ProviderSwitcher.tsx +0 -228
  37. package/src/client/components/Sidebar/ImportDialog.tsx +0 -257
  38. package/src/client/components/Sidebar/Sidebar.tsx +0 -362
  39. package/src/client/components/common/Breadcrumb.tsx +0 -40
  40. package/src/client/components/common/Button.tsx +0 -68
  41. package/src/client/components/common/ContextMenu.tsx +0 -202
  42. package/src/client/components/common/KeyboardShortcuts.tsx +0 -149
  43. package/src/client/components/common/Modal.tsx +0 -93
  44. package/src/client/components/common/Tabs.tsx +0 -57
  45. package/src/client/components/common/ThemeToggle.tsx +0 -63
  46. package/src/client/components/index.ts +0 -32
  47. package/src/client/hooks/index.ts +0 -4
  48. package/src/client/hooks/useAIPrompt.ts +0 -108
  49. package/src/client/hooks/useCanvas.ts +0 -247
  50. package/src/client/hooks/useWebSocket.ts +0 -164
  51. package/src/client/hooks/useWorkflow.ts +0 -138
  52. package/src/client/main.tsx +0 -10
  53. package/src/client/stores/agentStore.ts +0 -109
  54. package/src/client/stores/canvasStore.ts +0 -348
  55. package/src/client/stores/editorStore.ts +0 -133
  56. package/src/client/stores/executionStore.ts +0 -502
  57. package/src/client/stores/index.ts +0 -4
  58. package/src/client/stores/layoutStore.ts +0 -103
  59. package/src/client/stores/navigationStore.ts +0 -49
  60. package/src/client/stores/promptStore.ts +0 -113
  61. package/src/client/stores/themeStore.ts +0 -75
  62. package/src/client/stores/workflowStore.ts +0 -185
  63. package/src/client/styles/globals.css +0 -452
  64. package/src/client/utils/cn.ts +0 -9
  65. package/src/client/utils/index.ts +0 -4
  66. package/src/client/utils/platform.ts +0 -46
  67. package/src/client/utils/serviceIcons.tsx +0 -97
  68. package/src/client/utils/stepValidation.ts +0 -155
  69. package/src/client/utils/workflowToGraph.ts +0 -523
  70. package/src/server/index.ts +0 -137
  71. package/src/server/routes/ai.ts +0 -91
  72. package/src/server/routes/execute.ts +0 -71
  73. package/src/server/routes/executions.ts +0 -136
  74. package/src/server/routes/tools.ts +0 -970
  75. package/src/server/routes/workflows.ts +0 -147
  76. package/src/server/services/AIService.ts +0 -105
  77. package/src/server/services/FileWatcher.ts +0 -69
  78. package/src/server/services/WorkflowService.ts +0 -601
  79. package/src/server/services/agents/claude-code-provider.ts +0 -320
  80. package/src/server/services/agents/claude-provider.ts +0 -248
  81. package/src/server/services/agents/codex-provider.ts +0 -398
  82. package/src/server/services/agents/copilot-provider.ts +0 -311
  83. package/src/server/services/agents/demo-provider.ts +0 -184
  84. package/src/server/services/agents/index.ts +0 -31
  85. package/src/server/services/agents/ollama-provider.ts +0 -267
  86. package/src/server/services/agents/prompts.ts +0 -509
  87. package/src/server/services/agents/registry.ts +0 -310
  88. package/src/server/services/agents/types.ts +0 -146
  89. package/src/server/websocket/index.ts +0 -117
  90. package/src/shared/constants.ts +0 -180
  91. package/src/shared/types.ts +0 -179
  92. package/tailwind.config.ts +0 -73
  93. package/tests/e2e/app.spec.ts +0 -90
  94. package/tests/e2e/canvas.spec.ts +0 -128
  95. package/tests/e2e/workflow.spec.ts +0 -185
  96. package/tests/integration/api.test.ts +0 -452
  97. package/tests/integration/testApp.ts +0 -31
  98. package/tests/setup.ts +0 -72
  99. package/tests/unit/ForEachNode.test.tsx +0 -308
  100. package/tests/unit/IfElseNode.test.tsx +0 -235
  101. package/tests/unit/ParallelNode.test.tsx +0 -344
  102. package/tests/unit/SwitchNode.test.tsx +0 -327
  103. package/tests/unit/TransformNode.test.tsx +0 -386
  104. package/tests/unit/TryCatchNode.test.tsx +0 -243
  105. package/tests/unit/WhileNode.test.tsx +0 -230
  106. package/tests/unit/agentStore.test.ts +0 -218
  107. package/tests/unit/canvasStore.test.ts +0 -502
  108. package/tests/unit/codexProvider.test.ts +0 -399
  109. package/tests/unit/components.test.tsx +0 -151
  110. package/tests/unit/executionStore.test.ts +0 -567
  111. package/tests/unit/layoutStore.test.ts +0 -194
  112. package/tests/unit/navigationStore.test.ts +0 -152
  113. package/tests/unit/platform.test.ts +0 -118
  114. package/tests/unit/serviceIcons.test.ts +0 -197
  115. package/tests/unit/stepValidation.test.ts +0 -226
  116. package/tests/unit/themeStore.test.ts +0 -141
  117. package/tests/unit/workflowToGraph.test.ts +0 -311
  118. package/tsconfig.json +0 -29
  119. package/tsconfig.server.json +0 -28
  120. package/vite.config.ts +0 -31
  121. package/vitest.config.ts +0 -26
@@ -1,935 +0,0 @@
1
- import { useState } from 'react';
2
- import {
3
- Play,
4
- Pause,
5
- SkipForward,
6
- Square,
7
- CheckCircle,
8
- XCircle,
9
- Loader2,
10
- ChevronDown,
11
- ChevronRight,
12
- Copy,
13
- Check,
14
- Bug,
15
- Circle,
16
- ArrowRight,
17
- ArrowDown,
18
- ArrowUp,
19
- Trash2,
20
- Plus,
21
- X,
22
- } from 'lucide-react';
23
- import { Button } from '../common/Button';
24
- import type { StepStatus, WorkflowStatus } from '@shared/types';
25
- import type { DebugState } from '../../stores/executionStore';
26
-
27
- interface ExecutionStep {
28
- stepId: string;
29
- stepName: string;
30
- status: StepStatus;
31
- duration?: number;
32
- error?: string;
33
- inputs?: Record<string, unknown>;
34
- output?: unknown;
35
- outputVariable?: string;
36
- }
37
-
38
- interface ExecutionOverlayProps {
39
- isExecuting: boolean;
40
- isPaused: boolean;
41
- workflowStatus: WorkflowStatus;
42
- currentStepId: string | null;
43
- steps: ExecutionStep[];
44
- logs: string[];
45
- onPause: () => void;
46
- onResume: () => void;
47
- onStop: () => void;
48
- onStepOver: () => void;
49
- onClose: () => void;
50
- // Debug props
51
- debug?: DebugState;
52
- onToggleDebugMode?: () => void;
53
- onToggleBreakpoint?: (stepId: string) => void;
54
- onStepInto?: () => void;
55
- onStepOut?: () => void;
56
- onClearBreakpoints?: () => void;
57
- onAddWatchExpression?: (expression: string) => void;
58
- onRemoveWatchExpression?: (expression: string) => void;
59
- }
60
-
61
- export function ExecutionOverlay({
62
- isExecuting,
63
- isPaused,
64
- workflowStatus,
65
- currentStepId,
66
- steps,
67
- logs,
68
- onPause,
69
- onResume,
70
- onStop,
71
- onStepOver,
72
- onClose,
73
- // Debug props
74
- debug,
75
- onToggleDebugMode,
76
- onToggleBreakpoint,
77
- onStepInto,
78
- onStepOut,
79
- onClearBreakpoints,
80
- onAddWatchExpression,
81
- onRemoveWatchExpression,
82
- }: ExecutionOverlayProps) {
83
- const [activeTab, setActiveTab] = useState<'steps' | 'variables' | 'logs' | 'debug'>('steps');
84
- const isDebugEnabled = debug?.enabled ?? false;
85
-
86
- const completedSteps = steps.filter((s) => s.status === 'completed').length;
87
- const failedSteps = steps.filter((s) => s.status === 'failed').length;
88
- const progress = steps.length > 0 ? (completedSteps / steps.length) * 100 : 0;
89
-
90
- if (!isExecuting && workflowStatus === 'pending') {
91
- return null;
92
- }
93
-
94
- return (
95
- <div className="absolute bottom-20 left-4 right-4 z-20 bg-panel-bg border border-node-border rounded-lg shadow-xl max-h-[400px] flex flex-col">
96
- {/* Header */}
97
- <div className="flex items-center justify-between px-4 py-3 border-b border-node-border">
98
- <div className="flex items-center gap-3">
99
- <StatusIcon status={workflowStatus} />
100
- <div>
101
- <div className="text-sm font-medium text-white">
102
- {getStatusText(workflowStatus)}
103
- </div>
104
- <div className="text-xs text-gray-400">
105
- {completedSteps}/{steps.length} steps completed
106
- {failedSteps > 0 && ` • ${failedSteps} failed`}
107
- </div>
108
- </div>
109
- </div>
110
-
111
- {/* Controls */}
112
- <div className="flex items-center gap-2">
113
- {/* Debug mode toggle */}
114
- {onToggleDebugMode && (
115
- <Button
116
- variant={isDebugEnabled ? 'primary' : 'ghost'}
117
- size="sm"
118
- onClick={onToggleDebugMode}
119
- icon={<Bug className="w-4 h-4" />}
120
- title={isDebugEnabled ? 'Disable debug mode' : 'Enable debug mode'}
121
- >
122
- Debug
123
- </Button>
124
- )}
125
-
126
- {isExecuting && (
127
- <>
128
- {isPaused ? (
129
- <Button
130
- variant="secondary"
131
- size="sm"
132
- onClick={onResume}
133
- icon={<Play className="w-4 h-4" />}
134
- >
135
- Resume
136
- </Button>
137
- ) : (
138
- <Button
139
- variant="secondary"
140
- size="sm"
141
- onClick={onPause}
142
- icon={<Pause className="w-4 h-4" />}
143
- >
144
- Pause
145
- </Button>
146
- )}
147
-
148
- {/* Debug stepping controls */}
149
- {isDebugEnabled && isPaused && (
150
- <>
151
- <Button
152
- variant="secondary"
153
- size="sm"
154
- onClick={onStepOver}
155
- icon={<ArrowRight className="w-4 h-4" />}
156
- title="Step Over (F10)"
157
- >
158
- Over
159
- </Button>
160
- {onStepInto && (
161
- <Button
162
- variant="secondary"
163
- size="sm"
164
- onClick={onStepInto}
165
- icon={<ArrowDown className="w-4 h-4" />}
166
- title="Step Into (F11)"
167
- >
168
- Into
169
- </Button>
170
- )}
171
- {onStepOut && (
172
- <Button
173
- variant="secondary"
174
- size="sm"
175
- onClick={onStepOut}
176
- icon={<ArrowUp className="w-4 h-4" />}
177
- title="Step Out (Shift+F11)"
178
- >
179
- Out
180
- </Button>
181
- )}
182
- </>
183
- )}
184
-
185
- {/* Regular step control when not in debug mode */}
186
- {!isDebugEnabled && (
187
- <Button
188
- variant="secondary"
189
- size="sm"
190
- onClick={onStepOver}
191
- icon={<SkipForward className="w-4 h-4" />}
192
- disabled={!isPaused}
193
- >
194
- Step
195
- </Button>
196
- )}
197
-
198
- <Button
199
- variant="destructive"
200
- size="sm"
201
- onClick={onStop}
202
- icon={<Square className="w-4 h-4" />}
203
- >
204
- Stop
205
- </Button>
206
- </>
207
- )}
208
- {!isExecuting && (
209
- <Button variant="secondary" size="sm" onClick={onClose}>
210
- Close
211
- </Button>
212
- )}
213
- </div>
214
- </div>
215
-
216
- {/* Progress bar */}
217
- <div className="h-1 bg-node-bg">
218
- <div
219
- className={`h-full transition-all duration-300 ${
220
- workflowStatus === 'failed'
221
- ? 'bg-error'
222
- : workflowStatus === 'completed'
223
- ? 'bg-success'
224
- : 'bg-primary'
225
- }`}
226
- style={{ width: `${progress}%` }}
227
- />
228
- </div>
229
-
230
- {/* Tabs */}
231
- <div className="flex border-b border-node-border">
232
- <button
233
- onClick={() => setActiveTab('steps')}
234
- className={`px-4 py-2 text-sm font-medium transition-colors ${
235
- activeTab === 'steps'
236
- ? 'text-primary border-b-2 border-primary -mb-px'
237
- : 'text-gray-400 hover:text-white'
238
- }`}
239
- >
240
- Steps
241
- </button>
242
- <button
243
- onClick={() => setActiveTab('variables')}
244
- className={`px-4 py-2 text-sm font-medium transition-colors ${
245
- activeTab === 'variables'
246
- ? 'text-primary border-b-2 border-primary -mb-px'
247
- : 'text-gray-400 hover:text-white'
248
- }`}
249
- >
250
- Variables
251
- </button>
252
- <button
253
- onClick={() => setActiveTab('logs')}
254
- className={`px-4 py-2 text-sm font-medium transition-colors ${
255
- activeTab === 'logs'
256
- ? 'text-primary border-b-2 border-primary -mb-px'
257
- : 'text-gray-400 hover:text-white'
258
- }`}
259
- >
260
- Logs
261
- </button>
262
- {isDebugEnabled && (
263
- <button
264
- onClick={() => setActiveTab('debug')}
265
- className={`px-4 py-2 text-sm font-medium transition-colors flex items-center gap-1 ${
266
- activeTab === 'debug'
267
- ? 'text-primary border-b-2 border-primary -mb-px'
268
- : 'text-gray-400 hover:text-white'
269
- }`}
270
- >
271
- <Bug className="w-3 h-3" />
272
- Debug
273
- </button>
274
- )}
275
- </div>
276
-
277
- {/* Content */}
278
- <div className="flex-1 overflow-y-auto p-4">
279
- {activeTab === 'steps' && (
280
- <StepsList
281
- steps={steps}
282
- currentStepId={currentStepId}
283
- debugEnabled={isDebugEnabled}
284
- breakpoints={debug?.breakpoints}
285
- onToggleBreakpoint={onToggleBreakpoint}
286
- />
287
- )}
288
- {activeTab === 'variables' && (
289
- <VariableInspector steps={steps} />
290
- )}
291
- {activeTab === 'logs' && (
292
- <LogsViewer logs={logs} />
293
- )}
294
- {activeTab === 'debug' && isDebugEnabled && (
295
- <DebugPanel
296
- debug={debug!}
297
- onClearBreakpoints={onClearBreakpoints}
298
- onAddWatchExpression={onAddWatchExpression}
299
- onRemoveWatchExpression={onRemoveWatchExpression}
300
- />
301
- )}
302
- </div>
303
- </div>
304
- );
305
- }
306
-
307
- function StepsList({
308
- steps,
309
- currentStepId,
310
- debugEnabled,
311
- breakpoints,
312
- onToggleBreakpoint,
313
- }: {
314
- steps: ExecutionStep[];
315
- currentStepId: string | null;
316
- debugEnabled?: boolean;
317
- breakpoints?: Set<string>;
318
- onToggleBreakpoint?: (stepId: string) => void;
319
- }) {
320
- return (
321
- <div className="space-y-2">
322
- {steps.map((step) => {
323
- const hasBreakpoint = breakpoints?.has(step.stepId);
324
-
325
- return (
326
- <div
327
- key={step.stepId}
328
- className={`flex items-center gap-3 p-3 rounded-lg border ${
329
- step.stepId === currentStepId
330
- ? 'bg-primary/10 border-primary'
331
- : hasBreakpoint
332
- ? 'bg-error/10 border-error/50'
333
- : 'bg-node-bg border-node-border'
334
- }`}
335
- >
336
- {/* Breakpoint indicator/toggle */}
337
- {debugEnabled && onToggleBreakpoint && (
338
- <button
339
- onClick={() => onToggleBreakpoint(step.stepId)}
340
- className={`w-4 h-4 rounded-full flex items-center justify-center transition-colors ${
341
- hasBreakpoint
342
- ? 'bg-error'
343
- : 'bg-transparent border border-gray-500 hover:border-error hover:bg-error/20'
344
- }`}
345
- title={hasBreakpoint ? 'Remove breakpoint' : 'Add breakpoint'}
346
- >
347
- {hasBreakpoint && <Circle className="w-2 h-2 fill-current text-white" />}
348
- </button>
349
- )}
350
-
351
- <StepStatusIcon status={step.status} />
352
- <div className="flex-1 min-w-0">
353
- <div className="text-sm font-medium text-white truncate">
354
- {step.stepName || step.stepId}
355
- </div>
356
- {step.error && (
357
- <div className="text-xs text-error mt-1 truncate">{step.error}</div>
358
- )}
359
- </div>
360
- {step.duration !== undefined && (
361
- <div className="text-xs text-gray-400">{step.duration}ms</div>
362
- )}
363
- </div>
364
- );
365
- })}
366
- </div>
367
- );
368
- }
369
-
370
- function LogsViewer({ logs }: { logs: string[] }) {
371
- return (
372
- <div className="font-mono text-xs space-y-1">
373
- {logs.length === 0 ? (
374
- <div className="text-gray-500">No logs yet...</div>
375
- ) : (
376
- logs.map((log, index) => (
377
- <div key={index} className="text-gray-300">
378
- {log}
379
- </div>
380
- ))
381
- )}
382
- </div>
383
- );
384
- }
385
-
386
- function VariableInspector({ steps }: { steps: ExecutionStep[] }) {
387
- const [expandedSteps, setExpandedSteps] = useState<Set<string>>(new Set());
388
- const [expandedSection, setExpandedSection] = useState<Set<string>>(new Set());
389
- const [copiedKey, setCopiedKey] = useState<string | null>(null);
390
-
391
- // Filter steps that have input or output data
392
- const stepsWithData = steps.filter(
393
- (step) => step.inputs !== undefined || (step.output !== undefined && step.outputVariable)
394
- );
395
-
396
- const toggleStep = (stepId: string) => {
397
- setExpandedSteps((prev) => {
398
- const next = new Set(prev);
399
- if (next.has(stepId)) {
400
- next.delete(stepId);
401
- } else {
402
- next.add(stepId);
403
- }
404
- return next;
405
- });
406
- };
407
-
408
- const toggleSection = (sectionId: string) => {
409
- setExpandedSection((prev) => {
410
- const next = new Set(prev);
411
- if (next.has(sectionId)) {
412
- next.delete(sectionId);
413
- } else {
414
- next.add(sectionId);
415
- }
416
- return next;
417
- });
418
- };
419
-
420
- const copyValue = async (key: string, value: unknown) => {
421
- try {
422
- const text = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
423
- await navigator.clipboard.writeText(text);
424
- setCopiedKey(key);
425
- setTimeout(() => setCopiedKey(null), 2000);
426
- } catch (error) {
427
- console.error('Failed to copy:', error);
428
- }
429
- };
430
-
431
- if (stepsWithData.length === 0) {
432
- return (
433
- <div className="text-center py-8 text-gray-500 text-sm">
434
- No data available yet.
435
- <br />
436
- <span className="text-xs">Step inputs and outputs will appear as steps execute.</span>
437
- </div>
438
- );
439
- }
440
-
441
- return (
442
- <div className="space-y-2">
443
- {stepsWithData.map((step) => {
444
- const isExpanded = expandedSteps.has(step.stepId);
445
- const hasInputs = step.inputs && Object.keys(step.inputs).length > 0;
446
- const hasOutput = step.output !== undefined && step.outputVariable;
447
-
448
- return (
449
- <div
450
- key={step.stepId}
451
- className="border border-node-border rounded-lg overflow-hidden"
452
- >
453
- {/* Step Header */}
454
- <button
455
- onClick={() => toggleStep(step.stepId)}
456
- className="w-full flex items-center gap-2 px-3 py-2 bg-node-bg hover:bg-white/5 transition-colors"
457
- >
458
- {isExpanded ? (
459
- <ChevronDown className="w-4 h-4 text-gray-400" />
460
- ) : (
461
- <ChevronRight className="w-4 h-4 text-gray-400" />
462
- )}
463
- <code className="text-sm text-white font-mono">
464
- {step.stepName || step.stepId}
465
- </code>
466
- <span className="text-xs text-gray-500 ml-auto">
467
- {hasInputs && `${Object.keys(step.inputs!).length} inputs`}
468
- {hasInputs && hasOutput && ' • '}
469
- {hasOutput && step.outputVariable}
470
- </span>
471
- </button>
472
-
473
- {/* Step Data */}
474
- {isExpanded && (
475
- <div className="bg-panel-bg border-t border-node-border">
476
- {/* Inputs Section */}
477
- {hasInputs && (
478
- <div className="border-b border-node-border">
479
- <button
480
- onClick={() => toggleSection(`${step.stepId}-inputs`)}
481
- className="w-full flex items-center gap-2 px-3 py-2 hover:bg-white/5 transition-colors"
482
- >
483
- {expandedSection.has(`${step.stepId}-inputs`) ? (
484
- <ChevronDown className="w-3 h-3 text-gray-400" />
485
- ) : (
486
- <ChevronRight className="w-3 h-3 text-gray-400" />
487
- )}
488
- <span className="text-xs font-medium text-gray-400">
489
- Inputs ({Object.keys(step.inputs!).length})
490
- </span>
491
- </button>
492
- {expandedSection.has(`${step.stepId}-inputs`) && (
493
- <div className="px-3 pb-3">
494
- <div className="flex items-start gap-2">
495
- <div className="flex-1 overflow-x-auto">
496
- <ValueRenderer
497
- value={step.inputs}
498
- onCopy={(key, val) => copyValue(key, val)}
499
- copiedKey={copiedKey}
500
- path="inputs"
501
- />
502
- </div>
503
- <button
504
- onClick={() => copyValue(`${step.stepId}-inputs`, step.inputs)}
505
- className="p-1.5 hover:bg-white/10 rounded transition-colors"
506
- title="Copy inputs"
507
- >
508
- {copiedKey === `${step.stepId}-inputs` ? (
509
- <Check className="w-4 h-4 text-success" />
510
- ) : (
511
- <Copy className="w-4 h-4 text-gray-400" />
512
- )}
513
- </button>
514
- </div>
515
- </div>
516
- )}
517
- </div>
518
- )}
519
-
520
- {/* Output Section */}
521
- {hasOutput && (
522
- <div>
523
- <button
524
- onClick={() => toggleSection(`${step.stepId}-output`)}
525
- className="w-full flex items-center gap-2 px-3 py-2 hover:bg-white/5 transition-colors"
526
- >
527
- {expandedSection.has(`${step.stepId}-output`) ? (
528
- <ChevronDown className="w-3 h-3 text-gray-400" />
529
- ) : (
530
- <ChevronRight className="w-3 h-3 text-gray-400" />
531
- )}
532
- <span className="text-xs font-medium text-gray-400">
533
- Output
534
- </span>
535
- <code className="text-xs text-primary font-mono ml-auto">
536
- {step.outputVariable}
537
- </code>
538
- </button>
539
- {expandedSection.has(`${step.stepId}-output`) && (
540
- <div className="px-3 pb-3">
541
- <div className="flex items-start gap-2">
542
- <div className="flex-1 overflow-x-auto">
543
- <ValueRenderer
544
- value={step.output}
545
- onCopy={(key, val) => copyValue(key, val)}
546
- copiedKey={copiedKey}
547
- path={step.outputVariable || 'output'}
548
- />
549
- </div>
550
- <button
551
- onClick={() => copyValue(step.outputVariable || '', step.output)}
552
- className="p-1.5 hover:bg-white/10 rounded transition-colors"
553
- title="Copy output"
554
- >
555
- {copiedKey === step.outputVariable ? (
556
- <Check className="w-4 h-4 text-success" />
557
- ) : (
558
- <Copy className="w-4 h-4 text-gray-400" />
559
- )}
560
- </button>
561
- </div>
562
- </div>
563
- )}
564
- </div>
565
- )}
566
- </div>
567
- )}
568
- </div>
569
- );
570
- })}
571
- </div>
572
- );
573
- }
574
-
575
- function ValueRenderer({
576
- value,
577
- onCopy,
578
- copiedKey,
579
- path,
580
- depth = 0,
581
- }: {
582
- value: unknown;
583
- onCopy: (key: string, value: unknown) => void;
584
- copiedKey: string | null;
585
- path: string;
586
- depth?: number;
587
- }) {
588
- const [expanded, setExpanded] = useState(depth < 2);
589
-
590
- if (value === null) {
591
- return <span className="text-gray-500 font-mono text-xs">null</span>;
592
- }
593
-
594
- if (value === undefined) {
595
- return <span className="text-gray-500 font-mono text-xs">undefined</span>;
596
- }
597
-
598
- if (typeof value === 'boolean') {
599
- return (
600
- <span className={`font-mono text-xs ${value ? 'text-success' : 'text-error'}`}>
601
- {String(value)}
602
- </span>
603
- );
604
- }
605
-
606
- if (typeof value === 'number') {
607
- return <span className="text-warning font-mono text-xs">{value}</span>;
608
- }
609
-
610
- if (typeof value === 'string') {
611
- // Truncate long strings
612
- const displayValue = value.length > 200 ? value.substring(0, 200) + '...' : value;
613
- return (
614
- <span className="text-success font-mono text-xs">
615
- &quot;{displayValue}&quot;
616
- </span>
617
- );
618
- }
619
-
620
- if (Array.isArray(value)) {
621
- if (value.length === 0) {
622
- return <span className="text-gray-400 font-mono text-xs">[]</span>;
623
- }
624
-
625
- return (
626
- <div className="space-y-1">
627
- <button
628
- onClick={() => setExpanded(!expanded)}
629
- className="flex items-center gap-1 text-gray-400 hover:text-white transition-colors"
630
- >
631
- {expanded ? (
632
- <ChevronDown className="w-3 h-3" />
633
- ) : (
634
- <ChevronRight className="w-3 h-3" />
635
- )}
636
- <span className="text-xs font-mono">Array({value.length})</span>
637
- </button>
638
- {expanded && (
639
- <div className="ml-4 pl-2 border-l border-node-border space-y-1">
640
- {value.slice(0, 20).map((item, index) => (
641
- <div key={index} className="flex items-start gap-2">
642
- <span className="text-gray-500 font-mono text-xs">[{index}]:</span>
643
- <ValueRenderer
644
- value={item}
645
- onCopy={onCopy}
646
- copiedKey={copiedKey}
647
- path={`${path}[${index}]`}
648
- depth={depth + 1}
649
- />
650
- </div>
651
- ))}
652
- {value.length > 20 && (
653
- <div className="text-gray-500 text-xs">
654
- ... and {value.length - 20} more items
655
- </div>
656
- )}
657
- </div>
658
- )}
659
- </div>
660
- );
661
- }
662
-
663
- if (typeof value === 'object') {
664
- const entries = Object.entries(value);
665
- if (entries.length === 0) {
666
- return <span className="text-gray-400 font-mono text-xs">{'{}'}</span>;
667
- }
668
-
669
- return (
670
- <div className="space-y-1">
671
- <button
672
- onClick={() => setExpanded(!expanded)}
673
- className="flex items-center gap-1 text-gray-400 hover:text-white transition-colors"
674
- >
675
- {expanded ? (
676
- <ChevronDown className="w-3 h-3" />
677
- ) : (
678
- <ChevronRight className="w-3 h-3" />
679
- )}
680
- <span className="text-xs font-mono">Object({entries.length} keys)</span>
681
- </button>
682
- {expanded && (
683
- <div className="ml-4 pl-2 border-l border-node-border space-y-1">
684
- {entries.slice(0, 30).map(([key, val]) => (
685
- <div key={key} className="flex items-start gap-2">
686
- <span className="text-primary font-mono text-xs">{key}:</span>
687
- <ValueRenderer
688
- value={val}
689
- onCopy={onCopy}
690
- copiedKey={copiedKey}
691
- path={`${path}.${key}`}
692
- depth={depth + 1}
693
- />
694
- </div>
695
- ))}
696
- {entries.length > 30 && (
697
- <div className="text-gray-500 text-xs">
698
- ... and {entries.length - 30} more keys
699
- </div>
700
- )}
701
- </div>
702
- )}
703
- </div>
704
- );
705
- }
706
-
707
- return <span className="text-gray-400 font-mono text-xs">{String(value)}</span>;
708
- }
709
-
710
- function getTypeLabel(value: unknown): string {
711
- if (value === null) return 'null';
712
- if (value === undefined) return 'undefined';
713
- if (Array.isArray(value)) return `array[${value.length}]`;
714
- if (typeof value === 'object') return `object`;
715
- return typeof value;
716
- }
717
-
718
- function StatusIcon({ status }: { status: WorkflowStatus }) {
719
- switch (status) {
720
- case 'running':
721
- return <Loader2 className="w-5 h-5 text-primary animate-spin" />;
722
- case 'completed':
723
- return <CheckCircle className="w-5 h-5 text-success" />;
724
- case 'failed':
725
- return <XCircle className="w-5 h-5 text-error" />;
726
- case 'cancelled':
727
- return <Square className="w-5 h-5 text-gray-400" />;
728
- default:
729
- return <div className="w-5 h-5 rounded-full bg-gray-500" />;
730
- }
731
- }
732
-
733
- function StepStatusIcon({ status }: { status: StepStatus }) {
734
- switch (status) {
735
- case 'running':
736
- return <Loader2 className="w-4 h-4 text-warning animate-spin" />;
737
- case 'completed':
738
- return <CheckCircle className="w-4 h-4 text-success" />;
739
- case 'failed':
740
- return <XCircle className="w-4 h-4 text-error" />;
741
- case 'skipped':
742
- return <SkipForward className="w-4 h-4 text-gray-400" />;
743
- default:
744
- return <div className="w-4 h-4 rounded-full border-2 border-gray-500" />;
745
- }
746
- }
747
-
748
- function getStatusText(status: WorkflowStatus): string {
749
- switch (status) {
750
- case 'pending':
751
- return 'Pending';
752
- case 'running':
753
- return 'Executing Workflow...';
754
- case 'completed':
755
- return 'Workflow Completed';
756
- case 'failed':
757
- return 'Workflow Failed';
758
- case 'cancelled':
759
- return 'Workflow Cancelled';
760
- default:
761
- return 'Unknown';
762
- }
763
- }
764
-
765
- // Debug Panel Component
766
- function DebugPanel({
767
- debug,
768
- onClearBreakpoints,
769
- onAddWatchExpression,
770
- onRemoveWatchExpression,
771
- }: {
772
- debug: DebugState;
773
- onClearBreakpoints?: () => void;
774
- onAddWatchExpression?: (expression: string) => void;
775
- onRemoveWatchExpression?: (expression: string) => void;
776
- }) {
777
- const [newWatchExpr, setNewWatchExpr] = useState('');
778
-
779
- const handleAddWatch = () => {
780
- if (newWatchExpr.trim() && onAddWatchExpression) {
781
- onAddWatchExpression(newWatchExpr.trim());
782
- setNewWatchExpr('');
783
- }
784
- };
785
-
786
- return (
787
- <div className="space-y-4">
788
- {/* Breakpoints Section */}
789
- <div className="space-y-2">
790
- <div className="flex items-center justify-between">
791
- <h4 className="text-sm font-medium text-white flex items-center gap-2">
792
- <Circle className="w-3 h-3 text-error" />
793
- Breakpoints ({debug.breakpoints.size})
794
- </h4>
795
- {debug.breakpoints.size > 0 && onClearBreakpoints && (
796
- <button
797
- onClick={onClearBreakpoints}
798
- className="text-xs text-gray-400 hover:text-white flex items-center gap-1"
799
- >
800
- <Trash2 className="w-3 h-3" />
801
- Clear all
802
- </button>
803
- )}
804
- </div>
805
- <div className="bg-node-bg rounded-lg p-3">
806
- {debug.breakpoints.size === 0 ? (
807
- <div className="text-xs text-gray-500">
808
- No breakpoints set. Click the dot next to a step to add one.
809
- </div>
810
- ) : (
811
- <div className="space-y-1">
812
- {Array.from(debug.breakpoints).map((stepId) => (
813
- <div
814
- key={stepId}
815
- className="flex items-center gap-2 text-xs text-gray-300"
816
- >
817
- <Circle className="w-2 h-2 fill-current text-error" />
818
- <span className="font-mono">{stepId}</span>
819
- </div>
820
- ))}
821
- </div>
822
- )}
823
- </div>
824
- </div>
825
-
826
- {/* Call Stack Section */}
827
- <div className="space-y-2">
828
- <h4 className="text-sm font-medium text-white">Call Stack</h4>
829
- <div className="bg-node-bg rounded-lg p-3">
830
- {debug.callStack.length === 0 ? (
831
- <div className="text-xs text-gray-500">No active call stack</div>
832
- ) : (
833
- <div className="space-y-1">
834
- {debug.callStack.map((frame, index) => (
835
- <div
836
- key={index}
837
- className={`flex items-center gap-2 text-xs ${
838
- index === 0 ? 'text-primary' : 'text-gray-400'
839
- }`}
840
- >
841
- <ArrowRight className="w-3 h-3" />
842
- <span className="font-mono">{frame}</span>
843
- </div>
844
- ))}
845
- </div>
846
- )}
847
- </div>
848
- </div>
849
-
850
- {/* Watch Expressions Section */}
851
- <div className="space-y-2">
852
- <h4 className="text-sm font-medium text-white">Watch Expressions</h4>
853
- <div className="bg-node-bg rounded-lg p-3 space-y-2">
854
- {/* Add new watch expression */}
855
- {onAddWatchExpression && (
856
- <div className="flex items-center gap-2">
857
- <input
858
- type="text"
859
- value={newWatchExpr}
860
- onChange={(e) => setNewWatchExpr(e.target.value)}
861
- onKeyDown={(e) => e.key === 'Enter' && handleAddWatch()}
862
- placeholder="Add expression..."
863
- className="flex-1 bg-transparent border border-node-border rounded px-2 py-1 text-xs text-white placeholder-gray-500 focus:outline-none focus:border-primary"
864
- />
865
- <button
866
- onClick={handleAddWatch}
867
- disabled={!newWatchExpr.trim()}
868
- className="p-1 text-gray-400 hover:text-white disabled:opacity-50"
869
- >
870
- <Plus className="w-4 h-4" />
871
- </button>
872
- </div>
873
- )}
874
-
875
- {/* Watch list */}
876
- {debug.watchExpressions.length === 0 ? (
877
- <div className="text-xs text-gray-500">
878
- No watch expressions. Add an expression to monitor its value.
879
- </div>
880
- ) : (
881
- <div className="space-y-1">
882
- {debug.watchExpressions.map((expr) => (
883
- <div
884
- key={expr}
885
- className="flex items-center justify-between gap-2 text-xs group"
886
- >
887
- <div className="flex items-center gap-2 min-w-0">
888
- <span className="text-primary font-mono truncate">{expr}</span>
889
- <span className="text-gray-500">=</span>
890
- <span className="text-gray-300 font-mono truncate">
891
- (not evaluated)
892
- </span>
893
- </div>
894
- {onRemoveWatchExpression && (
895
- <button
896
- onClick={() => onRemoveWatchExpression(expr)}
897
- className="p-1 text-gray-500 hover:text-error opacity-0 group-hover:opacity-100 transition-opacity"
898
- >
899
- <X className="w-3 h-3" />
900
- </button>
901
- )}
902
- </div>
903
- ))}
904
- </div>
905
- )}
906
- </div>
907
- </div>
908
-
909
- {/* Debug State Info */}
910
- <div className="space-y-2">
911
- <h4 className="text-sm font-medium text-white">Debug State</h4>
912
- <div className="bg-node-bg rounded-lg p-3 text-xs space-y-1">
913
- <div className="flex items-center justify-between">
914
- <span className="text-gray-400">Current Step:</span>
915
- <span className="text-white font-mono">
916
- {debug.currentStepId || '(none)'}
917
- </span>
918
- </div>
919
- <div className="flex items-center justify-between">
920
- <span className="text-gray-400">Paused at Breakpoint:</span>
921
- <span className={debug.pausedAtBreakpoint ? 'text-error' : 'text-gray-500'}>
922
- {debug.pausedAtBreakpoint ? 'Yes' : 'No'}
923
- </span>
924
- </div>
925
- <div className="flex items-center justify-between">
926
- <span className="text-gray-400">Step Over Pending:</span>
927
- <span className={debug.stepOverPending ? 'text-warning' : 'text-gray-500'}>
928
- {debug.stepOverPending ? 'Yes' : 'No'}
929
- </span>
930
- </div>
931
- </div>
932
- </div>
933
- </div>
934
- );
935
- }