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