@botonic/plugin-flow-builder 0.57.0-rslib-esm.1 → 0.58.0-alpha-new-stack.0

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.
Files changed (98) hide show
  1. package/lib/action/ai-agent-from-user-input.js +8 -0
  2. package/lib/action/ai-agent-from-user-input.js.map +1 -1
  3. package/lib/action/first-interaction.js +10 -0
  4. package/lib/action/first-interaction.js.map +1 -1
  5. package/lib/action/get-contents.js +5 -2
  6. package/lib/action/get-contents.js.map +1 -1
  7. package/lib/action/index.js +8 -1
  8. package/lib/action/index.js.map +1 -1
  9. package/lib/api.d.ts +8 -3
  10. package/lib/api.js +37 -13
  11. package/lib/api.js.map +1 -1
  12. package/lib/bot-context-manager.d.ts +17 -0
  13. package/lib/bot-context-manager.js +101 -0
  14. package/lib/bot-context-manager.js.map +1 -0
  15. package/lib/constants.d.ts +2 -1
  16. package/lib/constants.js +4 -3
  17. package/lib/constants.js.map +1 -1
  18. package/lib/content-fields/contact-reasons-conditional-matcher.d.ts +4 -0
  19. package/lib/content-fields/contact-reasons-conditional-matcher.js +10 -0
  20. package/lib/content-fields/contact-reasons-conditional-matcher.js.map +1 -0
  21. package/lib/content-fields/flow-ai-agent-base.js +7 -1
  22. package/lib/content-fields/flow-ai-agent-base.js.map +1 -1
  23. package/lib/content-fields/flow-ai-agent-router.js +2 -1
  24. package/lib/content-fields/flow-ai-agent-router.js.map +1 -1
  25. package/lib/content-fields/flow-ai-agent.js +2 -1
  26. package/lib/content-fields/flow-ai-agent.js.map +1 -1
  27. package/lib/content-fields/flow-contact-reasons-conditional.d.ts +14 -0
  28. package/lib/content-fields/flow-contact-reasons-conditional.js +68 -0
  29. package/lib/content-fields/flow-contact-reasons-conditional.js.map +1 -0
  30. package/lib/content-fields/flow-do-nothing.d.ts +9 -0
  31. package/lib/content-fields/flow-do-nothing.js +33 -0
  32. package/lib/content-fields/flow-do-nothing.js.map +1 -0
  33. package/lib/content-fields/flow-handoff.d.ts +7 -1
  34. package/lib/content-fields/flow-handoff.js +41 -3
  35. package/lib/content-fields/flow-handoff.js.map +1 -1
  36. package/lib/content-fields/flow-whatsapp-request-contact-info.d.ts +2 -1
  37. package/lib/content-fields/flow-whatsapp-request-contact-info.js +11 -0
  38. package/lib/content-fields/flow-whatsapp-request-contact-info.js.map +1 -1
  39. package/lib/content-fields/hubtype-fields/contact-reasons-conditional.d.ts +16 -0
  40. package/lib/content-fields/hubtype-fields/contact-reasons-conditional.js +4 -0
  41. package/lib/content-fields/hubtype-fields/contact-reasons-conditional.js.map +1 -0
  42. package/lib/content-fields/hubtype-fields/do-nothing.d.ts +6 -0
  43. package/lib/content-fields/hubtype-fields/do-nothing.js +4 -0
  44. package/lib/content-fields/hubtype-fields/do-nothing.js.map +1 -0
  45. package/lib/content-fields/hubtype-fields/handoff.d.ts +11 -1
  46. package/lib/content-fields/hubtype-fields/handoff.js +7 -1
  47. package/lib/content-fields/hubtype-fields/handoff.js.map +1 -1
  48. package/lib/content-fields/hubtype-fields/index.d.ts +1 -0
  49. package/lib/content-fields/hubtype-fields/index.js +2 -0
  50. package/lib/content-fields/hubtype-fields/index.js.map +1 -1
  51. package/lib/content-fields/hubtype-fields/node-types.d.ts +3 -1
  52. package/lib/content-fields/hubtype-fields/node-types.js +2 -0
  53. package/lib/content-fields/hubtype-fields/node-types.js.map +1 -1
  54. package/lib/content-fields/hubtype-fields/nodes.d.ts +3 -1
  55. package/lib/content-fields/hubtype-fields/nodes.js.map +1 -1
  56. package/lib/content-fields/hubtype-fields/whatsapp-request-contact-info.d.ts +2 -0
  57. package/lib/content-fields/hubtype-fields/whatsapp-request-contact-info.js.map +1 -1
  58. package/lib/content-fields/index.d.ts +4 -2
  59. package/lib/content-fields/index.js +6 -0
  60. package/lib/content-fields/index.js.map +1 -1
  61. package/lib/flow-factory.js +5 -1
  62. package/lib/flow-factory.js.map +1 -1
  63. package/lib/index.d.ts +5 -8
  64. package/lib/index.js +17 -55
  65. package/lib/index.js.map +1 -1
  66. package/lib/types.d.ts +2 -2
  67. package/lib/types.js.map +1 -1
  68. package/lib/utils/hubtype-assistant-content.d.ts +1 -0
  69. package/lib/utils/hubtype-assistant-content.js +13 -0
  70. package/lib/utils/hubtype-assistant-content.js.map +1 -1
  71. package/package.json +4 -4
  72. package/src/action/ai-agent-from-user-input.ts +7 -0
  73. package/src/action/first-interaction.ts +6 -0
  74. package/src/action/get-contents.ts +17 -2
  75. package/src/action/index.tsx +7 -0
  76. package/src/api.ts +52 -15
  77. package/src/bot-context-manager.ts +153 -0
  78. package/src/constants.ts +2 -1
  79. package/src/content-fields/contact-reasons-conditional-matcher.ts +24 -0
  80. package/src/content-fields/flow-ai-agent-base.tsx +12 -1
  81. package/src/content-fields/flow-ai-agent-router.tsx +1 -0
  82. package/src/content-fields/flow-ai-agent.tsx +1 -0
  83. package/src/content-fields/flow-contact-reasons-conditional.tsx +90 -0
  84. package/src/content-fields/flow-do-nothing.tsx +29 -0
  85. package/src/content-fields/flow-handoff.tsx +54 -7
  86. package/src/content-fields/flow-whatsapp-request-contact-info.tsx +19 -1
  87. package/src/content-fields/hubtype-fields/contact-reasons-conditional.ts +18 -0
  88. package/src/content-fields/hubtype-fields/do-nothing.ts +7 -0
  89. package/src/content-fields/hubtype-fields/handoff.ts +17 -1
  90. package/src/content-fields/hubtype-fields/index.ts +1 -0
  91. package/src/content-fields/hubtype-fields/node-types.ts +2 -0
  92. package/src/content-fields/hubtype-fields/nodes.ts +4 -0
  93. package/src/content-fields/hubtype-fields/whatsapp-request-contact-info.ts +2 -0
  94. package/src/content-fields/index.ts +6 -0
  95. package/src/flow-factory.ts +11 -0
  96. package/src/index.ts +26 -88
  97. package/src/types.ts +1 -2
  98. package/src/utils/hubtype-assistant-content.ts +14 -0
