flowEngine.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. /**
  2. * Flow Engine — executes saved Workflow flows for incoming chat messages.
  3. *
  4. * Execution model:
  5. * 1. Load all active flows from the DB
  6. * 2. For each flow, find its intent node and check if user message matches
  7. * 3. If matched, traverse the flow graph from intent → response nodes
  8. * 4. Return the response content (or null if no flow matched)
  9. */
  10. import { getDb } from "./db";
  11. import { workflowNodes } from "../drizzle/schema";
  12. import { eq } from "drizzle-orm";
  13. export interface FlowResult {
  14. content: string;
  15. shouldEscalate?: boolean;
  16. flowId: string;
  17. flowName?: string;
  18. }
  19. // Intent patterns for the 5 live Support Flows
  20. const FLOW_INTENT_PATTERNS: Record<string, RegExp[]> = {
  21. "check-order-status": [
  22. /\border\s*status\b/i,
  23. /\bcheck.*order\b/i,
  24. /\bwhere.*my order\b/i,
  25. /\border.*update\b/i,
  26. /\bmy order\b/i,
  27. ],
  28. "track-shipment": [
  29. /\btrack.*shipment\b/i,
  30. /\btracking\b/i,
  31. /\bshipment.*status\b/i,
  32. /\bwhere.*package\b/i,
  33. /\bdelivery.*status\b/i,
  34. /\btrack.*package\b/i,
  35. ],
  36. "submit-return": [
  37. /\breturn\b/i,
  38. /\bRMA\b/i,
  39. /\bsend.*back\b/i,
  40. /\breturn.*request\b/i,
  41. /\brefund\b/i,
  42. ],
  43. "cancel-order": [
  44. /\bcancel.*order\b/i,
  45. /\bcancel.*my order\b/i,
  46. /\bstop.*order\b/i,
  47. ],
  48. "faq-deflection": [
  49. /\bpayment\b/i,
  50. /\bwarranty\b/i,
  51. /\bhow.*long.*delivery\b/i,
  52. /\bshipping.*time\b/i,
  53. /\bminimum.*order\b/i,
  54. ],
  55. };
  56. // Fallback static responses for flows that don't have DB nodes yet
  57. const FLOW_STATIC_RESPONSES: Record<string, string | null> = {
  58. "check-order-status": "To check your order status, please provide your **Sales Order number** (e.g., SO-12345) and I'll look it up for you right away.",
  59. "track-shipment": "I can help you track your shipment! Please share your **order number** or **tracking number** and I'll pull up the latest status.",
  60. "submit-return": "I can guide you through the return process. Please provide:\n1. Your **order number**\n2. The **item(s)** you want to return\n3. The **reason** for the return\n\nOur return policy allows returns within 30 days of delivery.",
  61. "cancel-order": "I can help with order cancellation. Please provide your **order number** and I'll check if it's still within the cancellation window. Note: orders that have already shipped cannot be cancelled.",
  62. "faq-deflection": null, // handled by knowledge base
  63. };
  64. /**
  65. * Detect if the user message matches a flow intent.
  66. * Returns the flowId or null.
  67. */
  68. export function detectFlowIntent(message: string): string | null {
  69. for (const [flowId, patterns] of Object.entries(FLOW_INTENT_PATTERNS)) {
  70. if (patterns.some(p => p.test(message))) {
  71. return flowId;
  72. }
  73. }
  74. return null;
  75. }
  76. /**
  77. * Execute a flow by ID. Returns the response content or null.
  78. * Tries DB-saved nodes first, falls back to static responses.
  79. */
  80. export async function executeFlow(flowId: string, _userMessage: string): Promise<FlowResult | null> {
  81. // Try to load flow nodes from DB
  82. try {
  83. const db = await getDb();
  84. if (db) {
  85. const nodes = await db.select().from(workflowNodes).where(eq(workflowNodes.workflowId, flowId));
  86. if (nodes.length > 0) {
  87. const responseNodes = nodes.filter(n => n.type === "response" && (n.config as any)?.message);
  88. if (responseNodes.length > 0) {
  89. const escalationNode = nodes.find(n => n.type === "escalation");
  90. const content = responseNodes.map(n => (n.config as any).message).join("\n\n");
  91. return { content, shouldEscalate: !!escalationNode, flowId };
  92. }
  93. }
  94. }
  95. } catch (err) {
  96. console.error("[FlowEngine] DB error:", err);
  97. }
  98. const staticResponse = FLOW_STATIC_RESPONSES[flowId];
  99. if (staticResponse) {
  100. return { content: staticResponse, flowId };
  101. }
  102. return null;
  103. }