@startsimpli/ui 0.4.22 → 0.4.23
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/package.json +1 -1
- package/src/components/workflows/WorkflowListComposer.tsx +644 -0
- package/src/components/workflows/WorkflowSettingsCard.tsx +208 -0
- package/src/components/workflows/WorkflowStatsOverview.tsx +175 -0
- package/src/components/workflows/WorkflowTemplateGallery.tsx +608 -0
- package/src/components/workflows/index.ts +24 -0
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, type ReactNode } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
Zap,
|
|
6
|
+
Globe,
|
|
7
|
+
MousePointerClick,
|
|
8
|
+
Cpu,
|
|
9
|
+
Workflow,
|
|
10
|
+
Sparkles,
|
|
11
|
+
Loader2,
|
|
12
|
+
} from 'lucide-react';
|
|
13
|
+
import { Button } from '../ui/button';
|
|
14
|
+
import {
|
|
15
|
+
Dialog,
|
|
16
|
+
DialogContent,
|
|
17
|
+
DialogDescription,
|
|
18
|
+
DialogHeader,
|
|
19
|
+
DialogTitle,
|
|
20
|
+
} from '../ui/dialog';
|
|
21
|
+
import { Card, CardContent } from '../ui/card';
|
|
22
|
+
import { Badge } from '../ui/badge';
|
|
23
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs';
|
|
24
|
+
|
|
25
|
+
// ── Mini workflow diagram (ported inline — small + pure) ──
|
|
26
|
+
|
|
27
|
+
type DiagramColor =
|
|
28
|
+
| 'blue'
|
|
29
|
+
| 'orange'
|
|
30
|
+
| 'purple'
|
|
31
|
+
| 'green'
|
|
32
|
+
| 'gray'
|
|
33
|
+
| 'indigo'
|
|
34
|
+
| 'cyan'
|
|
35
|
+
| 'emerald'
|
|
36
|
+
| 'teal';
|
|
37
|
+
|
|
38
|
+
interface DiagramNode {
|
|
39
|
+
x: number;
|
|
40
|
+
y: number;
|
|
41
|
+
color: DiagramColor;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface DiagramConnection {
|
|
45
|
+
from: number;
|
|
46
|
+
to: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface MiniWorkflowDiagramProps {
|
|
50
|
+
nodes: DiagramNode[];
|
|
51
|
+
connections: DiagramConnection[];
|
|
52
|
+
isLoading?: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const colorMap: Record<DiagramColor, string> = {
|
|
56
|
+
blue: 'fill-blue-500/60',
|
|
57
|
+
orange: 'fill-orange-500/60',
|
|
58
|
+
purple: 'fill-purple-500/60',
|
|
59
|
+
green: 'fill-green-500/60',
|
|
60
|
+
gray: 'fill-gray-500/60',
|
|
61
|
+
indigo: 'fill-indigo-500/60',
|
|
62
|
+
cyan: 'fill-cyan-500/60',
|
|
63
|
+
emerald: 'fill-emerald-500/60',
|
|
64
|
+
teal: 'fill-teal-500/60',
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
function MiniWorkflowDiagram({
|
|
68
|
+
nodes,
|
|
69
|
+
connections,
|
|
70
|
+
isLoading = false,
|
|
71
|
+
}: MiniWorkflowDiagramProps) {
|
|
72
|
+
return (
|
|
73
|
+
<div className="relative w-full h-20 bg-muted/50 rounded-md overflow-hidden">
|
|
74
|
+
{isLoading ? (
|
|
75
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
76
|
+
<Loader2 className="w-6 h-6 animate-spin text-primary" />
|
|
77
|
+
</div>
|
|
78
|
+
) : (
|
|
79
|
+
<svg
|
|
80
|
+
width="188"
|
|
81
|
+
height="80"
|
|
82
|
+
viewBox="0 0 188 80"
|
|
83
|
+
className="w-full h-full"
|
|
84
|
+
role="img"
|
|
85
|
+
aria-label="Workflow diagram preview"
|
|
86
|
+
>
|
|
87
|
+
{/* Connection lines */}
|
|
88
|
+
<g className="text-muted-foreground/30">
|
|
89
|
+
{connections.map((conn, index) => {
|
|
90
|
+
const fromNode = nodes[conn.from];
|
|
91
|
+
const toNode = nodes[conn.to];
|
|
92
|
+
return (
|
|
93
|
+
<line
|
|
94
|
+
key={index}
|
|
95
|
+
x1={fromNode.x + 12}
|
|
96
|
+
y1={fromNode.y + 12}
|
|
97
|
+
x2={toNode.x + 12}
|
|
98
|
+
y2={toNode.y + 12}
|
|
99
|
+
stroke="currentColor"
|
|
100
|
+
strokeWidth="1.5"
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
103
|
+
})}
|
|
104
|
+
</g>
|
|
105
|
+
|
|
106
|
+
{/* Nodes */}
|
|
107
|
+
{nodes.map((node, index) => (
|
|
108
|
+
<rect
|
|
109
|
+
key={index}
|
|
110
|
+
x={node.x}
|
|
111
|
+
y={node.y}
|
|
112
|
+
width="24"
|
|
113
|
+
height="24"
|
|
114
|
+
rx="4"
|
|
115
|
+
className={colorMap[node.color]}
|
|
116
|
+
/>
|
|
117
|
+
))}
|
|
118
|
+
</svg>
|
|
119
|
+
)}
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Predefined diagram patterns for common workflow templates.
|
|
126
|
+
* Matches real backend workflow definitions.
|
|
127
|
+
*/
|
|
128
|
+
const DiagramPatterns = {
|
|
129
|
+
// E2E Pipeline: trigger → build → deploy → run_suite (4 nodes linear)
|
|
130
|
+
e2ePipeline: {
|
|
131
|
+
nodes: [
|
|
132
|
+
{ x: 20, y: 28, color: 'purple' as const },
|
|
133
|
+
{ x: 67, y: 28, color: 'green' as const },
|
|
134
|
+
{ x: 114, y: 28, color: 'green' as const },
|
|
135
|
+
{ x: 161, y: 28, color: 'indigo' as const },
|
|
136
|
+
],
|
|
137
|
+
connections: [
|
|
138
|
+
{ from: 0, to: 1 },
|
|
139
|
+
{ from: 1, to: 2 },
|
|
140
|
+
{ from: 2, to: 3 },
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
// Crawler: trigger → setup → crawl → kg_import → bootstrap → teardown (6 nodes)
|
|
145
|
+
crawler: {
|
|
146
|
+
nodes: [
|
|
147
|
+
{ x: 5, y: 28, color: 'purple' as const },
|
|
148
|
+
{ x: 38, y: 28, color: 'blue' as const },
|
|
149
|
+
{ x: 71, y: 28, color: 'cyan' as const },
|
|
150
|
+
{ x: 104, y: 28, color: 'emerald' as const },
|
|
151
|
+
{ x: 137, y: 28, color: 'emerald' as const },
|
|
152
|
+
{ x: 170, y: 28, color: 'blue' as const },
|
|
153
|
+
],
|
|
154
|
+
connections: [
|
|
155
|
+
{ from: 0, to: 1 },
|
|
156
|
+
{ from: 1, to: 2 },
|
|
157
|
+
{ from: 2, to: 3 },
|
|
158
|
+
{ from: 3, to: 4 },
|
|
159
|
+
{ from: 4, to: 5 },
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
// E2E Test Run: trigger → setup → execute_task → teardown (4 nodes)
|
|
164
|
+
e2eTestRun: {
|
|
165
|
+
nodes: [
|
|
166
|
+
{ x: 20, y: 28, color: 'purple' as const },
|
|
167
|
+
{ x: 67, y: 28, color: 'blue' as const },
|
|
168
|
+
{ x: 114, y: 28, color: 'cyan' as const },
|
|
169
|
+
{ x: 161, y: 28, color: 'blue' as const },
|
|
170
|
+
],
|
|
171
|
+
connections: [
|
|
172
|
+
{ from: 0, to: 1 },
|
|
173
|
+
{ from: 1, to: 2 },
|
|
174
|
+
{ from: 2, to: 3 },
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
// Agent Run: trigger → setup → execute_task → evaluate → teardown (5 nodes)
|
|
179
|
+
agentRun: {
|
|
180
|
+
nodes: [
|
|
181
|
+
{ x: 10, y: 28, color: 'purple' as const },
|
|
182
|
+
{ x: 47, y: 28, color: 'blue' as const },
|
|
183
|
+
{ x: 84, y: 28, color: 'cyan' as const },
|
|
184
|
+
{ x: 121, y: 28, color: 'teal' as const },
|
|
185
|
+
{ x: 158, y: 28, color: 'blue' as const },
|
|
186
|
+
],
|
|
187
|
+
connections: [
|
|
188
|
+
{ from: 0, to: 1 },
|
|
189
|
+
{ from: 1, to: 2 },
|
|
190
|
+
{ from: 2, to: 3 },
|
|
191
|
+
{ from: 3, to: 4 },
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
// App Evaluation: trigger → setup → execute_task → teardown (4 nodes)
|
|
196
|
+
appEvaluation: {
|
|
197
|
+
nodes: [
|
|
198
|
+
{ x: 20, y: 28, color: 'purple' as const },
|
|
199
|
+
{ x: 67, y: 28, color: 'blue' as const },
|
|
200
|
+
{ x: 114, y: 28, color: 'cyan' as const },
|
|
201
|
+
{ x: 161, y: 28, color: 'blue' as const },
|
|
202
|
+
],
|
|
203
|
+
connections: [
|
|
204
|
+
{ from: 0, to: 1 },
|
|
205
|
+
{ from: 1, to: 2 },
|
|
206
|
+
{ from: 2, to: 3 },
|
|
207
|
+
],
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
// Raw Crawl: trigger → setup → crawl → kg_import → teardown (5 nodes)
|
|
211
|
+
rawCrawl: {
|
|
212
|
+
nodes: [
|
|
213
|
+
{ x: 10, y: 28, color: 'purple' as const },
|
|
214
|
+
{ x: 50, y: 28, color: 'blue' as const },
|
|
215
|
+
{ x: 90, y: 28, color: 'cyan' as const },
|
|
216
|
+
{ x: 130, y: 28, color: 'emerald' as const },
|
|
217
|
+
{ x: 170, y: 28, color: 'blue' as const },
|
|
218
|
+
],
|
|
219
|
+
connections: [
|
|
220
|
+
{ from: 0, to: 1 },
|
|
221
|
+
{ from: 1, to: 2 },
|
|
222
|
+
{ from: 2, to: 3 },
|
|
223
|
+
{ from: 3, to: 4 },
|
|
224
|
+
],
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// ── Template model ──
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Opaque workflow definition carried by a template. Kept loose so this
|
|
232
|
+
* gallery stays free of app workflow-detail type coupling; the consumer
|
|
233
|
+
* receives it verbatim via {@link WorkflowTemplateGalleryProps.onSelectTemplate}.
|
|
234
|
+
*/
|
|
235
|
+
export type WorkflowTemplateDefinition = Record<string, unknown>;
|
|
236
|
+
|
|
237
|
+
export interface WorkflowTemplate {
|
|
238
|
+
id: string;
|
|
239
|
+
name: string;
|
|
240
|
+
description: string;
|
|
241
|
+
category: 'pipeline' | 'browser' | 'orchestration' | 'testing';
|
|
242
|
+
nodeCount: number;
|
|
243
|
+
complexity: 'beginner' | 'intermediate' | 'advanced';
|
|
244
|
+
icon: ReactNode;
|
|
245
|
+
useCase: string;
|
|
246
|
+
teaches: string[];
|
|
247
|
+
workflow: WorkflowTemplateDefinition;
|
|
248
|
+
diagramPattern: keyof typeof DiagramPatterns;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Default pre-built workflow templates. Consumers may override the set via
|
|
253
|
+
* {@link WorkflowTemplateGalleryProps.templates}.
|
|
254
|
+
*/
|
|
255
|
+
export const WORKFLOW_TEMPLATES: WorkflowTemplate[] = [
|
|
256
|
+
{
|
|
257
|
+
id: 'e2e-pipeline',
|
|
258
|
+
name: 'E2E Pipeline',
|
|
259
|
+
description: 'Full CI/CD pipeline: build, deploy, then run E2E test suite',
|
|
260
|
+
category: 'pipeline',
|
|
261
|
+
nodeCount: 4,
|
|
262
|
+
complexity: 'advanced',
|
|
263
|
+
icon: <Workflow className="h-5 w-5" />,
|
|
264
|
+
useCase: 'End-to-end CI/CD testing pipeline',
|
|
265
|
+
teaches: [
|
|
266
|
+
'Event triggers',
|
|
267
|
+
'Container builds',
|
|
268
|
+
'Deployments',
|
|
269
|
+
'Test orchestration',
|
|
270
|
+
],
|
|
271
|
+
diagramPattern: 'e2ePipeline',
|
|
272
|
+
workflow: {
|
|
273
|
+
name: 'E2E Pipeline Template',
|
|
274
|
+
description: 'Build, deploy, and run E2E tests triggered by pipeline events',
|
|
275
|
+
nodes: [
|
|
276
|
+
{ id: 'node-1', type: 'trigger.event', name: 'Pipeline Trigger', parameters: { event: 'e2e_pipeline_requested' }, position: { x: 100, y: 200 } },
|
|
277
|
+
{ id: 'node-2', type: 'pipeline.build', name: 'Build Container Image', parameters: {}, position: { x: 400, y: 200 } },
|
|
278
|
+
{ id: 'node-3', type: 'pipeline.deploy', name: 'Deploy Container', parameters: {}, position: { x: 700, y: 200 } },
|
|
279
|
+
{ id: 'node-4', type: 'e2e.run_suite', name: 'Run E2E Test Suite', parameters: {}, position: { x: 1000, y: 200 } },
|
|
280
|
+
],
|
|
281
|
+
connections: {
|
|
282
|
+
'node-1': { main: [[{ node: 'node-2', type: 'main', index: 0 }]] },
|
|
283
|
+
'node-2': { main: [[{ node: 'node-3', type: 'main', index: 0 }]] },
|
|
284
|
+
'node-3': { main: [[{ node: 'node-4', type: 'main', index: 0 }]] },
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
id: 'crawler',
|
|
290
|
+
name: 'Crawler Workflow',
|
|
291
|
+
description: 'Crawl a web app, build knowledge graph, and bootstrap E2E tests',
|
|
292
|
+
category: 'browser',
|
|
293
|
+
nodeCount: 6,
|
|
294
|
+
complexity: 'advanced',
|
|
295
|
+
icon: <Globe className="h-5 w-5" />,
|
|
296
|
+
useCase: 'Discover app pages and auto-generate tests',
|
|
297
|
+
teaches: [
|
|
298
|
+
'Browser sessions',
|
|
299
|
+
'Surfer crawling',
|
|
300
|
+
'Knowledge graphs',
|
|
301
|
+
'Test generation',
|
|
302
|
+
],
|
|
303
|
+
diagramPattern: 'crawler',
|
|
304
|
+
workflow: {
|
|
305
|
+
name: 'Crawler Workflow Template',
|
|
306
|
+
description: 'Crawl application, import knowledge graph, and bootstrap E2E tests',
|
|
307
|
+
nodes: [
|
|
308
|
+
{ id: 'node-1', type: 'trigger.event', name: 'Crawl Trigger', parameters: { event: 'crawl_requested' }, position: { x: 100, y: 200 } },
|
|
309
|
+
{ id: 'node-2', type: 'browser.setup', name: 'Setup Browser', parameters: { headless: true, viewport_width: 1280, viewport_height: 720 }, position: { x: 350, y: 200 } },
|
|
310
|
+
{ id: 'node-3', type: 'surfer.crawl', name: 'Crawl Application', parameters: { timeout: 600 }, position: { x: 600, y: 200 } },
|
|
311
|
+
{ id: 'node-4', type: 'knowledge_graph.import', name: 'Import Knowledge Graph', parameters: {}, position: { x: 850, y: 200 } },
|
|
312
|
+
{ id: 'node-5', type: 'e2e.bootstrap', name: 'Bootstrap E2E Tests', parameters: {}, position: { x: 1100, y: 200 } },
|
|
313
|
+
{ id: 'node-6', type: 'browser.teardown', name: 'Teardown Browser', parameters: {}, position: { x: 1350, y: 200 } },
|
|
314
|
+
],
|
|
315
|
+
connections: {
|
|
316
|
+
'node-1': { main: [[{ node: 'node-2', type: 'main', index: 0 }]] },
|
|
317
|
+
'node-2': { main: [[{ node: 'node-3', type: 'main', index: 0 }]] },
|
|
318
|
+
'node-3': { main: [[{ node: 'node-4', type: 'main', index: 0 }]] },
|
|
319
|
+
'node-4': { main: [[{ node: 'node-5', type: 'main', index: 0 }]] },
|
|
320
|
+
'node-5': { main: [[{ node: 'node-6', type: 'main', index: 0 }]] },
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
id: 'raw-crawl',
|
|
326
|
+
name: 'Raw Crawl',
|
|
327
|
+
description: 'Crawl app and build knowledge graph only — no E2E bootstrap',
|
|
328
|
+
category: 'browser',
|
|
329
|
+
nodeCount: 5,
|
|
330
|
+
complexity: 'intermediate',
|
|
331
|
+
icon: <Globe className="h-5 w-5" />,
|
|
332
|
+
useCase: 'On-demand crawl + KG build, triggered via API',
|
|
333
|
+
teaches: ['Browser sessions', 'Surfer crawling', 'Knowledge graphs'],
|
|
334
|
+
diagramPattern: 'rawCrawl',
|
|
335
|
+
workflow: {
|
|
336
|
+
name: 'Raw Crawl Workflow Template',
|
|
337
|
+
description: 'Crawl application and import knowledge graph. No test generation.',
|
|
338
|
+
nodes: [
|
|
339
|
+
{ id: 'node-1', type: 'trigger.event', name: 'Raw Crawl Trigger', parameters: { event: 'raw_crawl_requested' }, position: { x: 100, y: 200 } },
|
|
340
|
+
{ id: 'node-2', type: 'browser.setup', name: 'Setup Browser', parameters: { headless: false, viewport_width: 1280, viewport_height: 720 }, position: { x: 350, y: 200 } },
|
|
341
|
+
{ id: 'node-3', type: 'surfer.crawl', name: 'Crawl Application', parameters: { timeout: 600 }, position: { x: 600, y: 200 } },
|
|
342
|
+
{ id: 'node-4', type: 'knowledge_graph.import', name: 'Import Knowledge Graph', parameters: {}, position: { x: 850, y: 200 } },
|
|
343
|
+
{ id: 'node-5', type: 'browser.teardown', name: 'Teardown Browser', parameters: {}, position: { x: 1100, y: 200 } },
|
|
344
|
+
],
|
|
345
|
+
connections: {
|
|
346
|
+
'node-1': { main: [[{ node: 'node-2', type: 'main', index: 0 }]] },
|
|
347
|
+
'node-2': { main: [[{ node: 'node-3', type: 'main', index: 0 }]] },
|
|
348
|
+
'node-3': { main: [[{ node: 'node-4', type: 'main', index: 0 }]] },
|
|
349
|
+
'node-4': { main: [[{ node: 'node-5', type: 'main', index: 0 }]] },
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
id: 'e2e-test-run',
|
|
355
|
+
name: 'E2E Test Execution',
|
|
356
|
+
description: 'Execute a single E2E test with browser automation via surfer',
|
|
357
|
+
category: 'testing',
|
|
358
|
+
nodeCount: 4,
|
|
359
|
+
complexity: 'beginner',
|
|
360
|
+
icon: <MousePointerClick className="h-5 w-5" />,
|
|
361
|
+
useCase: 'Run an individual E2E test',
|
|
362
|
+
teaches: ['Browser setup', 'Surfer task execution', 'Session teardown'],
|
|
363
|
+
diagramPattern: 'e2eTestRun',
|
|
364
|
+
workflow: {
|
|
365
|
+
name: 'E2E Test Execution Template',
|
|
366
|
+
description: 'Execute a single E2E test using surfer browser automation',
|
|
367
|
+
nodes: [
|
|
368
|
+
{ id: 'node-1', type: 'trigger.event', name: 'Test Trigger', parameters: { event: 'e2e_run_requested' }, position: { x: 100, y: 200 } },
|
|
369
|
+
{ id: 'node-2', type: 'browser.setup', name: 'Setup Browser', parameters: { headless: true, viewport_width: 1280, viewport_height: 720 }, position: { x: 400, y: 200 } },
|
|
370
|
+
{ id: 'node-3', type: 'surfer.execute_task', name: 'Execute Test', parameters: { max_steps: 50, timeout: 300 }, position: { x: 700, y: 200 } },
|
|
371
|
+
{ id: 'node-4', type: 'browser.teardown', name: 'Teardown Browser', parameters: {}, position: { x: 1000, y: 200 } },
|
|
372
|
+
],
|
|
373
|
+
connections: {
|
|
374
|
+
'node-1': { main: [[{ node: 'node-2', type: 'main', index: 0 }]] },
|
|
375
|
+
'node-2': { main: [[{ node: 'node-3', type: 'main', index: 0 }]] },
|
|
376
|
+
'node-3': { main: [[{ node: 'node-4', type: 'main', index: 0 }]] },
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
id: 'app-evaluation',
|
|
382
|
+
name: 'App Evaluation',
|
|
383
|
+
description: 'Evaluate an app deployment with surfer-driven browser tests',
|
|
384
|
+
category: 'testing',
|
|
385
|
+
nodeCount: 4,
|
|
386
|
+
complexity: 'beginner',
|
|
387
|
+
icon: <Zap className="h-5 w-5" />,
|
|
388
|
+
useCase: 'Verify a deployed app works correctly',
|
|
389
|
+
teaches: ['Event triggers', 'Browser automation', 'App evaluation'],
|
|
390
|
+
diagramPattern: 'appEvaluation',
|
|
391
|
+
workflow: {
|
|
392
|
+
name: 'App Evaluation Workflow Template',
|
|
393
|
+
description: 'Evaluate a deployed application with surfer browser automation',
|
|
394
|
+
nodes: [
|
|
395
|
+
{ id: 'node-1', type: 'trigger.event', name: 'Evaluation Trigger', parameters: { event: 'app_evaluation_requested' }, position: { x: 100, y: 200 } },
|
|
396
|
+
{ id: 'node-2', type: 'browser.setup', name: 'Setup Browser', parameters: { headless: true, viewport_width: 1280, viewport_height: 720 }, position: { x: 400, y: 200 } },
|
|
397
|
+
{ id: 'node-3', type: 'surfer.execute_task', name: 'Evaluate App', parameters: { max_steps: 50, timeout: 300 }, position: { x: 700, y: 200 } },
|
|
398
|
+
{ id: 'node-4', type: 'browser.teardown', name: 'Teardown Browser', parameters: {}, position: { x: 1000, y: 200 } },
|
|
399
|
+
],
|
|
400
|
+
connections: {
|
|
401
|
+
'node-1': { main: [[{ node: 'node-2', type: 'main', index: 0 }]] },
|
|
402
|
+
'node-2': { main: [[{ node: 'node-3', type: 'main', index: 0 }]] },
|
|
403
|
+
'node-3': { main: [[{ node: 'node-4', type: 'main', index: 0 }]] },
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
id: 'agent-run',
|
|
409
|
+
name: 'Agent Run',
|
|
410
|
+
description: 'Execute a browser agent with LLM brain evaluation',
|
|
411
|
+
category: 'browser',
|
|
412
|
+
nodeCount: 5,
|
|
413
|
+
complexity: 'intermediate',
|
|
414
|
+
icon: <Cpu className="h-5 w-5" />,
|
|
415
|
+
useCase: 'Run an AI agent with evaluation loop',
|
|
416
|
+
teaches: [
|
|
417
|
+
'Agent triggers',
|
|
418
|
+
'Browser sessions',
|
|
419
|
+
'Brain evaluation',
|
|
420
|
+
'Pass/fail routing',
|
|
421
|
+
],
|
|
422
|
+
diagramPattern: 'agentRun',
|
|
423
|
+
workflow: {
|
|
424
|
+
name: 'Agent Run Template',
|
|
425
|
+
description: 'Execute a browser agent and evaluate results with LLM brain',
|
|
426
|
+
nodes: [
|
|
427
|
+
{ id: 'node-1', type: 'trigger.event', name: 'Agent Trigger', parameters: { event: 'agent_run_requested' }, position: { x: 100, y: 200 } },
|
|
428
|
+
{ id: 'node-2', type: 'browser.setup', name: 'Setup Browser', parameters: { headless: true, viewport_width: 1280, viewport_height: 720 }, position: { x: 350, y: 200 } },
|
|
429
|
+
{ id: 'node-3', type: 'surfer.execute_task', name: 'Execute Agent Task', parameters: { max_steps: 50, timeout: 300 }, position: { x: 600, y: 200 } },
|
|
430
|
+
{ id: 'node-4', type: 'brain.evaluate', name: 'Evaluate Results', parameters: {}, position: { x: 850, y: 200 } },
|
|
431
|
+
{ id: 'node-5', type: 'browser.teardown', name: 'Teardown Browser', parameters: {}, position: { x: 1100, y: 200 } },
|
|
432
|
+
],
|
|
433
|
+
connections: {
|
|
434
|
+
'node-1': { main: [[{ node: 'node-2', type: 'main', index: 0 }]] },
|
|
435
|
+
'node-2': { main: [[{ node: 'node-3', type: 'main', index: 0 }]] },
|
|
436
|
+
'node-3': { main: [[{ node: 'node-4', type: 'main', index: 0 }]] },
|
|
437
|
+
'node-4': { main: [[{ node: 'node-5', type: 'main', index: 0 }]] },
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
];
|
|
442
|
+
|
|
443
|
+
export interface WorkflowTemplateGalleryProps {
|
|
444
|
+
open: boolean;
|
|
445
|
+
onOpenChange: (open: boolean) => void;
|
|
446
|
+
/** Invoked with the selected template's workflow definition. */
|
|
447
|
+
onSelectTemplate: (template: WorkflowTemplateDefinition) => void;
|
|
448
|
+
/** Override the default template set. Defaults to {@link WORKFLOW_TEMPLATES}. */
|
|
449
|
+
templates?: WorkflowTemplate[];
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export function WorkflowTemplateGallery({
|
|
453
|
+
open,
|
|
454
|
+
onOpenChange,
|
|
455
|
+
onSelectTemplate,
|
|
456
|
+
templates = WORKFLOW_TEMPLATES,
|
|
457
|
+
}: WorkflowTemplateGalleryProps) {
|
|
458
|
+
const [selectedCategory, setSelectedCategory] = useState<string>('all');
|
|
459
|
+
|
|
460
|
+
const filteredTemplates =
|
|
461
|
+
selectedCategory === 'all'
|
|
462
|
+
? templates
|
|
463
|
+
: templates.filter((t) => t.category === selectedCategory);
|
|
464
|
+
|
|
465
|
+
const handleTemplateClick = (template: WorkflowTemplate) => {
|
|
466
|
+
onSelectTemplate(template.workflow);
|
|
467
|
+
onOpenChange(false);
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
return (
|
|
471
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
472
|
+
<DialogContent className="max-w-4xl max-h-[80vh] overflow-y-auto">
|
|
473
|
+
<DialogHeader>
|
|
474
|
+
<DialogTitle className="text-2xl flex items-center gap-2">
|
|
475
|
+
<Sparkles className="h-6 w-6 text-primary" />
|
|
476
|
+
Workflow Templates
|
|
477
|
+
</DialogTitle>
|
|
478
|
+
<DialogDescription>
|
|
479
|
+
Choose a pre-built workflow to customize for your needs
|
|
480
|
+
</DialogDescription>
|
|
481
|
+
</DialogHeader>
|
|
482
|
+
|
|
483
|
+
<Tabs
|
|
484
|
+
value={selectedCategory}
|
|
485
|
+
onValueChange={setSelectedCategory}
|
|
486
|
+
className="w-full"
|
|
487
|
+
>
|
|
488
|
+
<TabsList className="grid w-full grid-cols-4">
|
|
489
|
+
<TabsTrigger value="all">All</TabsTrigger>
|
|
490
|
+
<TabsTrigger value="pipeline">Pipeline</TabsTrigger>
|
|
491
|
+
<TabsTrigger value="browser">Browser</TabsTrigger>
|
|
492
|
+
<TabsTrigger value="testing">Testing</TabsTrigger>
|
|
493
|
+
</TabsList>
|
|
494
|
+
|
|
495
|
+
<TabsContent value={selectedCategory} className="mt-6 space-y-4">
|
|
496
|
+
{filteredTemplates.map((template) => {
|
|
497
|
+
const diagramPattern = DiagramPatterns[template.diagramPattern];
|
|
498
|
+
return (
|
|
499
|
+
<Card
|
|
500
|
+
key={template.id}
|
|
501
|
+
className="cursor-pointer hover:shadow-md hover:border-primary/50 transition-all duration-150 group"
|
|
502
|
+
onClick={() => handleTemplateClick(template)}
|
|
503
|
+
role="button"
|
|
504
|
+
aria-label={`Use ${template.name} template`}
|
|
505
|
+
>
|
|
506
|
+
<CardContent className="pt-6">
|
|
507
|
+
<div className="flex items-start gap-4">
|
|
508
|
+
<div className="flex-shrink-0">
|
|
509
|
+
<div className="h-12 w-12 rounded-lg bg-primary/5 flex items-center justify-center group-hover:bg-primary/10 transition-colors mb-3">
|
|
510
|
+
{template.icon}
|
|
511
|
+
</div>
|
|
512
|
+
{/* Mini workflow diagram */}
|
|
513
|
+
<div className="w-[188px]">
|
|
514
|
+
<MiniWorkflowDiagram
|
|
515
|
+
nodes={diagramPattern.nodes}
|
|
516
|
+
connections={diagramPattern.connections}
|
|
517
|
+
/>
|
|
518
|
+
</div>
|
|
519
|
+
</div>
|
|
520
|
+
|
|
521
|
+
<div className="flex-1 min-w-0">
|
|
522
|
+
<div className="flex items-start justify-between gap-4 mb-2">
|
|
523
|
+
<div>
|
|
524
|
+
<h3 className="font-semibold text-lg">
|
|
525
|
+
{template.name}
|
|
526
|
+
</h3>
|
|
527
|
+
<p className="text-sm text-muted-foreground mt-1">
|
|
528
|
+
{template.description}
|
|
529
|
+
</p>
|
|
530
|
+
</div>
|
|
531
|
+
<Badge
|
|
532
|
+
variant="secondary"
|
|
533
|
+
className="whitespace-nowrap"
|
|
534
|
+
>
|
|
535
|
+
{template.nodeCount} nodes
|
|
536
|
+
</Badge>
|
|
537
|
+
</div>
|
|
538
|
+
|
|
539
|
+
<div className="flex items-center gap-4 mt-3 text-xs text-muted-foreground">
|
|
540
|
+
<span className="inline-flex items-center gap-1">
|
|
541
|
+
<strong>Use case:</strong> {template.useCase}
|
|
542
|
+
</span>
|
|
543
|
+
</div>
|
|
544
|
+
|
|
545
|
+
<div className="mt-2 flex flex-wrap gap-1">
|
|
546
|
+
{template.teaches.map((skill) => (
|
|
547
|
+
<Badge
|
|
548
|
+
key={skill}
|
|
549
|
+
variant="outline"
|
|
550
|
+
className="text-xs"
|
|
551
|
+
>
|
|
552
|
+
{skill}
|
|
553
|
+
</Badge>
|
|
554
|
+
))}
|
|
555
|
+
</div>
|
|
556
|
+
|
|
557
|
+
<div className="mt-3 flex items-center gap-2">
|
|
558
|
+
<Badge
|
|
559
|
+
variant={
|
|
560
|
+
template.complexity === 'beginner'
|
|
561
|
+
? 'default'
|
|
562
|
+
: template.complexity === 'intermediate'
|
|
563
|
+
? 'secondary'
|
|
564
|
+
: 'destructive'
|
|
565
|
+
}
|
|
566
|
+
className="text-xs"
|
|
567
|
+
>
|
|
568
|
+
{template.complexity}
|
|
569
|
+
</Badge>
|
|
570
|
+
<Badge
|
|
571
|
+
variant="outline"
|
|
572
|
+
className="text-xs capitalize"
|
|
573
|
+
>
|
|
574
|
+
{template.category}
|
|
575
|
+
</Badge>
|
|
576
|
+
</div>
|
|
577
|
+
</div>
|
|
578
|
+
</div>
|
|
579
|
+
{/* Screen reader description */}
|
|
580
|
+
<span id={`${template.id}-desc`} className="sr-only">
|
|
581
|
+
{template.description}. Creates a workflow with{' '}
|
|
582
|
+
{template.nodeCount} nodes. Teaches:{' '}
|
|
583
|
+
{template.teaches.join(', ')}. Complexity:{' '}
|
|
584
|
+
{template.complexity}.
|
|
585
|
+
</span>
|
|
586
|
+
</CardContent>
|
|
587
|
+
</Card>
|
|
588
|
+
);
|
|
589
|
+
})}
|
|
590
|
+
</TabsContent>
|
|
591
|
+
</Tabs>
|
|
592
|
+
|
|
593
|
+
{filteredTemplates.length === 0 && (
|
|
594
|
+
<div className="text-center py-12 text-muted-foreground">
|
|
595
|
+
<p>No templates found in this category.</p>
|
|
596
|
+
<Button
|
|
597
|
+
variant="link"
|
|
598
|
+
onClick={() => setSelectedCategory('all')}
|
|
599
|
+
className="mt-2"
|
|
600
|
+
>
|
|
601
|
+
View all templates
|
|
602
|
+
</Button>
|
|
603
|
+
</div>
|
|
604
|
+
)}
|
|
605
|
+
</DialogContent>
|
|
606
|
+
</Dialog>
|
|
607
|
+
);
|
|
608
|
+
}
|
|
@@ -85,3 +85,27 @@ export type {
|
|
|
85
85
|
WorkflowVersion,
|
|
86
86
|
WorkflowVersionHistoryProps,
|
|
87
87
|
} from './WorkflowVersionHistory';
|
|
88
|
+
|
|
89
|
+
// Page composers — workflow settings, stats, list, and template gallery
|
|
90
|
+
export { WorkflowSettingsCard } from './WorkflowSettingsCard';
|
|
91
|
+
export type { WorkflowSettingsCardProps } from './WorkflowSettingsCard';
|
|
92
|
+
export { WorkflowStatsOverview } from './WorkflowStatsOverview';
|
|
93
|
+
export type {
|
|
94
|
+
WorkflowStats,
|
|
95
|
+
WorkflowStatsOverviewProps,
|
|
96
|
+
} from './WorkflowStatsOverview';
|
|
97
|
+
export { WorkflowListComposer } from './WorkflowListComposer';
|
|
98
|
+
export type {
|
|
99
|
+
WorkflowListItem,
|
|
100
|
+
WorkflowCreateInput,
|
|
101
|
+
WorkflowListComposerProps,
|
|
102
|
+
} from './WorkflowListComposer';
|
|
103
|
+
export {
|
|
104
|
+
WorkflowTemplateGallery,
|
|
105
|
+
WORKFLOW_TEMPLATES,
|
|
106
|
+
} from './WorkflowTemplateGallery';
|
|
107
|
+
export type {
|
|
108
|
+
WorkflowTemplate,
|
|
109
|
+
WorkflowTemplateDefinition,
|
|
110
|
+
WorkflowTemplateGalleryProps,
|
|
111
|
+
} from './WorkflowTemplateGallery';
|