@@ -1 +1 @@
1
- {"version":3,"file":"utils/hubtype-assistant-content.js","sources":["../../src/utils/hubtype-assistant-content.ts"],"sourcesContent":["import { WhatsappCTAUrlHeaderType } from '@botonic/react'\nimport type { FlowContent } from '../content-fields'\nimport type { FlowButton } from '../content-fields/flow-button'\nimport { FlowCarousel } from '../content-fields/flow-carousel'\nimport { FlowImage } from '../content-fields/flow-image'\nimport { FlowText } from '../content-fields/flow-text'\nimport { FlowVideo } from '../content-fields/flow-video'\nimport { FlowWhatsappCtaUrlButtonNode } from '../content-fields/flow-whatsapp-cta-url-button'\nimport { FlowWhatsappTemplate } from '../content-fields/flow-whatsapp-template'\nimport { HtButtonStyle } from '../content-fields/hubtype-fields'\nimport { FlowWhatsappButtonList } from '../content-fields/whatsapp-button-list/flow-whatsapp-button-list'\n\n// biome-ignore lint/complexity/noStaticOnlyClass: namespace-like adapter with private static helpers\nexport class HubtypeAssistantContent {\n static adapt(content: FlowContent): string {\n if (content instanceof FlowText) {\n return HubtypeAssistantContent.formatFlowTextContent(content)\n }\n if (content instanceof FlowCarousel) {\n return HubtypeAssistantContent.formatCarouselContent(content)\n }\n if (content instanceof FlowImage) {\n return content.src ? `[Image]\\n${content.src}` : '[Image]'\n }\n if (content instanceof FlowVideo) {\n return content.src ? `[Video]\\n${content.src}` : '[Video]'\n }\n if (content instanceof FlowWhatsappButtonList) {\n return HubtypeAssistantContent.formatWhatsappButtonListContent(content)\n }\n if (content instanceof FlowWhatsappCtaUrlButtonNode) {\n return HubtypeAssistantContent.formatWhatsappCtaContent(content)\n }\n if (content instanceof FlowWhatsappTemplate) {\n return HubtypeAssistantContent.formatWhatsappTemplateContent(content)\n }\n return ''\n }\n\n private static quickReplyLabel(button: FlowButton): string {\n const buttonText = button.text?.trim()\n if (buttonText) {\n return buttonText\n }\n\n return button.payload || 'Option'\n }\n\n private static persistentButtonLabel(button: FlowButton): string {\n return button.text?.trim() || 'Button'\n }\n\n private static carouselButtonLabel(button: FlowButton): string {\n return button.text?.trim() || 'Action'\n }\n\n private static formatFlowTextContent(text: FlowText): string {\n const body = text.text?.trim() ?? ''\n const buttons = text.buttons\n\n if (!body && buttons.length === 0) {\n return ''\n }\n\n if (buttons.length === 0) {\n return text.text\n }\n\n const isQuickReply = text.buttonStyle === HtButtonStyle.QUICK_REPLY\n const blockTitle = isQuickReply ? 'Quick replies:' : 'Buttons:'\n const lines = buttons.map((button, i) => {\n const label = isQuickReply\n ? HubtypeAssistantContent.quickReplyLabel(button)\n : HubtypeAssistantContent.persistentButtonLabel(button)\n return ` [${i + 1}] ${label}`\n })\n\n const block = [blockTitle, ...lines].join('\\n')\n if (!body) {\n return block\n }\n\n return `${body}\\n\\n${block}`\n }\n\n private static formatCarouselContent(carousel: FlowCarousel): string {\n const elements = carousel.elements\n const n = elements.length\n const mainText = (carousel.whatsappText ?? '').trim()\n\n if (n === 0) {\n return mainText\n }\n\n let header: string\n if (!mainText) {\n header = `Carousel displayed with ${n} items:`\n } else if (mainText.endsWith(':')) {\n header = mainText\n } else {\n header = `${mainText}\\nCarousel with ${n} items:`\n }\n\n const itemLines = elements.map((element, index) => {\n const title = element.title?.trim() || 'Item'\n const subtitle = element.subtitle?.trim()\n const subtitlePart = subtitle ? ` - ${subtitle}` : ''\n const btnLabels: string[] = []\n if (element.button) {\n btnLabels.push(\n HubtypeAssistantContent.carouselButtonLabel(element.button)\n )\n }\n const btnPart = btnLabels.length > 0 ? ` [${btnLabels.join(', ')}]` : ''\n\n return ` ${index + 1}. \"${title}\"${subtitlePart}${btnPart}`\n })\n\n return [header, ...itemLines].join('\\n')\n }\n\n private static formatWhatsappButtonListContent(\n list: FlowWhatsappButtonList\n ): string {\n const body = list.text?.trim() ?? ''\n const hasSections = list.sections.some(s => s.rows.length > 0)\n\n if (!body && !hasSections) {\n return '[WhatsApp Button List]'\n }\n\n const parts: string[] = []\n if (body) {\n parts.push(body)\n }\n\n if (hasSections) {\n const sectionBlocks = list.sections.map(section => {\n const rowLines = section.rows.map(row => {\n const rowTitle = row.title?.trim() ?? ''\n const rowDescription = row.description?.trim() ?? ''\n if (!rowTitle && !rowDescription) {\n return ' - '\n }\n if (!rowTitle) {\n return ` - ${rowDescription}`\n }\n if (!rowDescription) {\n return ` - ${rowTitle}`\n }\n return ` - ${rowTitle}: ${rowDescription}`\n })\n const rowsText = rowLines.join('\\n')\n\n const sectionTitle = section.title?.trim()\n if (sectionTitle) {\n return `${sectionTitle}:\\n${rowsText}`\n }\n\n return rowsText\n })\n parts.push(sectionBlocks.join('\\n'))\n }\n\n return parts.join('\\n\\n')\n }\n\n private static formatWhatsappCtaContent(\n cta: FlowWhatsappCtaUrlButtonNode\n ): string {\n const hasStructuredData =\n (cta.headerType === WhatsappCTAUrlHeaderType.Text &&\n !!cta.header?.trim()) ||\n cta.text?.trim() ||\n cta.footer?.trim() ||\n cta.displayText?.trim() ||\n cta.url\n\n if (!hasStructuredData) {\n return cta.text?.trim() || '[WhatsApp CTA Button]'\n }\n\n const lines: string[] = []\n if (\n cta.headerType === WhatsappCTAUrlHeaderType.Text &&\n cta.header?.trim()\n ) {\n lines.push(cta.header.trim())\n }\n if (cta.text?.trim()) {\n lines.push(cta.text.trim())\n }\n if (cta.footer?.trim()) {\n lines.push(cta.footer.trim())\n }\n const ctaLine = `[${cta.displayText || ''}]${cta.url ? ` (${cta.url})` : ''}`\n lines.push(ctaLine)\n\n return lines.join('\\n')\n }\n\n private static formatWhatsappTemplateContent(\n template: FlowWhatsappTemplate\n ): string {\n const { name, language } = template.htWhatsappTemplate\n return `WhatsApp Template: ${name} (${language})`\n }\n}\n"],"names":["WhatsappCTAUrlHeaderType","FlowCarousel","FlowImage","FlowText","FlowVideo","FlowWhatsappCtaUrlButtonNode","FlowWhatsappTemplate","HtButtonStyle","FlowWhatsappButtonList","HubtypeAssistantContent","content","button","buttonText","text","body","buttons","isQuickReply","blockTitle","lines","i","label","block","carousel","elements","n","mainText","header","itemLines","element","index","title","subtitle","subtitlePart","btnLabels","btnPart","list","hasSections","s","parts","sectionBlocks","section","rowLines","row","rowTitle","rowDescription","rowsText","sectionTitle","cta","hasStructuredData","ctaLine","template","name","language"],"mappings":"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"}
1
+ {"version":3,"file":"utils/hubtype-assistant-content.js","sources":["../../src/utils/hubtype-assistant-content.ts"],"sourcesContent":["import { WhatsappCTAUrlHeaderType } from '@botonic/react'\nimport type { FlowContent } from '../content-fields'\nimport type { FlowButton } from '../content-fields/flow-button'\nimport { FlowCarousel } from '../content-fields/flow-carousel'\nimport { FlowImage } from '../content-fields/flow-image'\nimport { FlowText } from '../content-fields/flow-text'\nimport { FlowVideo } from '../content-fields/flow-video'\nimport { FlowWhatsappCtaUrlButtonNode } from '../content-fields/flow-whatsapp-cta-url-button'\nimport { FlowWhatsappRequestContactInfoNode } from '../content-fields/flow-whatsapp-request-contact-info'\nimport { FlowWhatsappTemplate } from '../content-fields/flow-whatsapp-template'\nimport { HtButtonStyle } from '../content-fields/hubtype-fields'\nimport { FlowWhatsappButtonList } from '../content-fields/whatsapp-button-list/flow-whatsapp-button-list'\n\n// biome-ignore lint/complexity/noStaticOnlyClass: namespace-like adapter with private static helpers\nexport class HubtypeAssistantContent {\n static adapt(content: FlowContent): string {\n if (content instanceof FlowText) {\n return HubtypeAssistantContent.formatFlowTextContent(content)\n }\n if (content instanceof FlowCarousel) {\n return HubtypeAssistantContent.formatCarouselContent(content)\n }\n if (content instanceof FlowImage) {\n return content.src ? `[Image]\\n${content.src}` : '[Image]'\n }\n if (content instanceof FlowVideo) {\n return content.src ? `[Video]\\n${content.src}` : '[Video]'\n }\n if (content instanceof FlowWhatsappButtonList) {\n return HubtypeAssistantContent.formatWhatsappButtonListContent(content)\n }\n if (content instanceof FlowWhatsappCtaUrlButtonNode) {\n return HubtypeAssistantContent.formatWhatsappCtaContent(content)\n }\n if (content instanceof FlowWhatsappTemplate) {\n return HubtypeAssistantContent.formatWhatsappTemplateContent(content)\n }\n if (content instanceof FlowWhatsappRequestContactInfoNode) {\n return HubtypeAssistantContent.formatRequestContactInfoContent(content)\n }\n return ''\n }\n\n private static quickReplyLabel(button: FlowButton): string {\n const buttonText = button.text?.trim()\n if (buttonText) {\n return buttonText\n }\n\n return button.payload || 'Option'\n }\n\n private static persistentButtonLabel(button: FlowButton): string {\n return button.text?.trim() || 'Button'\n }\n\n private static carouselButtonLabel(button: FlowButton): string {\n return button.text?.trim() || 'Action'\n }\n\n private static formatFlowTextContent(text: FlowText): string {\n const body = text.text?.trim() ?? ''\n const buttons = text.buttons\n\n if (!body && buttons.length === 0) {\n return ''\n }\n\n if (buttons.length === 0) {\n return text.text\n }\n\n const isQuickReply = text.buttonStyle === HtButtonStyle.QUICK_REPLY\n const blockTitle = isQuickReply ? 'Quick replies:' : 'Buttons:'\n const lines = buttons.map((button, i) => {\n const label = isQuickReply\n ? HubtypeAssistantContent.quickReplyLabel(button)\n : HubtypeAssistantContent.persistentButtonLabel(button)\n return ` [${i + 1}] ${label}`\n })\n\n const block = [blockTitle, ...lines].join('\\n')\n if (!body) {\n return block\n }\n\n return `${body}\\n\\n${block}`\n }\n\n private static formatCarouselContent(carousel: FlowCarousel): string {\n const elements = carousel.elements\n const n = elements.length\n const mainText = (carousel.whatsappText ?? '').trim()\n\n if (n === 0) {\n return mainText\n }\n\n let header: string\n if (!mainText) {\n header = `Carousel displayed with ${n} items:`\n } else if (mainText.endsWith(':')) {\n header = mainText\n } else {\n header = `${mainText}\\nCarousel with ${n} items:`\n }\n\n const itemLines = elements.map((element, index) => {\n const title = element.title?.trim() || 'Item'\n const subtitle = element.subtitle?.trim()\n const subtitlePart = subtitle ? ` - ${subtitle}` : ''\n const btnLabels: string[] = []\n if (element.button) {\n btnLabels.push(\n HubtypeAssistantContent.carouselButtonLabel(element.button)\n )\n }\n const btnPart = btnLabels.length > 0 ? ` [${btnLabels.join(', ')}]` : ''\n\n return ` ${index + 1}. \"${title}\"${subtitlePart}${btnPart}`\n })\n\n return [header, ...itemLines].join('\\n')\n }\n\n private static formatWhatsappButtonListContent(\n list: FlowWhatsappButtonList\n ): string {\n const body = list.text?.trim() ?? ''\n const hasSections = list.sections.some(s => s.rows.length > 0)\n\n if (!body && !hasSections) {\n return '[WhatsApp Button List]'\n }\n\n const parts: string[] = []\n if (body) {\n parts.push(body)\n }\n\n if (hasSections) {\n const sectionBlocks = list.sections.map(section => {\n const rowLines = section.rows.map(row => {\n const rowTitle = row.title?.trim() ?? ''\n const rowDescription = row.description?.trim() ?? ''\n if (!rowTitle && !rowDescription) {\n return ' - '\n }\n if (!rowTitle) {\n return ` - ${rowDescription}`\n }\n if (!rowDescription) {\n return ` - ${rowTitle}`\n }\n return ` - ${rowTitle}: ${rowDescription}`\n })\n const rowsText = rowLines.join('\\n')\n\n const sectionTitle = section.title?.trim()\n if (sectionTitle) {\n return `${sectionTitle}:\\n${rowsText}`\n }\n\n return rowsText\n })\n parts.push(sectionBlocks.join('\\n'))\n }\n\n return parts.join('\\n\\n')\n }\n\n private static formatWhatsappCtaContent(\n cta: FlowWhatsappCtaUrlButtonNode\n ): string {\n const hasStructuredData =\n (cta.headerType === WhatsappCTAUrlHeaderType.Text &&\n !!cta.header?.trim()) ||\n cta.text?.trim() ||\n cta.footer?.trim() ||\n cta.displayText?.trim() ||\n cta.url\n\n if (!hasStructuredData) {\n return cta.text?.trim() || '[WhatsApp CTA Button]'\n }\n\n const lines: string[] = []\n if (\n cta.headerType === WhatsappCTAUrlHeaderType.Text &&\n cta.header?.trim()\n ) {\n lines.push(cta.header.trim())\n }\n if (cta.text?.trim()) {\n lines.push(cta.text.trim())\n }\n if (cta.footer?.trim()) {\n lines.push(cta.footer.trim())\n }\n const ctaLine = `[${cta.displayText || ''}]${cta.url ? ` (${cta.url})` : ''}`\n lines.push(ctaLine)\n\n return lines.join('\\n')\n }\n\n private static formatWhatsappTemplateContent(\n template: FlowWhatsappTemplate\n ): string {\n const { name, language } = template.htWhatsappTemplate\n return `WhatsApp Template: ${name} (${language})`\n }\n\n private static formatRequestContactInfoContent(\n requestContactInfo: FlowWhatsappRequestContactInfoNode\n ): string {\n const body = requestContactInfo.text?.trim() ?? ''\n if (!body) {\n return '[WhatsApp Request Contact Info]'\n }\n return requestContactInfo.text\n }\n}\n"],"names":["WhatsappCTAUrlHeaderType","FlowCarousel","FlowImage","FlowText","FlowVideo","FlowWhatsappCtaUrlButtonNode","FlowWhatsappRequestContactInfoNode","FlowWhatsappTemplate","HtButtonStyle","FlowWhatsappButtonList","HubtypeAssistantContent","content","button","buttonText","text","body","buttons","isQuickReply","blockTitle","lines","i","label","block","carousel","elements","n","mainText","header","itemLines","element","index","title","subtitle","subtitlePart","btnLabels","btnPart","list","hasSections","s","parts","sectionBlocks","section","rowLines","row","rowTitle","rowDescription","rowsText","sectionTitle","cta","hasStructuredData","ctaLine","template","name","language","requestContactInfo"],"mappings":"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"}
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@botonic/plugin-flow-builder",
4
- "version": "0.57.0-rslib-esm.1",
4
+ "version": "0.58.0-alpha-new-stack.0",
5
5
  "main": "./lib/index.js",
