'use client'; import { useState, type ReactNode } from 'react'; import { Zap, Globe, MousePointerClick, Cpu, Workflow, Sparkles, Loader2, } from 'lucide-react'; import { Button } from '../ui/button'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '../ui/dialog'; import { Card, CardContent } from '../ui/card'; import { Badge } from '../ui/badge'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs'; // ── Mini workflow diagram (ported inline — small + pure) ── type DiagramColor = | 'blue' | 'orange' | 'purple' | 'green' | 'gray' | 'indigo' | 'cyan' | 'emerald' | 'teal'; interface DiagramNode { x: number; y: number; color: DiagramColor; } interface DiagramConnection { from: number; to: number; } interface MiniWorkflowDiagramProps { nodes: DiagramNode[]; connections: DiagramConnection[]; isLoading?: boolean; } const colorMap: Record = { blue: 'fill-blue-500/60', orange: 'fill-orange-500/60', purple: 'fill-purple-500/60', green: 'fill-green-500/60', gray: 'fill-gray-500/60', indigo: 'fill-indigo-500/60', cyan: 'fill-cyan-500/60', emerald: 'fill-emerald-500/60', teal: 'fill-teal-500/60', }; function MiniWorkflowDiagram({ nodes, connections, isLoading = false, }: MiniWorkflowDiagramProps) { return (
{isLoading ? (
) : ( {/* Connection lines */} {connections.map((conn, index) => { const fromNode = nodes[conn.from]; const toNode = nodes[conn.to]; return ( ); })} {/* Nodes */} {nodes.map((node, index) => ( ))} )}
); } /** * Predefined diagram patterns for common workflow templates. * Matches real backend workflow definitions. */ const DiagramPatterns = { // E2E Pipeline: trigger → build → deploy → run_suite (4 nodes linear) e2ePipeline: { nodes: [ { x: 20, y: 28, color: 'purple' as const }, { x: 67, y: 28, color: 'green' as const }, { x: 114, y: 28, color: 'green' as const }, { x: 161, y: 28, color: 'indigo' as const }, ], connections: [ { from: 0, to: 1 }, { from: 1, to: 2 }, { from: 2, to: 3 }, ], }, // Crawler: trigger → setup → crawl → kg_import → bootstrap → teardown (6 nodes) crawler: { nodes: [ { x: 5, y: 28, color: 'purple' as const }, { x: 38, y: 28, color: 'blue' as const }, { x: 71, y: 28, color: 'cyan' as const }, { x: 104, y: 28, color: 'emerald' as const }, { x: 137, y: 28, color: 'emerald' as const }, { x: 170, y: 28, color: 'blue' as const }, ], connections: [ { from: 0, to: 1 }, { from: 1, to: 2 }, { from: 2, to: 3 }, { from: 3, to: 4 }, { from: 4, to: 5 }, ], }, // E2E Test Run: trigger → setup → execute_task → teardown (4 nodes) e2eTestRun: { nodes: [ { x: 20, y: 28, color: 'purple' as const }, { x: 67, y: 28, color: 'blue' as const }, { x: 114, y: 28, color: 'cyan' as const }, { x: 161, y: 28, color: 'blue' as const }, ], connections: [ { from: 0, to: 1 }, { from: 1, to: 2 }, { from: 2, to: 3 }, ], }, // Agent Run: trigger → setup → execute_task → evaluate → teardown (5 nodes) agentRun: { nodes: [ { x: 10, y: 28, color: 'purple' as const }, { x: 47, y: 28, color: 'blue' as const }, { x: 84, y: 28, color: 'cyan' as const }, { x: 121, y: 28, color: 'teal' as const }, { x: 158, y: 28, color: 'blue' as const }, ], connections: [ { from: 0, to: 1 }, { from: 1, to: 2 }, { from: 2, to: 3 }, { from: 3, to: 4 }, ], }, // App Evaluation: trigger → setup → execute_task → teardown (4 nodes) appEvaluation: { nodes: [ { x: 20, y: 28, color: 'purple' as const }, { x: 67, y: 28, color: 'blue' as const }, { x: 114, y: 28, color: 'cyan' as const }, { x: 161, y: 28, color: 'blue' as const }, ], connections: [ { from: 0, to: 1 }, { from: 1, to: 2 }, { from: 2, to: 3 }, ], }, // Raw Crawl: trigger → setup → crawl → kg_import → teardown (5 nodes) rawCrawl: { nodes: [ { x: 10, y: 28, color: 'purple' as const }, { x: 50, y: 28, color: 'blue' as const }, { x: 90, y: 28, color: 'cyan' as const }, { x: 130, y: 28, color: 'emerald' as const }, { x: 170, y: 28, color: 'blue' as const }, ], connections: [ { from: 0, to: 1 }, { from: 1, to: 2 }, { from: 2, to: 3 }, { from: 3, to: 4 }, ], }, }; // ── Template model ── /** * Opaque workflow definition carried by a template. Kept loose so this * gallery stays free of app workflow-detail type coupling; the consumer * receives it verbatim via {@link WorkflowTemplateGalleryProps.onSelectTemplate}. */ export type WorkflowTemplateDefinition = Record; export interface WorkflowTemplate { id: string; name: string; description: string; category: 'pipeline' | 'browser' | 'orchestration' | 'testing'; nodeCount: number; complexity: 'beginner' | 'intermediate' | 'advanced'; icon: ReactNode; useCase: string; teaches: string[]; workflow: WorkflowTemplateDefinition; diagramPattern: keyof typeof DiagramPatterns; } /** * Default pre-built workflow templates. Consumers may override the set via * {@link WorkflowTemplateGalleryProps.templates}. */ export const WORKFLOW_TEMPLATES: WorkflowTemplate[] = [ { id: 'e2e-pipeline', name: 'E2E Pipeline', description: 'Full CI/CD pipeline: build, deploy, then run E2E test suite', category: 'pipeline', nodeCount: 4, complexity: 'advanced', icon: , useCase: 'End-to-end CI/CD testing pipeline', teaches: [ 'Event triggers', 'Container builds', 'Deployments', 'Test orchestration', ], diagramPattern: 'e2ePipeline', workflow: { name: 'E2E Pipeline Template', description: 'Build, deploy, and run E2E tests triggered by pipeline events', nodes: [ { id: 'node-1', type: 'trigger.event', name: 'Pipeline Trigger', parameters: { event: 'e2e_pipeline_requested' }, position: { x: 100, y: 200 } }, { id: 'node-2', type: 'pipeline.build', name: 'Build Container Image', parameters: {}, position: { x: 400, y: 200 } }, { id: 'node-3', type: 'pipeline.deploy', name: 'Deploy Container', parameters: {}, position: { x: 700, y: 200 } }, { id: 'node-4', type: 'e2e.run_suite', name: 'Run E2E Test Suite', parameters: {}, position: { x: 1000, y: 200 } }, ], connections: { 'node-1': { main: [[{ node: 'node-2', type: 'main', index: 0 }]] }, 'node-2': { main: [[{ node: 'node-3', type: 'main', index: 0 }]] }, 'node-3': { main: [[{ node: 'node-4', type: 'main', index: 0 }]] }, }, }, }, { id: 'crawler', name: 'Crawler Workflow', description: 'Crawl a web app, build knowledge graph, and bootstrap E2E tests', category: 'browser', nodeCount: 6, complexity: 'advanced', icon: , useCase: 'Discover app pages and auto-generate tests', teaches: [ 'Browser sessions', 'Surfer crawling', 'Knowledge graphs', 'Test generation', ], diagramPattern: 'crawler', workflow: { name: 'Crawler Workflow Template', description: 'Crawl application, import knowledge graph, and bootstrap E2E tests', nodes: [ { id: 'node-1', type: 'trigger.event', name: 'Crawl Trigger', parameters: { event: 'crawl_requested' }, position: { x: 100, y: 200 } }, { id: 'node-2', type: 'browser.setup', name: 'Setup Browser', parameters: { headless: true, viewport_width: 1280, viewport_height: 720 }, position: { x: 350, y: 200 } }, { id: 'node-3', type: 'surfer.crawl', name: 'Crawl Application', parameters: { timeout: 600 }, position: { x: 600, y: 200 } }, { id: 'node-4', type: 'knowledge_graph.import', name: 'Import Knowledge Graph', parameters: {}, position: { x: 850, y: 200 } }, { id: 'node-5', type: 'e2e.bootstrap', name: 'Bootstrap E2E Tests', parameters: {}, position: { x: 1100, y: 200 } }, { id: 'node-6', type: 'browser.teardown', name: 'Teardown Browser', parameters: {}, position: { x: 1350, y: 200 } }, ], connections: { 'node-1': { main: [[{ node: 'node-2', type: 'main', index: 0 }]] }, 'node-2': { main: [[{ node: 'node-3', type: 'main', index: 0 }]] }, 'node-3': { main: [[{ node: 'node-4', type: 'main', index: 0 }]] }, 'node-4': { main: [[{ node: 'node-5', type: 'main', index: 0 }]] }, 'node-5': { main: [[{ node: 'node-6', type: 'main', index: 0 }]] }, }, }, }, { id: 'raw-crawl', name: 'Raw Crawl', description: 'Crawl app and build knowledge graph only — no E2E bootstrap', category: 'browser', nodeCount: 5, complexity: 'intermediate', icon: , useCase: 'On-demand crawl + KG build, triggered via API', teaches: ['Browser sessions', 'Surfer crawling', 'Knowledge graphs'], diagramPattern: 'rawCrawl', workflow: { name: 'Raw Crawl Workflow Template', description: 'Crawl application and import knowledge graph. No test generation.', nodes: [ { id: 'node-1', type: 'trigger.event', name: 'Raw Crawl Trigger', parameters: { event: 'raw_crawl_requested' }, position: { x: 100, y: 200 } }, { id: 'node-2', type: 'browser.setup', name: 'Setup Browser', parameters: { headless: false, viewport_width: 1280, viewport_height: 720 }, position: { x: 350, y: 200 } }, { id: 'node-3', type: 'surfer.crawl', name: 'Crawl Application', parameters: { timeout: 600 }, position: { x: 600, y: 200 } }, { id: 'node-4', type: 'knowledge_graph.import', name: 'Import Knowledge Graph', parameters: {}, position: { x: 850, y: 200 } }, { id: 'node-5', type: 'browser.teardown', name: 'Teardown Browser', parameters: {}, position: { x: 1100, y: 200 } }, ], connections: { 'node-1': { main: [[{ node: 'node-2', type: 'main', index: 0 }]] }, 'node-2': { main: [[{ node: 'node-3', type: 'main', index: 0 }]] }, 'node-3': { main: [[{ node: 'node-4', type: 'main', index: 0 }]] }, 'node-4': { main: [[{ node: 'node-5', type: 'main', index: 0 }]] }, }, }, }, { id: 'e2e-test-run', name: 'E2E Test Execution', description: 'Execute a single E2E test with browser automation via surfer', category: 'testing', nodeCount: 4, complexity: 'beginner', icon: , useCase: 'Run an individual E2E test', teaches: ['Browser setup', 'Surfer task execution', 'Session teardown'], diagramPattern: 'e2eTestRun', workflow: { name: 'E2E Test Execution Template', description: 'Execute a single E2E test using surfer browser automation', nodes: [ { id: 'node-1', type: 'trigger.event', name: 'Test Trigger', parameters: { event: 'e2e_run_requested' }, position: { x: 100, y: 200 } }, { id: 'node-2', type: 'browser.setup', name: 'Setup Browser', parameters: { headless: true, viewport_width: 1280, viewport_height: 720 }, position: { x: 400, y: 200 } }, { id: 'node-3', type: 'surfer.execute_task', name: 'Execute Test', parameters: { max_steps: 50, timeout: 300 }, position: { x: 700, y: 200 } }, { id: 'node-4', type: 'browser.teardown', name: 'Teardown Browser', parameters: {}, position: { x: 1000, y: 200 } }, ], connections: { 'node-1': { main: [[{ node: 'node-2', type: 'main', index: 0 }]] }, 'node-2': { main: [[{ node: 'node-3', type: 'main', index: 0 }]] }, 'node-3': { main: [[{ node: 'node-4', type: 'main', index: 0 }]] }, }, }, }, { id: 'app-evaluation', name: 'App Evaluation', description: 'Evaluate an app deployment with surfer-driven browser tests', category: 'testing', nodeCount: 4, complexity: 'beginner', icon: , useCase: 'Verify a deployed app works correctly', teaches: ['Event triggers', 'Browser automation', 'App evaluation'], diagramPattern: 'appEvaluation', workflow: { name: 'App Evaluation Workflow Template', description: 'Evaluate a deployed application with surfer browser automation', nodes: [ { id: 'node-1', type: 'trigger.event', name: 'Evaluation Trigger', parameters: { event: 'app_evaluation_requested' }, position: { x: 100, y: 200 } }, { id: 'node-2', type: 'browser.setup', name: 'Setup Browser', parameters: { headless: true, viewport_width: 1280, viewport_height: 720 }, position: { x: 400, y: 200 } }, { id: 'node-3', type: 'surfer.execute_task', name: 'Evaluate App', parameters: { max_steps: 50, timeout: 300 }, position: { x: 700, y: 200 } }, { id: 'node-4', type: 'browser.teardown', name: 'Teardown Browser', parameters: {}, position: { x: 1000, y: 200 } }, ], connections: { 'node-1': { main: [[{ node: 'node-2', type: 'main', index: 0 }]] }, 'node-2': { main: [[{ node: 'node-3', type: 'main', index: 0 }]] }, 'node-3': { main: [[{ node: 'node-4', type: 'main', index: 0 }]] }, }, }, }, { id: 'agent-run', name: 'Agent Run', description: 'Execute a browser agent with LLM brain evaluation', category: 'browser', nodeCount: 5, complexity: 'intermediate', icon: , useCase: 'Run an AI agent with evaluation loop', teaches: [ 'Agent triggers', 'Browser sessions', 'Brain evaluation', 'Pass/fail routing', ], diagramPattern: 'agentRun', workflow: { name: 'Agent Run Template', description: 'Execute a browser agent and evaluate results with LLM brain', nodes: [ { id: 'node-1', type: 'trigger.event', name: 'Agent Trigger', parameters: { event: 'agent_run_requested' }, position: { x: 100, y: 200 } }, { id: 'node-2', type: 'browser.setup', name: 'Setup Browser', parameters: { headless: true, viewport_width: 1280, viewport_height: 720 }, position: { x: 350, y: 200 } }, { id: 'node-3', type: 'surfer.execute_task', name: 'Execute Agent Task', parameters: { max_steps: 50, timeout: 300 }, position: { x: 600, y: 200 } }, { id: 'node-4', type: 'brain.evaluate', name: 'Evaluate Results', parameters: {}, position: { x: 850, y: 200 } }, { id: 'node-5', type: 'browser.teardown', name: 'Teardown Browser', parameters: {}, position: { x: 1100, y: 200 } }, ], connections: { 'node-1': { main: [[{ node: 'node-2', type: 'main', index: 0 }]] }, 'node-2': { main: [[{ node: 'node-3', type: 'main', index: 0 }]] }, 'node-3': { main: [[{ node: 'node-4', type: 'main', index: 0 }]] }, 'node-4': { main: [[{ node: 'node-5', type: 'main', index: 0 }]] }, }, }, }, ]; export interface WorkflowTemplateGalleryProps { open: boolean; onOpenChange: (open: boolean) => void; /** Invoked with the selected template's workflow definition. */ onSelectTemplate: (template: WorkflowTemplateDefinition) => void; /** Override the default template set. Defaults to {@link WORKFLOW_TEMPLATES}. */ templates?: WorkflowTemplate[]; } export function WorkflowTemplateGallery({ open, onOpenChange, onSelectTemplate, templates = WORKFLOW_TEMPLATES, }: WorkflowTemplateGalleryProps) { const [selectedCategory, setSelectedCategory] = useState('all'); const filteredTemplates = selectedCategory === 'all' ? templates : templates.filter((t) => t.category === selectedCategory); const handleTemplateClick = (template: WorkflowTemplate) => { onSelectTemplate(template.workflow); onOpenChange(false); }; return ( Workflow Templates Choose a pre-built workflow to customize for your needs All Pipeline Browser Testing {filteredTemplates.map((template) => { const diagramPattern = DiagramPatterns[template.diagramPattern]; return ( handleTemplateClick(template)} role="button" aria-label={`Use ${template.name} template`} >
{template.icon}
{/* Mini workflow diagram */}

{template.name}

{template.description}

{template.nodeCount} nodes
Use case: {template.useCase}
{template.teaches.map((skill) => ( {skill} ))}
{template.complexity} {template.category}
{/* Screen reader description */} {template.description}. Creates a workflow with{' '} {template.nodeCount} nodes. Teaches:{' '} {template.teaches.join(', ')}. Complexity:{' '} {template.complexity}.
); })}
{filteredTemplates.length === 0 && (

No templates found in this category.

)}
); }