@botonic/plugin-flow-builder 0.57.0-rslib-esm.2 → 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 (93) 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 +1 -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.js +1 -0
  37. package/lib/content-fields/flow-whatsapp-request-contact-info.js.map +1 -1
  38. package/lib/content-fields/hubtype-fields/contact-reasons-conditional.d.ts +16 -0
  39. package/lib/content-fields/hubtype-fields/contact-reasons-conditional.js +4 -0
  40. package/lib/content-fields/hubtype-fields/contact-reasons-conditional.js.map +1 -0
  41. package/lib/content-fields/hubtype-fields/do-nothing.d.ts +6 -0
  42. package/lib/content-fields/hubtype-fields/do-nothing.js +4 -0
  43. package/lib/content-fields/hubtype-fields/do-nothing.js.map +1 -0
  44. package/lib/content-fields/hubtype-fields/handoff.d.ts +11 -1
  45. package/lib/content-fields/hubtype-fields/handoff.js +7 -1
  46. package/lib/content-fields/hubtype-fields/handoff.js.map +1 -1
  47. package/lib/content-fields/hubtype-fields/index.d.ts +1 -0
  48. package/lib/content-fields/hubtype-fields/index.js +2 -0
  49. package/lib/content-fields/hubtype-fields/index.js.map +1 -1
  50. package/lib/content-fields/hubtype-fields/node-types.d.ts +3 -1
  51. package/lib/content-fields/hubtype-fields/node-types.js +2 -0
  52. package/lib/content-fields/hubtype-fields/node-types.js.map +1 -1
  53. package/lib/content-fields/hubtype-fields/nodes.d.ts +3 -1
  54. package/lib/content-fields/hubtype-fields/nodes.js.map +1 -1
  55. package/lib/content-fields/hubtype-fields/whatsapp-request-contact-info.d.ts +2 -0
  56. package/lib/content-fields/hubtype-fields/whatsapp-request-contact-info.js.map +1 -1
  57. package/lib/content-fields/index.d.ts +4 -2
  58. package/lib/content-fields/index.js +6 -0
  59. package/lib/content-fields/index.js.map +1 -1
  60. package/lib/flow-factory.js +5 -1
  61. package/lib/flow-factory.js.map +1 -1
  62. package/lib/index.d.ts +5 -8
  63. package/lib/index.js +17 -55
  64. package/lib/index.js.map +1 -1
  65. package/lib/types.d.ts +2 -2
  66. package/lib/types.js.map +1 -1
  67. package/package.json +3 -3
  68. package/src/action/ai-agent-from-user-input.ts +7 -0
  69. package/src/action/first-interaction.ts +6 -0
  70. package/src/action/get-contents.ts +17 -2
  71. package/src/action/index.tsx +7 -0
  72. package/src/api.ts +52 -15
  73. package/src/bot-context-manager.ts +153 -0
  74. package/src/constants.ts +2 -1
  75. package/src/content-fields/contact-reasons-conditional-matcher.ts +24 -0
  76. package/src/content-fields/flow-ai-agent-base.tsx +1 -1
  77. package/src/content-fields/flow-ai-agent-router.tsx +1 -0
  78. package/src/content-fields/flow-ai-agent.tsx +1 -0
  79. package/src/content-fields/flow-contact-reasons-conditional.tsx +90 -0
  80. package/src/content-fields/flow-do-nothing.tsx +29 -0
  81. package/src/content-fields/flow-handoff.tsx +54 -7
  82. package/src/content-fields/flow-whatsapp-request-contact-info.tsx +2 -0
  83. package/src/content-fields/hubtype-fields/contact-reasons-conditional.ts +18 -0
  84. package/src/content-fields/hubtype-fields/do-nothing.ts +7 -0
  85. package/src/content-fields/hubtype-fields/handoff.ts +17 -1
  86. package/src/content-fields/hubtype-fields/index.ts +1 -0
  87. package/src/content-fields/hubtype-fields/node-types.ts +2 -0
  88. package/src/content-fields/hubtype-fields/nodes.ts +4 -0
  89. package/src/content-fields/hubtype-fields/whatsapp-request-contact-info.ts +2 -0
  90. package/src/content-fields/index.ts +6 -0
  91. package/src/flow-factory.ts +11 -0
  92. package/src/index.ts +26 -88
  93. package/src/types.ts +1 -2
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
+ }
@@ -118,7 +118,7 @@ export abstract class FlowAiAgentBase extends ContentFieldsBase {
118
118
  botContext: BotContext,
119
119
  previousContents?: FlowContent[]
120
120
  ): Promise<void> {
121
- if (this.messages.length === 0) {
121
+ if (!this.aiAgentResponse) {
122
122
  await this.resolveAIAgentResponse(botContext, previousContents)
123
123
  }
124
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
+ }
@@ -0,0 +1,29 @@
1
+ import type { BotContext } from '@botonic/core'
2
+
3
+ import { trackOneContent } from '../tracking'
4
+ import { ContentFieldsBase } from './content-fields-base'
5
+ import type { HtDoNothingNode } from './hubtype-fields'
6
+
7
+ export class FlowDoNothing extends ContentFieldsBase {
8
+ static fromHubtypeCMS(cmsDoNothing: HtDoNothingNode): FlowDoNothing {
9
+ const newDoNothing = new FlowDoNothing(cmsDoNothing.id)
10
+ newDoNothing.code = cmsDoNothing.code
11
+ newDoNothing.followUp = cmsDoNothing.follow_up
12
+
13
+ return newDoNothing
14
+ }
15
+
16
+ async trackFlow(botContext: BotContext): Promise<void> {
17
+ await trackOneContent(botContext, this)
18
+ }
19
+
20
+ async processContent(botContext: BotContext): Promise<void> {
21
+ await this.filterContent(botContext, this)
22
+ await this.trackFlow(botContext)
23
+ return
24
+ }
25
+
26
+ toBotonic(): JSX.Element {
27
+ return <></>
28
+ }
29
+ }
@@ -6,17 +6,27 @@ import {
6
6
  isWebchat,
7
7
  } from '@botonic/core'
8
8
  import { WebchatSettings } from '@botonic/react'
9
-
10
9
  import type { FlowBuilderApi } from '../api'
10
+ import { ON_CLOSE_HANDOFF_PAYLOAD, SEPARATOR } from '../constants'
11
11
  import { getCommonFlowContentEventArgsForContentId } from '../tracking'
12
12
  import { ContentFieldsBase } from './content-fields-base'
13
- import type { HtHandoffNode, HtQueueLocale } from './hubtype-fields'
13
+ import {
14
+ DiscardType,
15
+ type HtHandoffNode,
16
+ type HtNodeLink,
17
+ type HtQueueLocale,
18
+ } from './hubtype-fields'
14
19
 
15
20
  export class FlowHandoff extends ContentFieldsBase {
16
21
  public queue?: HtQueueLocale
17
22
  public onFinishPayload?: string
18
23
  public handoffAutoAssign: boolean
19
24
  public hasQueuePositionChangedNotificationsEnabled: boolean
25
+ public hasDifferentiatedCloseTypes: boolean
26
+ public resolvedByAgent?: HtNodeLink
27
+ public discardedByAgent?: HtNodeLink
28
+ public discardedByUser?: HtNodeLink
29
+ public discardedBySystem?: HtNodeLink
20
30
  public isTestIntegration: boolean
21
31
 
22
32
  static fromHubtypeCMS(
@@ -30,14 +40,21 @@ export class FlowHandoff extends ContentFieldsBase {
30
40
  locale,
31
41
  cmsHandoff.content.queue
32
42
  )
43
+ newHandoff.hasDifferentiatedCloseTypes =
44
+ cmsHandoff.content.has_differentiated_close_types
45
+ newHandoff.resolvedByAgent = cmsHandoff.content.resolved_by_agent
46
+ newHandoff.discardedByAgent = cmsHandoff.content.discarded_by_agent
47
+ newHandoff.discardedByUser = cmsHandoff.content.discarded_by_user
48
+ newHandoff.discardedBySystem = cmsHandoff.content.discarded_by_system
49
+ newHandoff.handoffAutoAssign = cmsHandoff.content.has_auto_assign
50
+ newHandoff.hasQueuePositionChangedNotificationsEnabled =
51
+ cmsHandoff.content.has_queue_position_changed_notifications_enabled
52
+
33
53
  newHandoff.onFinishPayload = FlowHandoff.getOnFinishPayload(
34
54
  cmsHandoff,
35
55
  cmsApi
36
56
  )
37
- newHandoff.handoffAutoAssign = cmsHandoff.content.has_auto_assign
38
- newHandoff.hasQueuePositionChangedNotificationsEnabled =
39
- cmsHandoff.content.has_queue_position_changed_notifications_enabled
40
- newHandoff.followUp = cmsHandoff.follow_up
57
+ newHandoff.followUp = undefined
41
58
 
42
59
  return newHandoff
43
60
  }
@@ -46,13 +63,43 @@ export class FlowHandoff extends ContentFieldsBase {
46
63
  cmsHandoff: HtHandoffNode,
47
64
  cmsApi: FlowBuilderApi
48
65
  ): string | undefined {
49
- if (cmsHandoff.target?.id) {
66
+ if (cmsHandoff.content.has_differentiated_close_types) {
67
+ return `${ON_CLOSE_HANDOFF_PAYLOAD}${SEPARATOR}${cmsHandoff.id}`
68
+ } else if (cmsHandoff.target?.id) {
50
69
  return cmsApi.getPayload(cmsHandoff.target)
51
70
  }
52
71
 
53
72
  return undefined
54
73
  }
55
74
 
75
+ resolveOnClosePayload(botContext: BotContext): string {
76
+ const contactReasons =
77
+ botContext.session._hubtype_case_contact_reasons || []
78
+
79
+ if (contactReasons.some(reason => reason.name === DiscardType.ByAgent)) {
80
+ if (this.discardedByAgent?.id) {
81
+ return this.discardedByAgent.id
82
+ }
83
+ console.error('No discarded by agent target found')
84
+ }
85
+ if (contactReasons.some(reason => reason.name === DiscardType.ByUser)) {
86
+ if (this.discardedByUser?.id) {
87
+ return this.discardedByUser.id
88
+ }
89
+ console.error('No discarded by user target found')
90
+ }
91
+ if (contactReasons.some(reason => reason.name === DiscardType.BySystem)) {
92
+ if (this.discardedBySystem?.id) {
93
+ return this.discardedBySystem.id
94
+ }
95
+ console.error('No discarded by system target found')
96
+ }
97
+ if (this.resolvedByAgent?.id) {
98
+ return this.resolvedByAgent.id
99
+ }
100
+ throw new Error('No resolved by agent target found')
101
+ }
102
+
56
103
  async doHandoff(botContext: BotContext): Promise<void> {
57
104
  const handOffBuilder = new HandOffBuilder(botContext.session)
58
105
  handOffBuilder.withAutoAssignOnWaiting(this.handoffAutoAssign)
@@ -35,6 +35,8 @@ export class FlowWhatsappRequestContactInfoNode extends ContentFieldsBase {
35
35
  message: RequestContactInfoMessage,
36
36
  botContext: BotContext
37
37
  ): JSX.Element {
38
+ botContext.session.whatsapp_request_contact = undefined
39
+
38
40
  if (!isWhatsapp(botContext.session)) {
39
41
  return <Text key={id}>{message.content.text}</Text>
40
42
  }
@@ -0,0 +1,18 @@
1
+ import type { HtBaseNode, HtNodeLink } from './common'
2
+ import type { HtNodeWithContentType } from './node-types'
3
+
4
+ export interface HtContactReasonBranch {
5
+ id: string
6
+ name: string
7
+ project_id: string
8
+ project_name: string
9
+ target: HtNodeLink
10
+ }
11
+
12
+ export interface HtContactReasonsNode extends HtBaseNode {
13
+ type: HtNodeWithContentType.CONTACT_REASONS_CONDITION
14
+ content: {
15
+ contact_reasons: HtContactReasonBranch[]
16
+ default_target: HtNodeLink
17
+ }
18
+ }
@@ -0,0 +1,7 @@
1
+ import type { HtBaseNode } from './common'
2
+ import type { HtNodeWithContentType } from './node-types'
3
+
4
+ export interface HtDoNothingNode extends HtBaseNode {
5
+ id: string
6
+ type: HtNodeWithContentType.DO_NOTHING
7
+ }
@@ -1,6 +1,17 @@
1
- import type { HtBaseNode, HtPayloadLocale, HtQueueLocale } from './common'
1
+ import type {
2
+ HtBaseNode,
3
+ HtNodeLink,
4
+ HtPayloadLocale,
5
+ HtQueueLocale,
6
+ } from './common'
2
7
  import type { HtNodeWithContentType } from './node-types'
3
8
 
9
+ export enum DiscardType {
10
+ ByAgent = 'discarded',
11
+ ByUser = 'discarded_by_user',
12
+ BySystem = 'discarded_by_system',
13
+ }
14
+
4
15
  export interface HtHandoffNode extends HtBaseNode {
5
16
  type: HtNodeWithContentType.HANDOFF
6
17
  content: {
@@ -8,5 +19,10 @@ export interface HtHandoffNode extends HtBaseNode {
8
19
  payload: HtPayloadLocale[]
9
20
  has_auto_assign: boolean
10
21
  has_queue_position_changed_notifications_enabled: boolean
22
+ has_differentiated_close_types: boolean
23
+ resolved_by_agent?: HtNodeLink
24
+ discarded_by_agent?: HtNodeLink
25
+ discarded_by_user?: HtNodeLink
26
+ discarded_by_system?: HtNodeLink
11
27
  }
12
28
  }
@@ -6,6 +6,7 @@ export * from './capture-user-input'
6
6
  export * from './carousel'
7
7
  export * from './common'
8
8
  export * from './custom-conditional-v2'
9
+ export * from './do-nothing'
9
10
  export * from './fallback'
10
11
  export * from './function'
11
12
  export * from './go-to-flow'
@@ -20,6 +20,8 @@ export enum HtNodeWithContentType {
20
20
  GO_TO_FLOW = 'go-to-flow',
21
21
  CAPTURE_USER_INPUT = 'capture-user-input',
22
22
  CUSTOM_CONDITION = 'custom-condition',
23
+ DO_NOTHING = 'do-nothing',
24
+ CONTACT_REASONS_CONDITION = 'contact-reasons-condition',
23
25
  }
24
26
 
25
27
  export enum HtNodeWithoutContentType {
@@ -3,7 +3,9 @@ import type { HtAiAgentRouterNode } from './ai-agent-router'
3
3
  import type { HtBotActionNode } from './bot-action'
4
4
  import type { HtCaptureUserInputNode } from './capture-user-input'
5
5
  import type { HtCarouselNode } from './carousel'
6
+ import type { HtContactReasonsNode } from './contact-reasons-conditional'
6
7
  import type { HtCustomConditionalV2Node } from './custom-conditional-v2'
8
+ import type { HtDoNothingNode } from './do-nothing'
7
9
  // import { HtChannelConditionalNode } from './channel-conditional'
8
10
  // import { HtCountryConditionalNode } from './country-conditional'
9
11
  // import { HtCustomConditionalNode } from './custom-conditional'
@@ -48,6 +50,8 @@ export type HtNodeWithContent =
48
50
  | HtWhatsappTemplateNode
49
51
  | HtCaptureUserInputNode
50
52
  | HtCustomConditionalV2Node
53
+ | HtDoNothingNode
54
+ | HtContactReasonsNode
51
55
  // | HtChannelConditionalNode
52
56
  // | HtCountryConditionalNode
53
57
  // | HtCustomConditionalNode