@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,589 @@
|
|
|
1
|
+
import { useState, useMemo } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Settings,
|
|
4
|
+
Variable,
|
|
5
|
+
History,
|
|
6
|
+
X,
|
|
7
|
+
ChevronRight,
|
|
8
|
+
ChevronLeft,
|
|
9
|
+
CheckCircle,
|
|
10
|
+
XCircle,
|
|
11
|
+
Clock,
|
|
12
|
+
Loader2,
|
|
13
|
+
Trash2,
|
|
14
|
+
Play,
|
|
15
|
+
} from 'lucide-react';
|
|
16
|
+
import { useCanvasStore } from '../../stores/canvasStore';
|
|
17
|
+
import { useWorkflowStore } from '../../stores/workflowStore';
|
|
18
|
+
import { useLayoutStore } from '../../stores/layoutStore';
|
|
19
|
+
import {
|
|
20
|
+
useExecutionStore,
|
|
21
|
+
formatDuration,
|
|
22
|
+
formatRelativeTime,
|
|
23
|
+
type ExecutionRun,
|
|
24
|
+
} from '../../stores/executionStore';
|
|
25
|
+
|
|
26
|
+
type TabId = 'properties' | 'variables' | 'history';
|
|
27
|
+
|
|
28
|
+
export function PropertiesPanel() {
|
|
29
|
+
const [activeTab, setActiveTab] = useState<TabId>('properties');
|
|
30
|
+
const nodes = useCanvasStore((s) => s.nodes);
|
|
31
|
+
const selectedNodes = useMemo(() => nodes.filter((n) => n.selected), [nodes]);
|
|
32
|
+
const workflow = useWorkflowStore((s) => s.currentWorkflow);
|
|
33
|
+
const { propertiesPanelOpen, setPropertiesPanelOpen, breakpoint } = useLayoutStore();
|
|
34
|
+
|
|
35
|
+
// On mobile, show as an overlay when open
|
|
36
|
+
if (breakpoint === 'mobile') {
|
|
37
|
+
if (!propertiesPanelOpen) return null;
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<>
|
|
41
|
+
{/* Backdrop */}
|
|
42
|
+
<div
|
|
43
|
+
className="fixed inset-0 bg-black/50 z-40"
|
|
44
|
+
onClick={() => setPropertiesPanelOpen(false)}
|
|
45
|
+
/>
|
|
46
|
+
{/* Panel */}
|
|
47
|
+
<div className="fixed inset-y-0 right-0 w-80 max-w-[85vw] bg-panel-bg border-l border-node-border flex flex-col z-50 animate-slide-in-right">
|
|
48
|
+
<PropertiesPanelContent
|
|
49
|
+
activeTab={activeTab}
|
|
50
|
+
setActiveTab={setActiveTab}
|
|
51
|
+
selectedNodes={selectedNodes}
|
|
52
|
+
workflow={workflow}
|
|
53
|
+
onClose={() => setPropertiesPanelOpen(false)}
|
|
54
|
+
showClose
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
</>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Desktop/Tablet: collapsed state
|
|
62
|
+
if (!propertiesPanelOpen) {
|
|
63
|
+
return (
|
|
64
|
+
<button
|
|
65
|
+
onClick={() => setPropertiesPanelOpen(true)}
|
|
66
|
+
className="w-10 bg-panel-bg border-l border-node-border flex flex-col items-center py-4 gap-4 hover:bg-white/5 transition-colors"
|
|
67
|
+
aria-label="Expand properties panel"
|
|
68
|
+
>
|
|
69
|
+
<ChevronLeft className="w-4 h-4 text-gray-400" />
|
|
70
|
+
<Settings className="w-5 h-5 text-gray-400" />
|
|
71
|
+
</button>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div className="w-80 bg-panel-bg border-l border-node-border flex flex-col">
|
|
77
|
+
<PropertiesPanelContent
|
|
78
|
+
activeTab={activeTab}
|
|
79
|
+
setActiveTab={setActiveTab}
|
|
80
|
+
selectedNodes={selectedNodes}
|
|
81
|
+
workflow={workflow}
|
|
82
|
+
onClose={() => setPropertiesPanelOpen(false)}
|
|
83
|
+
showClose={breakpoint === 'tablet'}
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface PropertiesPanelContentProps {
|
|
90
|
+
activeTab: TabId;
|
|
91
|
+
setActiveTab: (tab: TabId) => void;
|
|
92
|
+
selectedNodes: any[];
|
|
93
|
+
workflow: any;
|
|
94
|
+
onClose: () => void;
|
|
95
|
+
showClose?: boolean;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function PropertiesPanelContent({
|
|
99
|
+
activeTab,
|
|
100
|
+
setActiveTab,
|
|
101
|
+
selectedNodes,
|
|
102
|
+
workflow,
|
|
103
|
+
onClose,
|
|
104
|
+
showClose,
|
|
105
|
+
}: PropertiesPanelContentProps) {
|
|
106
|
+
return (
|
|
107
|
+
<>
|
|
108
|
+
{/* Header */}
|
|
109
|
+
<div className="flex items-center justify-between p-4 border-b border-node-border">
|
|
110
|
+
<h2 className="text-sm font-medium text-white">Properties</h2>
|
|
111
|
+
{showClose && (
|
|
112
|
+
<button
|
|
113
|
+
onClick={onClose}
|
|
114
|
+
className="w-8 h-8 rounded-lg flex items-center justify-center hover:bg-white/10 transition-colors"
|
|
115
|
+
aria-label="Close properties panel"
|
|
116
|
+
>
|
|
117
|
+
<X className="w-4 h-4 text-gray-400" />
|
|
118
|
+
</button>
|
|
119
|
+
)}
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
{/* Tabs */}
|
|
123
|
+
<div className="flex border-b border-node-border">
|
|
124
|
+
<TabButton
|
|
125
|
+
active={activeTab === 'properties'}
|
|
126
|
+
onClick={() => setActiveTab('properties')}
|
|
127
|
+
icon={<Settings className="w-4 h-4" />}
|
|
128
|
+
label="Properties"
|
|
129
|
+
/>
|
|
130
|
+
<TabButton
|
|
131
|
+
active={activeTab === 'variables'}
|
|
132
|
+
onClick={() => setActiveTab('variables')}
|
|
133
|
+
icon={<Variable className="w-4 h-4" />}
|
|
134
|
+
label="Variables"
|
|
135
|
+
/>
|
|
136
|
+
<TabButton
|
|
137
|
+
active={activeTab === 'history'}
|
|
138
|
+
onClick={() => setActiveTab('history')}
|
|
139
|
+
icon={<History className="w-4 h-4" />}
|
|
140
|
+
label="History"
|
|
141
|
+
/>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
{/* Content */}
|
|
145
|
+
<div className="flex-1 overflow-y-auto">
|
|
146
|
+
{activeTab === 'properties' && (
|
|
147
|
+
<PropertiesTab selectedNodes={selectedNodes} workflow={workflow} />
|
|
148
|
+
)}
|
|
149
|
+
{activeTab === 'variables' && <VariablesTab />}
|
|
150
|
+
{activeTab === 'history' && <HistoryTab />}
|
|
151
|
+
</div>
|
|
152
|
+
</>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
interface TabButtonProps {
|
|
157
|
+
active: boolean;
|
|
158
|
+
onClick: () => void;
|
|
159
|
+
icon: React.ReactNode;
|
|
160
|
+
label: string;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function TabButton({ active, onClick, icon, label }: TabButtonProps) {
|
|
164
|
+
return (
|
|
165
|
+
<button
|
|
166
|
+
onClick={onClick}
|
|
167
|
+
className={`flex-1 flex items-center justify-center gap-1.5 px-3 py-2.5 text-xs font-medium transition-colors ${
|
|
168
|
+
active
|
|
169
|
+
? 'text-primary border-b-2 border-primary -mb-px'
|
|
170
|
+
: 'text-gray-400 hover:text-white'
|
|
171
|
+
}`}
|
|
172
|
+
>
|
|
173
|
+
{icon}
|
|
174
|
+
{label}
|
|
175
|
+
</button>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
interface PropertiesTabProps {
|
|
180
|
+
selectedNodes: any[];
|
|
181
|
+
workflow: any;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function PropertiesTab({ selectedNodes, workflow }: PropertiesTabProps) {
|
|
185
|
+
if (selectedNodes.length === 0) {
|
|
186
|
+
// Show workflow properties
|
|
187
|
+
return (
|
|
188
|
+
<div className="p-4 space-y-4">
|
|
189
|
+
<Section title="Workflow">
|
|
190
|
+
{workflow ? (
|
|
191
|
+
<div className="space-y-3">
|
|
192
|
+
<Property label="Name" value={workflow.metadata?.name || 'Untitled'} />
|
|
193
|
+
<Property label="Version" value={workflow.metadata?.version || '1.0.0'} />
|
|
194
|
+
<Property label="Author" value={workflow.metadata?.author || 'Unknown'} />
|
|
195
|
+
<Property
|
|
196
|
+
label="Steps"
|
|
197
|
+
value={`${workflow.steps?.length || 0} steps`}
|
|
198
|
+
/>
|
|
199
|
+
</div>
|
|
200
|
+
) : (
|
|
201
|
+
<div className="text-sm text-gray-500">No workflow loaded</div>
|
|
202
|
+
)}
|
|
203
|
+
</Section>
|
|
204
|
+
|
|
205
|
+
{workflow?.metadata?.description && (
|
|
206
|
+
<Section title="Description">
|
|
207
|
+
<p className="text-sm text-gray-300">
|
|
208
|
+
{workflow.metadata.description}
|
|
209
|
+
</p>
|
|
210
|
+
</Section>
|
|
211
|
+
)}
|
|
212
|
+
|
|
213
|
+
{workflow?.metadata?.tags && workflow.metadata.tags.length > 0 && (
|
|
214
|
+
<Section title="Tags">
|
|
215
|
+
<div className="flex flex-wrap gap-1.5">
|
|
216
|
+
{workflow.metadata.tags.map((tag: string) => (
|
|
217
|
+
<span
|
|
218
|
+
key={tag}
|
|
219
|
+
className="px-2 py-0.5 bg-primary/10 text-primary text-xs rounded-full"
|
|
220
|
+
>
|
|
221
|
+
{tag}
|
|
222
|
+
</span>
|
|
223
|
+
))}
|
|
224
|
+
</div>
|
|
225
|
+
</Section>
|
|
226
|
+
)}
|
|
227
|
+
</div>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (selectedNodes.length === 1) {
|
|
232
|
+
const node = selectedNodes[0];
|
|
233
|
+
return (
|
|
234
|
+
<div className="p-4 space-y-4">
|
|
235
|
+
<Section title="Step">
|
|
236
|
+
<div className="space-y-3">
|
|
237
|
+
<Property label="ID" value={node.data?.id || node.id} />
|
|
238
|
+
<Property label="Name" value={node.data?.name || '(unnamed)'} />
|
|
239
|
+
<Property label="Action" value={node.data?.action || node.data?.workflowPath || '-'} />
|
|
240
|
+
<Property
|
|
241
|
+
label="Status"
|
|
242
|
+
value={node.data?.status || 'pending'}
|
|
243
|
+
badge
|
|
244
|
+
badgeColor={
|
|
245
|
+
node.data?.status === 'completed'
|
|
246
|
+
? 'success'
|
|
247
|
+
: node.data?.status === 'failed'
|
|
248
|
+
? 'error'
|
|
249
|
+
: node.data?.status === 'running'
|
|
250
|
+
? 'warning'
|
|
251
|
+
: 'default'
|
|
252
|
+
}
|
|
253
|
+
/>
|
|
254
|
+
</div>
|
|
255
|
+
</Section>
|
|
256
|
+
|
|
257
|
+
{node.data?.error && (
|
|
258
|
+
<Section title="Error">
|
|
259
|
+
<div className="p-2 bg-error/10 border border-error/20 rounded text-xs text-error font-mono">
|
|
260
|
+
{node.data.error}
|
|
261
|
+
</div>
|
|
262
|
+
</Section>
|
|
263
|
+
)}
|
|
264
|
+
|
|
265
|
+
<Section title="Actions">
|
|
266
|
+
<div className="flex gap-2">
|
|
267
|
+
<button className="flex-1 px-3 py-1.5 bg-node-bg border border-node-border rounded text-xs text-gray-300 hover:border-primary transition-colors">
|
|
268
|
+
Edit
|
|
269
|
+
</button>
|
|
270
|
+
<button className="flex-1 px-3 py-1.5 bg-node-bg border border-node-border rounded text-xs text-gray-300 hover:border-primary transition-colors">
|
|
271
|
+
View YAML
|
|
272
|
+
</button>
|
|
273
|
+
</div>
|
|
274
|
+
</Section>
|
|
275
|
+
</div>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Multiple nodes selected
|
|
280
|
+
return (
|
|
281
|
+
<div className="p-4">
|
|
282
|
+
<div className="text-sm text-gray-400">
|
|
283
|
+
{selectedNodes.length} nodes selected
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function VariablesTab() {
|
|
290
|
+
const variables = [
|
|
291
|
+
{ name: 'inputs.repo', value: 'owner/repo', type: 'string' },
|
|
292
|
+
{ name: 'inputs.pr_number', value: '123', type: 'number' },
|
|
293
|
+
{ name: 'pr_details.title', value: 'Fix bug', type: 'string' },
|
|
294
|
+
{ name: 'pr_details.state', value: 'open', type: 'string' },
|
|
295
|
+
];
|
|
296
|
+
|
|
297
|
+
return (
|
|
298
|
+
<div className="p-4 space-y-2">
|
|
299
|
+
{variables.map((variable) => (
|
|
300
|
+
<div
|
|
301
|
+
key={variable.name}
|
|
302
|
+
className="p-3 bg-node-bg rounded-lg border border-node-border"
|
|
303
|
+
>
|
|
304
|
+
<div className="flex items-center justify-between mb-1">
|
|
305
|
+
<span className="text-xs font-mono text-primary">
|
|
306
|
+
{variable.name}
|
|
307
|
+
</span>
|
|
308
|
+
<span className="text-xs text-gray-500">{variable.type}</span>
|
|
309
|
+
</div>
|
|
310
|
+
<div className="text-sm text-gray-300 font-mono truncate">
|
|
311
|
+
{variable.value}
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
))}
|
|
315
|
+
</div>
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function HistoryTab() {
|
|
320
|
+
const { runs, clearHistory } = useExecutionStore();
|
|
321
|
+
const [selectedRun, setSelectedRun] = useState<ExecutionRun | null>(null);
|
|
322
|
+
|
|
323
|
+
if (selectedRun) {
|
|
324
|
+
return <RunDetailView run={selectedRun} onBack={() => setSelectedRun(null)} />;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (runs.length === 0) {
|
|
328
|
+
return (
|
|
329
|
+
<div className="p-4 text-center">
|
|
330
|
+
<div className="w-12 h-12 mx-auto mb-3 rounded-full bg-node-bg flex items-center justify-center">
|
|
331
|
+
<History className="w-6 h-6 text-gray-500" />
|
|
332
|
+
</div>
|
|
333
|
+
<p className="text-sm text-gray-400 mb-1">No execution history</p>
|
|
334
|
+
<p className="text-xs text-gray-500">
|
|
335
|
+
Run a workflow to see execution history here
|
|
336
|
+
</p>
|
|
337
|
+
</div>
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return (
|
|
342
|
+
<div className="flex flex-col h-full">
|
|
343
|
+
<div className="flex-1 overflow-y-auto p-4 space-y-2">
|
|
344
|
+
{runs.map((run) => (
|
|
345
|
+
<button
|
|
346
|
+
key={run.id}
|
|
347
|
+
onClick={() => setSelectedRun(run)}
|
|
348
|
+
className="w-full p-3 bg-node-bg rounded-lg border border-node-border cursor-pointer hover:border-primary transition-colors text-left"
|
|
349
|
+
>
|
|
350
|
+
<div className="flex items-center justify-between mb-1">
|
|
351
|
+
<span className="text-xs font-medium text-white truncate max-w-[140px]">
|
|
352
|
+
{run.workflowName}
|
|
353
|
+
</span>
|
|
354
|
+
<RunStatusBadge status={run.status} />
|
|
355
|
+
</div>
|
|
356
|
+
<div className="flex items-center justify-between text-xs text-gray-400">
|
|
357
|
+
<span>{run.duration ? formatDuration(run.duration) : '-'}</span>
|
|
358
|
+
<span>{formatRelativeTime(run.startTime)}</span>
|
|
359
|
+
</div>
|
|
360
|
+
{run.steps.length > 0 && (
|
|
361
|
+
<div className="mt-2 flex items-center gap-1">
|
|
362
|
+
{run.steps.slice(0, 5).map((step) => (
|
|
363
|
+
<StepStatusDot key={step.stepId} status={step.status} />
|
|
364
|
+
))}
|
|
365
|
+
{run.steps.length > 5 && (
|
|
366
|
+
<span className="text-xs text-gray-500">+{run.steps.length - 5}</span>
|
|
367
|
+
)}
|
|
368
|
+
</div>
|
|
369
|
+
)}
|
|
370
|
+
</button>
|
|
371
|
+
))}
|
|
372
|
+
</div>
|
|
373
|
+
{runs.length > 0 && (
|
|
374
|
+
<div className="p-3 border-t border-node-border">
|
|
375
|
+
<button
|
|
376
|
+
onClick={clearHistory}
|
|
377
|
+
className="w-full flex items-center justify-center gap-2 px-3 py-2 text-xs text-gray-400 hover:text-error transition-colors"
|
|
378
|
+
>
|
|
379
|
+
<Trash2 className="w-3.5 h-3.5" />
|
|
380
|
+
Clear History
|
|
381
|
+
</button>
|
|
382
|
+
</div>
|
|
383
|
+
)}
|
|
384
|
+
</div>
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
interface RunDetailViewProps {
|
|
389
|
+
run: ExecutionRun;
|
|
390
|
+
onBack: () => void;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function RunDetailView({ run, onBack }: RunDetailViewProps) {
|
|
394
|
+
const [showLogs, setShowLogs] = useState(false);
|
|
395
|
+
|
|
396
|
+
return (
|
|
397
|
+
<div className="flex flex-col h-full">
|
|
398
|
+
{/* Header */}
|
|
399
|
+
<div className="p-3 border-b border-node-border">
|
|
400
|
+
<button
|
|
401
|
+
onClick={onBack}
|
|
402
|
+
className="flex items-center gap-1 text-xs text-gray-400 hover:text-white mb-2"
|
|
403
|
+
>
|
|
404
|
+
<ChevronRight className="w-3 h-3 rotate-180" />
|
|
405
|
+
Back to history
|
|
406
|
+
</button>
|
|
407
|
+
<div className="flex items-center justify-between">
|
|
408
|
+
<span className="text-sm font-medium text-white truncate max-w-[180px]">
|
|
409
|
+
{run.workflowName}
|
|
410
|
+
</span>
|
|
411
|
+
<RunStatusBadge status={run.status} />
|
|
412
|
+
</div>
|
|
413
|
+
<div className="flex items-center justify-between mt-1 text-xs text-gray-400">
|
|
414
|
+
<span>{run.duration ? formatDuration(run.duration) : 'Running...'}</span>
|
|
415
|
+
<span>{new Date(run.startTime).toLocaleString()}</span>
|
|
416
|
+
</div>
|
|
417
|
+
</div>
|
|
418
|
+
|
|
419
|
+
{/* Tabs */}
|
|
420
|
+
<div className="flex border-b border-node-border">
|
|
421
|
+
<button
|
|
422
|
+
onClick={() => setShowLogs(false)}
|
|
423
|
+
className={`flex-1 px-3 py-2 text-xs font-medium transition-colors ${
|
|
424
|
+
!showLogs
|
|
425
|
+
? 'text-primary border-b-2 border-primary -mb-px'
|
|
426
|
+
: 'text-gray-400 hover:text-white'
|
|
427
|
+
}`}
|
|
428
|
+
>
|
|
429
|
+
Steps ({run.steps.length})
|
|
430
|
+
</button>
|
|
431
|
+
<button
|
|
432
|
+
onClick={() => setShowLogs(true)}
|
|
433
|
+
className={`flex-1 px-3 py-2 text-xs font-medium transition-colors ${
|
|
434
|
+
showLogs
|
|
435
|
+
? 'text-primary border-b-2 border-primary -mb-px'
|
|
436
|
+
: 'text-gray-400 hover:text-white'
|
|
437
|
+
}`}
|
|
438
|
+
>
|
|
439
|
+
Logs ({run.logs.length})
|
|
440
|
+
</button>
|
|
441
|
+
</div>
|
|
442
|
+
|
|
443
|
+
{/* Content */}
|
|
444
|
+
<div className="flex-1 overflow-y-auto p-3">
|
|
445
|
+
{showLogs ? (
|
|
446
|
+
<div className="space-y-1 font-mono text-xs">
|
|
447
|
+
{run.logs.map((log, i) => (
|
|
448
|
+
<div key={i} className="text-gray-300 break-words">
|
|
449
|
+
{log}
|
|
450
|
+
</div>
|
|
451
|
+
))}
|
|
452
|
+
</div>
|
|
453
|
+
) : (
|
|
454
|
+
<div className="space-y-2">
|
|
455
|
+
{run.steps.map((step) => (
|
|
456
|
+
<div
|
|
457
|
+
key={step.stepId}
|
|
458
|
+
className="p-2 bg-node-bg rounded border border-node-border"
|
|
459
|
+
>
|
|
460
|
+
<div className="flex items-center justify-between">
|
|
461
|
+
<span className="text-xs font-medium text-white truncate">
|
|
462
|
+
{step.stepName}
|
|
463
|
+
</span>
|
|
464
|
+
<StepStatusBadge status={step.status} />
|
|
465
|
+
</div>
|
|
466
|
+
{step.duration && (
|
|
467
|
+
<div className="text-xs text-gray-500 mt-1">
|
|
468
|
+
{formatDuration(step.duration)}
|
|
469
|
+
</div>
|
|
470
|
+
)}
|
|
471
|
+
{step.error && (
|
|
472
|
+
<div className="mt-2 p-2 bg-error/10 border border-error/20 rounded text-xs text-error">
|
|
473
|
+
{step.error}
|
|
474
|
+
</div>
|
|
475
|
+
)}
|
|
476
|
+
</div>
|
|
477
|
+
))}
|
|
478
|
+
</div>
|
|
479
|
+
)}
|
|
480
|
+
</div>
|
|
481
|
+
</div>
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function RunStatusBadge({ status }: { status: ExecutionRun['status'] }) {
|
|
486
|
+
const config = {
|
|
487
|
+
running: { bg: 'bg-warning/10', text: 'text-warning', icon: Loader2 },
|
|
488
|
+
completed: { bg: 'bg-success/10', text: 'text-success', icon: CheckCircle },
|
|
489
|
+
failed: { bg: 'bg-error/10', text: 'text-error', icon: XCircle },
|
|
490
|
+
cancelled: { bg: 'bg-gray-500/10', text: 'text-gray-400', icon: XCircle },
|
|
491
|
+
pending: { bg: 'bg-gray-500/10', text: 'text-gray-400', icon: Clock },
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
const { bg, text, icon: Icon } = config[status] || config.pending;
|
|
495
|
+
|
|
496
|
+
return (
|
|
497
|
+
<span className={`flex items-center gap-1 text-xs px-2 py-0.5 rounded-full ${bg} ${text}`}>
|
|
498
|
+
<Icon className={`w-3 h-3 ${status === 'running' ? 'animate-spin' : ''}`} />
|
|
499
|
+
{status}
|
|
500
|
+
</span>
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function StepStatusBadge({ status }: { status: string }) {
|
|
505
|
+
const config: Record<string, { bg: string; text: string }> = {
|
|
506
|
+
running: { bg: 'bg-warning/10', text: 'text-warning' },
|
|
507
|
+
completed: { bg: 'bg-success/10', text: 'text-success' },
|
|
508
|
+
failed: { bg: 'bg-error/10', text: 'text-error' },
|
|
509
|
+
pending: { bg: 'bg-gray-500/10', text: 'text-gray-400' },
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
const { bg, text } = config[status] || config.pending;
|
|
513
|
+
|
|
514
|
+
return (
|
|
515
|
+
<span className={`text-xs px-1.5 py-0.5 rounded ${bg} ${text}`}>
|
|
516
|
+
{status}
|
|
517
|
+
</span>
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function StepStatusDot({ status }: { status: string }) {
|
|
522
|
+
const colors: Record<string, string> = {
|
|
523
|
+
running: 'bg-warning',
|
|
524
|
+
completed: 'bg-success',
|
|
525
|
+
failed: 'bg-error',
|
|
526
|
+
pending: 'bg-gray-500',
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
return (
|
|
530
|
+
<div
|
|
531
|
+
className={`w-2 h-2 rounded-full ${colors[status] || colors.pending} ${
|
|
532
|
+
status === 'running' ? 'animate-pulse' : ''
|
|
533
|
+
}`}
|
|
534
|
+
title={status}
|
|
535
|
+
/>
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
interface SectionProps {
|
|
540
|
+
title: string;
|
|
541
|
+
children: React.ReactNode;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function Section({ title, children }: SectionProps) {
|
|
545
|
+
return (
|
|
546
|
+
<div>
|
|
547
|
+
<h3 className="text-xs font-medium text-gray-500 uppercase tracking-wider mb-2">
|
|
548
|
+
{title}
|
|
549
|
+
</h3>
|
|
550
|
+
{children}
|
|
551
|
+
</div>
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
interface PropertyProps {
|
|
556
|
+
label: string;
|
|
557
|
+
value: string;
|
|
558
|
+
badge?: boolean;
|
|
559
|
+
badgeColor?: 'default' | 'success' | 'error' | 'warning';
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function Property({
|
|
563
|
+
label,
|
|
564
|
+
value,
|
|
565
|
+
badge,
|
|
566
|
+
badgeColor = 'default',
|
|
567
|
+
}: PropertyProps) {
|
|
568
|
+
const colors = {
|
|
569
|
+
default: 'bg-gray-500/10 text-gray-400',
|
|
570
|
+
success: 'bg-success/10 text-success',
|
|
571
|
+
error: 'bg-error/10 text-error',
|
|
572
|
+
warning: 'bg-warning/10 text-warning',
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
return (
|
|
576
|
+
<div className="flex items-center justify-between">
|
|
577
|
+
<span className="text-xs text-gray-400">{label}</span>
|
|
578
|
+
{badge ? (
|
|
579
|
+
<span className={`text-xs px-2 py-0.5 rounded-full ${colors[badgeColor]}`}>
|
|
580
|
+
{value}
|
|
581
|
+
</span>
|
|
582
|
+
) : (
|
|
583
|
+
<span className="text-sm text-gray-200 font-mono truncate max-w-[180px]">
|
|
584
|
+
{value}
|
|
585
|
+
</span>
|
|
586
|
+
)}
|
|
587
|
+
</div>
|
|
588
|
+
);
|
|
589
|
+
}
|