/* global React */ const { useState, useEffect, useRef, useMemo } = React; /* ============== Workflow templates ============== */ const TEMPLATES = { support: { name: "Support Ticket Triage", blurb: "Inbound email → classify intent → fetch context → draft reply → human review → send.", inputLabel: "Incoming email", sampleInputs: [ { from: "siti@retailco.my", subject: "Order #4821 — wrong size", body: "Hi, I received my order today but the shirt is XL not L. Could I get a swap?" }, { from: "ravi@startup.io", subject: "API rate limits?", body: "We're hitting 429s on /v1/agents. What are the per-minute limits on our plan?" }, { from: "linda@fnb.com", subject: "Invoice for May", body: "Could you resend the May invoice? Accounts couldn't find it in our inbox." }, ], nodes: [ { id: "in", label: "Email Inbox", sub: "Gmail · IMAP", x: 30, y: 110, kind: "trigger" }, { id: "cls", label: "Classify Intent", sub: "GPT-4o · few-shot", x: 250, y: 50, kind: "ai" }, { id: "rag", label: "Retrieve Context", sub: "Vector DB · top-k", x: 250, y: 200, kind: "tool" }, { id: "draft", label: "Draft Reply", sub: "GPT-4o · template", x: 470, y: 110, kind: "ai" }, { id: "review", label: "Human Review", sub: "Slack · #ops-inbox", x: 690, y: 50, kind: "human" }, { id: "send", label: "Send Reply", sub: "Gmail · API", x: 690, y: 200, kind: "action" }, { id: "log", label: "Audit Log", sub: "Postgres", x: 910, y: 110, kind: "store" }, ], edges: [ ["in","cls"], ["in","rag"], ["cls","draft"], ["rag","draft"], ["draft","review"], ["draft","send"], ["review","send"], ["send","log"] ], steps: [ { node: "in", log: (i) => `Received: "${i.subject}" from ${i.from}` }, { node: "cls", log: () => `Classified intent → category, confidence 0.94` }, { node: "rag", log: () => `Retrieved 3 relevant docs from knowledge base (12 ms)` }, { node: "draft", log: () => `Drafted reply (147 tokens) — tone: friendly, concise` }, { node: "review", log: () => `Posted to #ops-inbox · auto-approved (high confidence)` }, { node: "send", log: (i) => `Reply sent to ${i.from}` }, { node: "log", log: () => `Audit row written · trace_id=tk_8f3a2c` }, ], output: (i) => `Hi ${i.from.split("@")[0]},\n\nThanks for reaching out — sorted. Reply scheduled to send in 3s.\n\n— AI Agency support`, }, leads: { name: "Lead Enrichment", blurb: "New form submission → enrich firmographics → score → route to AE → notify Slack.", inputLabel: "Form submission", sampleInputs: [ { from: "marcus@axesoft.my", subject: "Demo request — Axesoft", body: "Hi, we'd like to see your AI automation in action. ~120 staff, KL-based logistics SaaS." }, { from: "yuki@bento.co", subject: "Pricing question", body: "Asking on behalf of our COO. Series A fintech, 40 staff, looking at process automation." }, ], nodes: [ { id: "in", label: "Form", sub: "Webhook", x: 30, y: 110, kind: "trigger" }, { id: "enr", label: "Enrich", sub: "Clearbit + LinkedIn", x: 250, y: 50, kind: "tool" }, { id: "geo", label: "Geo & Sector", sub: "Lookup", x: 250, y: 200, kind: "tool" }, { id: "score",label: "ICP Score", sub: "GPT-4o", x: 470, y: 110, kind: "ai" }, { id: "route",label: "Route to AE", sub: "Round-robin", x: 690, y: 50, kind: "action" }, { id: "slack",label: "Slack Notify", sub: "#sales-pipeline", x: 690, y: 200, kind: "action" }, { id: "crm", label: "Write to CRM", sub: "HubSpot", x: 910, y: 110, kind: "store" }, ], edges: [ ["in","enr"], ["in","geo"], ["enr","score"], ["geo","score"], ["score","route"], ["score","slack"], ["route","crm"], ["slack","crm"] ], steps: [ { node: "in", log: (i) => `Form submitted by ${i.from}` }, { node: "enr", log: () => `Enriched: company size, funding, tech stack` }, { node: "geo", log: () => `Resolved: APAC · SEA · Logistics SaaS` }, { node: "score", log: () => `ICP score: 8.4 / 10 — strong fit` }, { node: "route", log: () => `Assigned to AE: Hana Y. (next in rotation)` }, { node: "slack", log: () => `Posted to #sales-pipeline with full context` }, { node: "crm", log: () => `Contact + deal created in HubSpot` }, ], output: (i) => `New lead from ${i.from} routed to Hana — ICP 8.4 / 10. CRM record opened.`, }, invoice: { name: "Invoice Processing", blurb: "Inbox watcher → OCR PDF → match PO → validate totals → approve → post to ledger.", inputLabel: "Vendor invoice", sampleInputs: [ { from: "billing@vendor.co", subject: "Invoice INV-2049 — May", body: "PDF attached, total RM 12,840.00 due in 30 days." }, { from: "ar@logistico.my", subject: "Invoice 8821", body: "Monthly fulfilment charges, RM 4,210.00." }, ], nodes: [ { id: "in", label: "Vendor Inbox", sub: "IMAP watcher", x: 30, y: 110, kind: "trigger" }, { id: "ocr", label: "OCR Extract", sub: "Vision model", x: 250, y: 110, kind: "ai" }, { id: "po", label: "Match PO", sub: "ERP lookup", x: 470, y: 50, kind: "tool" }, { id: "val", label: "Validate Totals",sub: "Rules engine", x: 470, y: 200, kind: "tool" }, { id: "ap", label: "AP Approval", sub: "Slack approval", x: 690, y: 110, kind: "human" }, { id: "post",label: "Post to Ledger", sub: "Xero · API", x: 910, y: 110, kind: "store" }, ], edges: [ ["in","ocr"], ["ocr","po"], ["ocr","val"], ["po","ap"], ["val","ap"], ["ap","post"] ], steps: [ { node: "in", log: (i) => `Detected: ${i.subject} from ${i.from}` }, { node: "ocr", log: () => `Extracted line items + totals (1.2 s)` }, { node: "po", log: () => `Matched PO #PO-7741 — vendor authorised` }, { node: "val", log: () => `Totals reconcile · tax computed correctly` }, { node: "ap", log: () => `Auto-approved (under RM 20k threshold)` }, { node: "post", log: () => `Posted to Xero ledger · ref AP-2049` }, ], output: () => `Invoice approved and posted. Vendor notified of ETA payment date.`, }, }; /* ============== Visualization ============== */ function nodeColor(kind, accent) { const map = { trigger: accent, ai: "#8b5cf6", tool: "#0891b2", human: "#f59e0b", action: "#16a34a", store: "#64748b", }; return map[kind] || accent; } function nodeIcon(kind) { const stroke = { stroke: "currentColor", fill: "none", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round" }; switch (kind) { case "trigger": return ; case "ai": return ; case "tool": return ; case "human": return ; case "action": return ; case "store": return ; default: return null; } } function pathFor(a, b) { // smooth cubic curve from right edge of a to left edge of b const x1 = a.x + 170, y1 = a.y + 34; const x2 = b.x, y2 = b.y + 34; const dx = (x2 - x1) * 0.5; return `M ${x1} ${y1} C ${x1+dx} ${y1}, ${x2-dx} ${y2}, ${x2} ${y2}`; } function WorkflowGraph({ template, activeNodes, activeEdges, accent }) { const W = 1100, H = 320; const nodes = template.nodes; const edges = template.edges; const nodeMap = useMemo(() => Object.fromEntries(nodes.map(n => [n.id, n])), [nodes]); return (
Pick a workflow, hit run, and watch each node fire. The logs on the right are what your team would see in production.
{template.output(input)}
We scope, prototype and ship workflows like this in 2-4 weeks.