6
6
  "module": "./lib/index.js",
7
7
  "description": "Use Flow Builder to show your contents",
8
8
  "scripts": {
9
9
  "build": "rslib build --no-env",
10
10
  "build:watch": "rslib build --watch --no-env",
11
- "test": "../../node_modules/.bin/jest --coverage",
11
+ "test": "vitest run --coverage",
12
12
  "cloc": "../../scripts/qa/cloc-package.sh .",
13
13
  "prepublishOnly": "npm run build && npm run test && npm run lint",
14
14
  "lint": "biome check --write src/ tests/",
@@ -16,7 +16,7 @@
16
16
  "format": "biome format --write src/ tests/"
17
17
  },
18
18
  "dependencies": {
19
- "@botonic/react": "0.57.0-rslib-esm.1",
19
+ "@botonic/react": "0.58.0-alpha-new-stack.0",
20
20
  "axios": "^1.18.1",
21
21
  "uuid": "^10.0.0"
22
22
  },
@@ -34,7 +34,7 @@
34
34
  "README.md"
35
35
  ],
36
36
  "engines": {
37
- "node": ">=22.19.0",
37
+ "node": ">=22.23.2",
38
38
  "npm": ">=10.0.0"
39
39
  },
40
40
  "keywords": [
@@ -1,3 +1,4 @@
1
+ import { NluType } from '@botonic/core'
1
2
  import type { FlowContent } from '../content-fields/index'
2
3
  import { splitAiAgentContents } from '../utils/ai-agent'
3
4
  import type { FlowBuilderContext } from './context'
@@ -46,5 +47,11 @@ export async function getContentsByAiAgentFromUserInput({
46
47
  }
47
48
  }
48
49
 
50
+ request.input.nluResolution = {
51
+ type: NluType.AiAgent,
52
+ matchedValue: 'ai-agent-from-user-input',
53
+ payload: undefined,
54
+ }
55
+
49
56
  return contents
50
57
  }
@@ -1,3 +1,4 @@
1
+ import { NluType } from '@botonic/core'
1
2
  import type { FlowBuilderApi } from '../api'
2
3
  import { MAIN_FLOW_NAME } from '../constants'
3
4
  import {
@@ -51,6 +52,11 @@ export async function getContentsByFirstInteraction(
51
52
  if (request.input.nluResolution || inputHasTextOrTranscript(request.input)) {
52
53
  const contentsByUserInput = await getContentsByUserInput(context)
53
54
 
55
+ if (request.input.nluResolution?.type === NluType.AiAgent) {
56
+ const startContents = await flowBuilderPlugin.getStartContents()
57
+ return [...startContents, ...contentsByUserInput]
58
+ }
59
+
54
60
  return [...firstInteractionContents, ...contentsByUserInput]
55
61
  }
56
62
 
@@ -1,4 +1,9 @@
1
- import { type BotContext, INPUT } from '@botonic/core'
1
+ import {
2
+ type BotContext,
3
+ INPUT,
4
+ type Input,
5
+ WhatsappInputOrigin,
6
+ } from '@botonic/core'
2
7
  import { EMPTY_PAYLOAD } from '../constants'
3
8
  import type { FlowContent } from '../content-fields'
4
9
  import { inputHasTextOrTranscript } from '../utils/input'
@@ -35,7 +40,10 @@ export async function getContents(
35
40
  return await getContentsByFallback(context)
36
41
  }
37
42
 
38
- if (inputHasTextOrTranscript(botContext.input)) {
43
+ if (
44
+ inputHasTextOrTranscript(botContext.input) ||
45
+ isContactRequested(botContext.input)
46
+ ) {
39
47
  const aiAgentContents = await getContentsByAiAgentFromUserInput(context)
40
48
  if (aiAgentContents.length > 0) {
41
49
  return aiAgentContents
@@ -44,3 +52,10 @@ export async function getContents(
44
52
 
45
53
  return await getContentsByFallback(context)
46
54
  }
55
+
56
+ function isContactRequested(input: Input) {
57
+ return (
58
+ input.type === INPUT.CONTACT &&
59
+ input.origin === WhatsappInputOrigin.ContactRequest
60
+ )
61
+ }
@@ -11,8 +11,10 @@ import {
11
11
  FlowAiAgent,
12
12
  FlowAiAgentRouter,
13
13
  type FlowContent,
14
+ FlowWhatsappRequestContactInfoNode,
14
15
  } from '../content-fields'
15
16
  import { splitAiAgentContents } from '../utils/ai-agent'
17
+ import { getFlowBuilderPlugin } from '../utils/get-flow-builder-plugin'
16
18
  import { getFlowBuilderActionContext } from './context'
17
19
  import { getContentsByFirstInteraction } from './first-interaction'
18
20
  import { getContents } from './get-contents'
@@ -65,6 +67,11 @@ export class FlowBuilderAction extends React.Component<FlowBuilderActionProps> {
65
67
  }
66
68
 
67
69
  await content.processContent(botContext)
70
+
71
+ if (content instanceof FlowWhatsappRequestContactInfoNode) {
72
+ const flowBuilderPlugin = getFlowBuilderPlugin(botContext.plugins)
73
+ flowBuilderPlugin.cmsApi.setWhatsappRequestContactId(content.id)
74
+ }
68
75
  }
69
76
 
70
77
  return contents
package/src/api.ts CHANGED
@@ -1,7 +1,12 @@
1
- import type { PluginPreRequest } from '@botonic/core'
1
+ import type { BotContext } from '@botonic/core'
2
2
  import axios from 'axios'
3
3
 
4
- import { AI_AGENTS_FLOW_NAME, SEPARATOR, UUID_REGEXP } from './constants'
4
+ import {
5
+ AI_AGENTS_FLOW_NAME,
6
+ PUSH_FLOW_PAYLOAD,
7
+ SEPARATOR,
8
+ UUID_REGEXP,
9
+ } from './constants'
5
10
  import {
6
11
  type HtBotActionNode,
7
12
  type HtCaptureUserInputNode,
@@ -18,6 +23,7 @@ import {
18
23
  type HtRatingButton,
19
24
  type HtRatingNode,
20
25
  type HtSmartIntentNode,
26
+ type HtWhatsappRequestContactInfoNode,
21
27
  } from './content-fields/hubtype-fields'
22
28
  import {
23
29
  type FlowBuilderApiOptions,
@@ -30,7 +36,7 @@ export class FlowBuilderApi {
30
36
  url: string
31
37
  flowUrl: string
32
38
  flow: HtFlowBuilderData
33
- request: PluginPreRequest
39
+ botContext: BotContext
34
40
 
35
41
  private constructor() {}
36
42
 
@@ -38,12 +44,12 @@ export class FlowBuilderApi {
38
44
  const newApi = new FlowBuilderApi()
39
45
 
40
46
  newApi.url = options.url
41
- newApi.request = options.request
47
+ newApi.botContext = options.botContext
42
48
  // TODO: Refactor later to combine logic from `FlowBuilderApi.create`, `resolveFlowUrl` and `getAccessToken` to be in one place
43
49
  if (process.env.NODE_ENV === ProcessEnvNodeEnvs.DEVELOPMENT) {
44
50
  await newApi.updateSessionWithUserInfo(options.accessToken)
45
51
  }
46
- const updatedRequest = newApi.request
52
+ const updatedRequest = newApi.botContext
47
53
  newApi.flowUrl = options.flowUrl.replace(
48
54
  '{bot_id}',
49
55
  updatedRequest.session.bot.id
@@ -64,8 +70,8 @@ export class FlowBuilderApi {
64
70
  const response = await axios.get(url, {
65
71
  headers: { Authorization: `Bearer ${token}` },
66
72
  })
67
- this.request.session.organization_id = response.data.organization_id
68
- this.request.session.bot.id = response.data.bot_id
73
+ this.botContext.session.organization_id = response.data.organization_id
74
+ this.botContext.session.bot.id = response.data.bot_id
69
75
  }
70
76
 
71
77
  getNodeByFlowId(id: string): HtNodeWithContent {
@@ -259,23 +265,23 @@ export class FlowBuilderApi {
259
265
  }
260
266
 
261
267
  getCaptureUserInputId(): string | undefined {
262
- return this.request.session.capture_user_input?.node_id
268
+ return this.botContext.session.capture_user_input?.node_id
263
269
  }
264
270
 
265
271
  setCaptureUserInputId(id: string): void {
266
- if (this.request.session.capture_user_input) {
267
- this.request.session.capture_user_input.node_id = id
272
+ if (this.botContext.session.capture_user_input) {
273
+ this.botContext.session.capture_user_input.node_id = id
268
274
  }
269
- this.request.session.capture_user_input = { node_id: id }
275
+ this.botContext.session.capture_user_input = { node_id: id }
270
276
  }
271
277
 
272
278
  removeCaptureUserInputId(): void {
273
- this.request.session.capture_user_input = undefined
279
+ this.botContext.session.capture_user_input = undefined
274
280
  }
275
281
 
276
282
  setUserExtraDataVariable(key: string, value: string): void {
277
- if (this.request.session.user.extra_data) {
278
- this.request.session.user.extra_data[key] = value
283
+ if (this.botContext.session.user.extra_data) {
284
+ this.botContext.session.user.extra_data[key] = value
279
285
  }
280
286
  }
281
287
 
@@ -287,14 +293,45 @@ export class FlowBuilderApi {
287
293
  return this.getNodeById<HtCaptureUserInputNode>(captureUserInputId)
288
294
  }
289
295
 
296
+ getSessionWhatsappRequestContactId(): string | undefined {
297
+ return this.botContext.session.whatsapp_request_contact?.id
298
+ }
299
+
300
+ setWhatsappRequestContactId(id: string): void {
301
+ if (this.botContext.session.whatsapp_request_contact) {
302
+ this.botContext.session.whatsapp_request_contact.id = id
303
+ }
304
+ this.botContext.session.whatsapp_request_contact = { id }
305
+ }
306
+
307
+ removeWhatsappRequestContactId(): void {
308
+ this.botContext.session.whatsapp_request_contact = undefined
309
+ }
310
+
311
+ getWhatsappRequestContactInfoNode():
312
+ | HtWhatsappRequestContactInfoNode
313
+ | undefined {
314
+ const whatsappRequestContactId = this.getSessionWhatsappRequestContactId()
315
+ if (!whatsappRequestContactId) {
316
+ return undefined
317
+ }
318
+ return this.getNodeById<HtWhatsappRequestContactInfoNode>(
319
+ whatsappRequestContactId
320
+ )
321
+ }
322
+
290
323
  getResolvedLocale(): string {
291
324
  const flowLocales = this.flow.locales
292
325
  const defaultLocaleCode = this.flow.default_locale_code
293
326
 
294
327
  return new FlowLocale(
295
- this.request,
328
+ this.botContext,
296
329
  flowLocales,
297
330
  defaultLocaleCode
298
331
  ).resolve()
299
332
  }
333
+
334
+ isPushFlowPayload(payload?: string): boolean {
335
+ return payload?.startsWith(PUSH_FLOW_PAYLOAD) ?? false
336
+ }
300
337
  }
@@ -0,0 +1,153 @@
1
+ import {
2
+ type BotContext,
3
+ INPUT,
4
+ PROVIDER,
5
+ WhatsappInputOrigin,
6
+ } from '@botonic/core'
7
+
8
+ import type { FlowBuilderApi } from './api'
9
+ import {
10
+ EMPTY_PAYLOAD,
11
+ ON_CLOSE_HANDOFF_PAYLOAD,
12
+ SEPARATOR,
13
+ SOURCE_INFO_SEPARATOR,
14
+ } from './constants'
15
+ import { FlowHandoff } from './content-fields'
16
+ import {
17
+ type HtBotActionNode,
18
+ type HtHandoffNode,
19
+ HtNodeWithContentType,
20
+ } from './content-fields/hubtype-fields'
21
+ import { getNextPayloadByUserInput } from './user-input'
22
+ import type { SmartIntentsInferenceConfig } from './user-input/smart-intent'
23
+ import { inputHasTextOrTranscript } from './utils/input'
24
+
25
+ export class BotContextManager {
26
+ constructor(
27
+ private readonly cmsApi: FlowBuilderApi,
28
+ private readonly smartIntentsConfig: SmartIntentsInferenceConfig
29
+ ) {}
30
+
31
+ async prepareInputForRoutes(botContext: BotContext): Promise<void> {
32
+ // When AI Agent is executed in Whatsapp, button payloads come as referral and must be converted to text being processed by the agent.
33
+ this.convertWhatsappAiAgentEmptyPayloads(botContext)
34
+
35
+ // If the payload is a push-flow payload, set first interaction to false
36
+ this.updateFirstInteractionOnPushFlowPayload(botContext)
37
+
38
+ this.resolveWhatsappContactRequestPayload(botContext)
39
+ this.resolveOnCloseHandoffPayload(botContext)
40
+
41
+ await this.resolvePayloadFromUserTextInput(botContext)
42
+
43
+ this.cmsApi.removeCaptureUserInputId()
44
+ this.removePayloadSourceSuffix(botContext)
45
+ this.resolveBotActionPayload(botContext)
46
+ }
47
+
48
+ private async updateFirstInteractionOnPushFlowPayload(
49
+ botContext: BotContext
50
+ ): Promise<void> {
51
+ if (this.cmsApi.isPushFlowPayload(botContext.input.payload)) {
52
+ botContext.session.is_first_interaction = false
53
+ }
54
+ }
55
+
56
+ private async resolvePayloadFromUserTextInput(
57
+ botContext: BotContext
58
+ ): Promise<void> {
59
+ const checkUserTextInput =
60
+ inputHasTextOrTranscript(botContext.input) && !botContext.input.payload
61
+
62
+ if (!checkUserTextInput) {
63
+ return
64
+ }
65
+
66
+ const resolvedLocale = this.cmsApi.getResolvedLocale()
67
+ const nextPayload = await getNextPayloadByUserInput(
68
+ this.cmsApi,
69
+ resolvedLocale,
70
+ botContext,
71
+ this.smartIntentsConfig
72
+ )
73
+ botContext.input.payload = nextPayload
74
+ }
75
+
76
+ private resolveWhatsappContactRequestPayload(botContext: BotContext): void {
77
+ if (
78
+ botContext.input.type !== INPUT.CONTACT ||
79
+ botContext.input.origin !== WhatsappInputOrigin.ContactRequest
80
+ ) {
81
+ return
82
+ }
83
+
84
+ const whatsappRequestContactInfoNode =
85
+ this.cmsApi.getWhatsappRequestContactInfoNode()
86
+
87
+ if (whatsappRequestContactInfoNode) {
88
+ botContext.input.payload = this.cmsApi.getPayload(
89
+ whatsappRequestContactInfoNode.content.button.target
90
+ )
91
+ }
92
+
93
+ this.cmsApi.removeWhatsappRequestContactId()
94
+ }
95
+
96
+ private resolveOnCloseHandoffPayload(botContext: BotContext): void {
97
+ if (botContext.input.payload?.startsWith(ON_CLOSE_HANDOFF_PAYLOAD)) {
98
+ const handoffNodeId = botContext.input.payload.split(SEPARATOR)[1]
99
+ const handoffNode = this.cmsApi.getNodeById<HtHandoffNode>(handoffNodeId)
100
+ if (handoffNode && handoffNode.type === HtNodeWithContentType.HANDOFF) {
101
+ const flowHandoff = FlowHandoff.fromHubtypeCMS(
102
+ handoffNode,
103
+ this.cmsApi.getResolvedLocale(),
104
+ this.cmsApi
105
+ )
106
+ botContext.input.payload = flowHandoff.resolveOnClosePayload(botContext)
107
+ }
108
+ }
109
+ }
110
+
111
+ private convertWhatsappAiAgentEmptyPayloads(botContext: BotContext): void {
112
+ if (botContext.session.user.provider === PROVIDER.WHATSAPP) {
113
+ const shouldUseReferral =
114
+ botContext.input.referral &&
115
+ botContext.input.payload?.startsWith(EMPTY_PAYLOAD)
116
+
117
+ if (shouldUseReferral) {
118
+ botContext.input.type = INPUT.TEXT
119
+ botContext.input.data = botContext.input.referral
120
+ }
121
+ }
122
+ }
123
+
124
+ private removePayloadSourceSuffix(botContext: BotContext): void {
125
+ if (botContext.input.payload) {
126
+ botContext.input.payload = this.removeSourceSuffix(
127
+ botContext.input.payload
128
+ )
129
+ }
130
+ }
131
+
132
+ private resolveBotActionPayload(botContext: BotContext): void {
133
+ if (
134
+ botContext.input.payload &&
135
+ this.cmsApi.isBotAction(botContext.input.payload)
136
+ ) {
137
+ const cmsBotAction = this.cmsApi.getNodeById<HtBotActionNode>(
138
+ botContext.input.payload
139
+ )
140
+
141
+ botContext.input.payload =
142
+ this.cmsApi.createPayloadWithParams(cmsBotAction)
143
+
144
+ // Re-execute convertWhatsappAiAgentEmptyPayloads function to handle
145
+ // the case that a BotAction has a payload equals to EMPTY_PAYLOAD
146
+ this.convertWhatsappAiAgentEmptyPayloads(botContext)
147
+ }
148
+ }
149
+
150
+ private removeSourceSuffix(payload: string): string {
151
+ return payload.split(SOURCE_INFO_SEPARATOR)[0]
152
+ }
153
+ }
package/src/constants.ts CHANGED
@@ -2,7 +2,6 @@ export const FLOW_BUILDER_API_URL_PROD =
2
2
  process.env.HUBTYPE_API_URL || 'https://api.hubtype.com'
3
3
  export const SEPARATOR = '|'
4
4
  export const SOURCE_INFO_SEPARATOR = `${SEPARATOR}source_`
5
- export const EMPTY_PAYLOAD = 'fb-empty-payload'
6
5
  export const VARIABLE_PATTERN_GLOBAL = /{([^{}]+)}/g
7
6
  export const ACCESS_TOKEN_VARIABLE_KEY = '_access_token'
8
7
  export const REG_EXP_PATTERN = /^\/(.*)\/([gimyus]*)$/
@@ -10,6 +9,8 @@ export const UUID_REGEXP =
10
9
  /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
11
10
 
12
11
  /* ********** PAYLOADS ********** */
12
+ export const EMPTY_PAYLOAD = 'fb-empty-payload'
13
+ export const ON_CLOSE_HANDOFF_PAYLOAD = 'fb-on-close-handoff'
13
14
  export const AGENT_RATING_PAYLOAD = 'agent-rating'
14
15
  export const PUSH_FLOW_PAYLOAD = 'push-flow'
15
16
 
@@ -0,0 +1,24 @@
1
+ import type { HubtypeCaseContactReason } from '@botonic/core'
2
+
3
+ import type { HtContactReasonBranch } from './hubtype-fields/contact-reasons-conditional'
4
+
5
+ export function contactReasonMatchesBranch(
6
+ sessionContactReason: HubtypeCaseContactReason,
7
+ branch: HtContactReasonBranch
8
+ ): boolean {
9
+ return (
10
+ sessionContactReason.project_id === branch.project_id &&
11
+ sessionContactReason.name === branch.name
12
+ )
13
+ }
14
+
15
+ export function findMatchingContactReasonBranch(
16
+ sessionContactReasons: HubtypeCaseContactReason[],
17
+ contactReasonBranches: HtContactReasonBranch[]
18
+ ): HtContactReasonBranch | undefined {
19
+ return contactReasonBranches.find(branch =>
20
+ sessionContactReasons.some(sessionContactReason =>
21
+ contactReasonMatchesBranch(sessionContactReason, branch)
22
+ )
23
+ )
24
+ }
@@ -16,6 +16,7 @@ import { HubtypeAssistantContent } from '../utils/hubtype-assistant-content'
16
16
  import { ContentFieldsBase } from './content-fields-base'
17
17
  import { FlowCarousel } from './flow-carousel'
18
18
  import { FlowText } from './flow-text'
19
+ import { FlowWhatsappRequestContactInfoNode } from './flow-whatsapp-request-contact-info'
19
20
  import type { HtInputGuardrailRule, HtNodeWithContent } from './hubtype-fields'
20
21
  import type { FlowContent } from './index'
21
22
 
@@ -89,6 +90,16 @@ export abstract class FlowAiAgentBase extends ContentFieldsBase {
89
90
  )
90
91
  }
91
92
 
93
+ if (message.type === OutputMessageType.RequestContactInfo) {
94
+ this.jsxElements.push(
95
+ FlowWhatsappRequestContactInfoNode.fromAIAgent(
96
+ this.id,
97
+ message,
98
+ botContext
99
+ )
100
+ )
101
+ }
102
+
92
103
  if (message.type === FlowBuilderOutputMessageType.FlowBuilderContent) {
93
104
  const flowContents = await this.getFlowContentsByContentId(
94
105
  botContext,
@@ -107,7 +118,7 @@ export abstract class FlowAiAgentBase extends ContentFieldsBase {
107
118
  botContext: BotContext,
108
119
  previousContents?: FlowContent[]
109
120
  ): Promise<void> {
110
- if (this.messages.length === 0) {
121
+ if (!this.aiAgentResponse) {
111
122
  await this.resolveAIAgentResponse(botContext, previousContents)
112
123
  }
113
124
  if (this.jsxElements.length === 0) {
@@ -194,6 +194,7 @@ export class FlowAiAgentRouter extends FlowAiAgentBase {
194
194
  inputGuardrailsTriggered: [],
195
195
  outputGuardrailsTriggered:
196
196
  this.aiAgentResponse?.outputGuardrailsTriggered ?? [],
197
+ doNothing: this.aiAgentResponse?.doNothing ?? false,
197
198
  }
198
199
  const { action, ...specialistEventArgs } = specialistEvent
199
200
  await trackEvent(botContext, action, specialistEventArgs)
@@ -94,6 +94,7 @@ export class FlowAiAgent extends FlowAiAgentBase {
94
94
  this.aiAgentResponse?.inputGuardrailsTriggered ?? [],
95
95
  outputGuardrailsTriggered: [], //aiAgentResponse.outputGuardrailsTriggered,
96
96
  error: this.aiAgentResponse?.error ?? false,
97
+ doNothing: this.aiAgentResponse?.doNothing ?? false,
97
98
  }
98
99
  const { action, ...eventArgs } = event
99
100
 
@@ -0,0 +1,90 @@
1
+ import {
2
+ type BotContext,
3
+ EventAction,
4
+ type EventConditionalContactReasons,
5
+ } from '@botonic/core'
6
+
7
+ import {
8
+ getCommonFlowContentEventArgsForContentId,
9
+ trackEvent,
10
+ } from '../tracking'
11
+ import { findMatchingContactReasonBranch } from './contact-reasons-conditional-matcher'
12
+ import { ContentFieldsBase } from './content-fields-base'
13
+ import type { HtNodeLink } from './hubtype-fields/common'
14
+ import type {
15
+ HtContactReasonBranch,
16
+ HtContactReasonsNode,
17
+ } from './hubtype-fields/contact-reasons-conditional'
18
+
19
+ export class FlowContactReasonsConditional extends ContentFieldsBase {
20
+ public contactReasons: HtContactReasonBranch[] = []
21
+ public conditionalResult: string = ''
22
+ public defaultTarget: HtNodeLink
23
+
24
+ static fromHubtypeCMS(
25
+ component: HtContactReasonsNode,
26
+ botContext: BotContext
27
+ ): FlowContactReasonsConditional {
28
+ const newContactReasonsConditional = new FlowContactReasonsConditional(
29
+ component.id
30
+ )
31
+ newContactReasonsConditional.contactReasons =
32
+ component.content.contact_reasons
33
+ newContactReasonsConditional.defaultTarget =
34
+ component.content.default_target
35
+ newContactReasonsConditional.setFollowUp(botContext)
36
+ return newContactReasonsConditional
37
+ }
38
+
39
+ private setFollowUp(botContext: BotContext): void {
40
+ const sessionContactReasons =
41
+ botContext.session._hubtype_case_contact_reasons
42
+
43
+ const matchingBranch = sessionContactReasons
44
+ ? findMatchingContactReasonBranch(
45
+ sessionContactReasons,
46
+ this.contactReasons
47
+ )
48
+ : undefined
49
+
50
+ if (matchingBranch) {
51
+ this.conditionalResult = matchingBranch.name
52
+ this.followUp = matchingBranch.target
53
+ } else {
54
+ this.conditionalResult = 'default'
55
+ this.followUp = this.defaultTarget
56
+ }
57
+ }
58
+
59
+ async trackFlow(botContext: BotContext): Promise<void> {
60
+ const { flowThreadId, flowId, flowName, flowNodeId, flowNodeContentId } =
61
+ getCommonFlowContentEventArgsForContentId(botContext, this.id)
62
+ const eventContactReasonsConditional: EventConditionalContactReasons = {
63
+ action: EventAction.ConditionalContactReasons,
64
+ flowThreadId,
65
+ flowId,
66
+ flowName,
67
+ flowNodeId,
68
+ flowNodeContentId,
69
+ flowNodeIsMeaningful: false,
70
+ contactReasons:
71
+ botContext.session._hubtype_case_contact_reasons?.map(
72
+ contactReason => contactReason.name
73
+ ) || [],
74
+ result: this.conditionalResult,
75
+ }
76
+ const { action, ...eventArgs } = eventContactReasonsConditional
77
+ await trackEvent(botContext, action, eventArgs)
78
+ return
79
+ }
80
+
81
+ async processContent(botContext: BotContext): Promise<void> {
82
+ await this.filterContent(botContext, this)
83
+ await this.trackFlow(botContext)
84
+ return
85
+ }
86
+
87
+ toBotonic() {
88
+ return <></>
89
+ }
90
+ }