@botonic/plugin-flow-builder 0.56.0 → 0.57.0-alpha.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 (87) hide show
  1. package/lib/cjs/api.d.ts +3 -2
  2. package/lib/cjs/api.js +20 -17
  3. package/lib/cjs/api.js.map +1 -1
  4. package/lib/cjs/bot-context-manager.d.ts +17 -0
  5. package/lib/cjs/bot-context-manager.js +93 -0
  6. package/lib/cjs/bot-context-manager.js.map +1 -0
  7. package/lib/cjs/constants.d.ts +2 -1
  8. package/lib/cjs/constants.js +3 -2
  9. package/lib/cjs/constants.js.map +1 -1
  10. package/lib/cjs/content-fields/contact-reasons-conditional-matcher.d.ts +4 -0
  11. package/lib/cjs/content-fields/contact-reasons-conditional-matcher.js +12 -0
  12. package/lib/cjs/content-fields/contact-reasons-conditional-matcher.js.map +1 -0
  13. package/lib/cjs/content-fields/flow-contact-reasons-conditional.d.ts +13 -0
  14. package/lib/cjs/content-fields/flow-contact-reasons-conditional.js +45 -0
  15. package/lib/cjs/content-fields/flow-contact-reasons-conditional.js.map +1 -0
  16. package/lib/cjs/content-fields/flow-handoff.d.ts +7 -1
  17. package/lib/cjs/content-fields/flow-handoff.js +38 -3
  18. package/lib/cjs/content-fields/flow-handoff.js.map +1 -1
  19. package/lib/cjs/content-fields/hubtype-fields/contact-reasons-conditional.d.ts +16 -0
  20. package/lib/cjs/content-fields/hubtype-fields/contact-reasons-conditional.js +3 -0
  21. package/lib/cjs/content-fields/hubtype-fields/contact-reasons-conditional.js.map +1 -0
  22. package/lib/cjs/content-fields/hubtype-fields/handoff.d.ts +6 -1
  23. package/lib/cjs/content-fields/hubtype-fields/node-types.d.ts +2 -1
  24. package/lib/cjs/content-fields/hubtype-fields/node-types.js +1 -0
  25. package/lib/cjs/content-fields/hubtype-fields/node-types.js.map +1 -1
  26. package/lib/cjs/content-fields/hubtype-fields/nodes.d.ts +2 -1
  27. package/lib/cjs/content-fields/index.d.ts +3 -2
  28. package/lib/cjs/content-fields/index.js +3 -1
  29. package/lib/cjs/content-fields/index.js.map +1 -1
  30. package/lib/cjs/flow-factory.js +2 -0
  31. package/lib/cjs/flow-factory.js.map +1 -1
  32. package/lib/cjs/index.d.ts +5 -9
  33. package/lib/cjs/index.js +16 -64
  34. package/lib/cjs/index.js.map +1 -1
  35. package/lib/cjs/types.d.ts +2 -2
  36. package/lib/cjs/types.js.map +1 -1
  37. package/lib/esm/api.d.ts +3 -2
  38. package/lib/esm/api.js +20 -17
  39. package/lib/esm/api.js.map +1 -1
  40. package/lib/esm/bot-context-manager.d.ts +17 -0
  41. package/lib/esm/bot-context-manager.js +93 -0
  42. package/lib/esm/bot-context-manager.js.map +1 -0
  43. package/lib/esm/constants.d.ts +2 -1
  44. package/lib/esm/constants.js +3 -2
  45. package/lib/esm/constants.js.map +1 -1
  46. package/lib/esm/content-fields/contact-reasons-conditional-matcher.d.ts +4 -0
  47. package/lib/esm/content-fields/contact-reasons-conditional-matcher.js +12 -0
  48. package/lib/esm/content-fields/contact-reasons-conditional-matcher.js.map +1 -0
  49. package/lib/esm/content-fields/flow-contact-reasons-conditional.d.ts +13 -0
  50. package/lib/esm/content-fields/flow-contact-reasons-conditional.js +45 -0
  51. package/lib/esm/content-fields/flow-contact-reasons-conditional.js.map +1 -0
  52. package/lib/esm/content-fields/flow-handoff.d.ts +7 -1
  53. package/lib/esm/content-fields/flow-handoff.js +38 -3
  54. package/lib/esm/content-fields/flow-handoff.js.map +1 -1
  55. package/lib/esm/content-fields/hubtype-fields/contact-reasons-conditional.d.ts +16 -0
  56. package/lib/esm/content-fields/hubtype-fields/contact-reasons-conditional.js +3 -0
  57. package/lib/esm/content-fields/hubtype-fields/contact-reasons-conditional.js.map +1 -0
  58. package/lib/esm/content-fields/hubtype-fields/handoff.d.ts +6 -1
  59. package/lib/esm/content-fields/hubtype-fields/node-types.d.ts +2 -1
  60. package/lib/esm/content-fields/hubtype-fields/node-types.js +1 -0
  61. package/lib/esm/content-fields/hubtype-fields/node-types.js.map +1 -1
  62. package/lib/esm/content-fields/hubtype-fields/nodes.d.ts +2 -1
  63. package/lib/esm/content-fields/index.d.ts +3 -2
  64. package/lib/esm/content-fields/index.js +3 -1
  65. package/lib/esm/content-fields/index.js.map +1 -1
  66. package/lib/esm/flow-factory.js +2 -0
  67. package/lib/esm/flow-factory.js.map +1 -1
  68. package/lib/esm/index.d.ts +5 -9
  69. package/lib/esm/index.js +16 -64
  70. package/lib/esm/index.js.map +1 -1
  71. package/lib/esm/types.d.ts +2 -2
  72. package/lib/esm/types.js.map +1 -1
  73. package/package.json +2 -2
  74. package/src/api.ts +29 -20
  75. package/src/bot-context-manager.ts +153 -0
  76. package/src/constants.ts +2 -1
  77. package/src/content-fields/contact-reasons-conditional-matcher.ts +24 -0
  78. package/src/content-fields/flow-contact-reasons-conditional.tsx +60 -0
  79. package/src/content-fields/flow-handoff.tsx +49 -7
  80. package/src/content-fields/hubtype-fields/contact-reasons-conditional.ts +18 -0
  81. package/src/content-fields/hubtype-fields/handoff.ts +11 -1
  82. package/src/content-fields/hubtype-fields/node-types.ts +1 -0
  83. package/src/content-fields/hubtype-fields/nodes.ts +2 -0
  84. package/src/content-fields/index.ts +3 -0
  85. package/src/flow-factory.ts +7 -0
  86. package/src/index.ts +26 -113
  87. 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,
@@ -31,7 +36,7 @@ export class FlowBuilderApi {
31
36
  url: string
32
37
  flowUrl: string
33
38
  flow: HtFlowBuilderData
34
- request: PluginPreRequest
39
+ botContext: BotContext
35
40
 
36
41
  private constructor() {}
37
42
 
@@ -39,12 +44,12 @@ export class FlowBuilderApi {
39
44
  const newApi = new FlowBuilderApi()
40
45
 
41
46
  newApi.url = options.url
42
- newApi.request = options.request
47
+ newApi.botContext = options.botContext
43
48
  // TODO: Refactor later to combine logic from `FlowBuilderApi.create`, `resolveFlowUrl` and `getAccessToken` to be in one place
44
49
  if (process.env.NODE_ENV === ProcessEnvNodeEnvs.DEVELOPMENT) {
45
50
  await newApi.updateSessionWithUserInfo(options.accessToken)
46
51
  }
47
- const updatedRequest = newApi.request
52
+ const updatedRequest = newApi.botContext
48
53
  newApi.flowUrl = options.flowUrl.replace(
49
54
  '{bot_id}',
50
55
  updatedRequest.session.bot.id
@@ -65,8 +70,8 @@ export class FlowBuilderApi {
65
70
  const response = await axios.get(url, {
66
71
  headers: { Authorization: `Bearer ${token}` },
67
72
  })
68
- this.request.session.organization_id = response.data.organization_id
69
- 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
70
75
  }
71
76
 
72
77
  getNodeByFlowId(id: string): HtNodeWithContent {
@@ -260,23 +265,23 @@ export class FlowBuilderApi {
260
265
  }
261
266
 
262
267
  getCaptureUserInputId(): string | undefined {
263
- return this.request.session.capture_user_input?.node_id
268
+ return this.botContext.session.capture_user_input?.node_id
264
269
  }
265
270
 
266
271
  setCaptureUserInputId(id: string): void {
267
- if (this.request.session.capture_user_input) {
268
- 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
269
274
  }
270
- this.request.session.capture_user_input = { node_id: id }
275
+ this.botContext.session.capture_user_input = { node_id: id }
271
276
  }
272
277
 
273
278
  removeCaptureUserInputId(): void {
274
- this.request.session.capture_user_input = undefined
279
+ this.botContext.session.capture_user_input = undefined
275
280
  }
276
281
 
277
282
  setUserExtraDataVariable(key: string, value: string): void {
278
- if (this.request.session.user.extra_data) {
279
- 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
280
285
  }
281
286
  }
282
287
 
@@ -289,18 +294,18 @@ export class FlowBuilderApi {
289
294
  }
290
295
 
291
296
  getSessionWhatsappRequestContactId(): string | undefined {
292
- return this.request.session.whatsapp_request_contact?.id
297
+ return this.botContext.session.whatsapp_request_contact?.id
293
298
  }
294
299
 
295
300
  setWhatsappRequestContactId(id: string): void {
296
- if (this.request.session.whatsapp_request_contact) {
297
- this.request.session.whatsapp_request_contact.id = id
301
+ if (this.botContext.session.whatsapp_request_contact) {
302
+ this.botContext.session.whatsapp_request_contact.id = id
298
303
  }
299
- this.request.session.whatsapp_request_contact = { id }
304
+ this.botContext.session.whatsapp_request_contact = { id }
300
305
  }
301
306
 
302
307
  removeWhatsappRequestContactId(): void {
303
- this.request.session.whatsapp_request_contact = undefined
308
+ this.botContext.session.whatsapp_request_contact = undefined
304
309
  }
305
310
 
306
311
  getWhatsappRequestContactInfoNode():
@@ -320,9 +325,13 @@ export class FlowBuilderApi {
320
325
  const defaultLocaleCode = this.flow.default_locale_code
321
326
 
322
327
  return new FlowLocale(
323
- this.request,
328
+ this.botContext,
324
329
  flowLocales,
325
330
  defaultLocaleCode
326
331
  ).resolve()
327
332
  }
333
+
334
+ isPushFlowPayload(payload?: string): boolean {
335
+ return payload?.startsWith(PUSH_FLOW_PAYLOAD) ?? false
336
+ }
328
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 updateBeforeRoutes(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
+ }
@@ -0,0 +1,60 @@
1
+ import type { BotContext, HubtypeCaseContactReason } from '@botonic/core'
2
+ import { findMatchingContactReasonBranch } from './contact-reasons-conditional-matcher'
3
+ import { ContentFieldsBase } from './content-fields-base'
4
+ import type { HtNodeLink } from './hubtype-fields/common'
5
+ import type {
6
+ HtContactReasonBranch,
7
+ HtContactReasonsNode,
8
+ } from './hubtype-fields/contact-reasons-conditional'
9
+
10
+ export class FlowContactReasonsConditional extends ContentFieldsBase {
11
+ public contactReasons: HtContactReasonBranch[] = []
12
+ public defaultTarget?: HtNodeLink
13
+
14
+ static fromHubtypeCMS(
15
+ component: HtContactReasonsNode,
16
+ botContext: BotContext
17
+ ): FlowContactReasonsConditional {
18
+ const newContactReasonsConditional = new FlowContactReasonsConditional(
19
+ component.id
20
+ )
21
+ newContactReasonsConditional.contactReasons =
22
+ component.content.contact_reasons
23
+ newContactReasonsConditional.defaultTarget =
24
+ component.content.default_target
25
+ newContactReasonsConditional.setFollowUp(botContext)
26
+ return newContactReasonsConditional
27
+ }
28
+
29
+ private setFollowUp(botContext: BotContext): void {
30
+ const sessionContactReasons: HubtypeCaseContactReason[] | undefined =
31
+ botContext.session._hubtype_case_contact_reasons
32
+
33
+ if (sessionContactReasons) {
34
+ const matchingBranch = findMatchingContactReasonBranch(
35
+ sessionContactReasons,
36
+ this.contactReasons
37
+ )
38
+ if (matchingBranch?.target) {
39
+ this.followUp = matchingBranch.target
40
+ }
41
+ }
42
+
43
+ this.followUp ??= this.defaultTarget
44
+ }
45
+
46
+ async trackFlow(_botContext: BotContext): Promise<void> {
47
+ // await trackFlow(botContext)
48
+ return
49
+ }
50
+
51
+ async processContent(_botContext: BotContext): Promise<void> {
52
+ // await this.filterContent(botContext, this)
53
+ // await this.trackFlow(botContext)
54
+ return
55
+ }
56
+
57
+ toBotonic() {
58
+ return <></>
59
+ }
60
+ }
@@ -6,17 +6,22 @@ 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 type { HtHandoffNode, HtNodeLink, HtQueueLocale } from './hubtype-fields'
14
14
 
15
15
  export class FlowHandoff extends ContentFieldsBase {
16
16
  public queue?: HtQueueLocale
17
17
  public onFinishPayload?: string
18
18
  public handoffAutoAssign: boolean
19
19
  public hasQueuePositionChangedNotificationsEnabled: boolean
20
+ public hasDifferentiatedCloseTypes: boolean
21
+ public resolvedByAgent?: HtNodeLink
22
+ public discardedByAgent?: HtNodeLink
23
+ public discardedByUser?: HtNodeLink
24
+ public discardedBySystem?: HtNodeLink
20
25
  public isTestIntegration: boolean
21
26
 
22
27
  static fromHubtypeCMS(
@@ -30,14 +35,21 @@ export class FlowHandoff extends ContentFieldsBase {
30
35
  locale,
31
36
  cmsHandoff.content.queue
32
37
  )
38
+ newHandoff.hasDifferentiatedCloseTypes =
39
+ cmsHandoff.content.has_differentiated_close_types
40
+ newHandoff.resolvedByAgent = cmsHandoff.content.resolved_by_agent
41
+ newHandoff.discardedByAgent = cmsHandoff.content.discarded_by_agent
42
+ newHandoff.discardedByUser = cmsHandoff.content.discarded_by_user
43
+ newHandoff.discardedBySystem = cmsHandoff.content.discarded_by_system
44
+ newHandoff.handoffAutoAssign = cmsHandoff.content.has_auto_assign
45
+ newHandoff.hasQueuePositionChangedNotificationsEnabled =
46
+ cmsHandoff.content.has_queue_position_changed_notifications_enabled
47
+
33
48
  newHandoff.onFinishPayload = FlowHandoff.getOnFinishPayload(
34
49
  cmsHandoff,
35
50
  cmsApi
36
51
  )
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
52
+ newHandoff.followUp = undefined
41
53
 
42
54
  return newHandoff
43
55
  }
@@ -46,13 +58,43 @@ export class FlowHandoff extends ContentFieldsBase {
46
58
  cmsHandoff: HtHandoffNode,
47
59
  cmsApi: FlowBuilderApi
48
60
  ): string | undefined {
49
- if (cmsHandoff.target?.id) {
61
+ if (cmsHandoff.content.has_differentiated_close_types) {
62
+ return `${ON_CLOSE_HANDOFF_PAYLOAD}${SEPARATOR}${cmsHandoff.id}`
63
+ } else if (cmsHandoff.target?.id) {
50
64
  return cmsApi.getPayload(cmsHandoff.target)
51
65
  }
52
66
 
53
67
  return undefined
54
68
  }
55
69
 
70
+ resolveOnClosePayload(botContext: BotContext): string {
71
+ const contactReasons =
72
+ botContext.session._hubtype_case_contact_reasons || []
73
+
74
+ if (contactReasons.some(reason => reason.name === 'discarded')) {
75
+ if (this.discardedByAgent?.id) {
76
+ return this.discardedByAgent.id
77
+ }
78
+ console.error('No discarded by agent target found')
79
+ }
80
+ if (contactReasons.some(reason => reason.name === 'discarded_by_user')) {
81
+ if (this.discardedByUser?.id) {
82
+ return this.discardedByUser.id
83
+ }
84
+ console.error('No discarded by user target found')
85
+ }
86
+ if (contactReasons.some(reason => reason.name === 'discarded_by_system')) {
87
+ if (this.discardedBySystem?.id) {
88
+ return this.discardedBySystem.id
89
+ }
90
+ console.error('No discarded by system target found')
91
+ }
92
+ if (this.resolvedByAgent?.id) {
93
+ return this.resolvedByAgent.id
94
+ }
95
+ throw new Error('No resolved by agent target found')
96
+ }
97
+
56
98
  async doHandoff(botContext: BotContext): Promise<void> {
57
99
  const handOffBuilder = new HandOffBuilder(botContext.session)
58
100
  handOffBuilder.withAutoAssignOnWaiting(this.handoffAutoAssign)
@@ -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
+ }
@@ -1,4 +1,9 @@
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
 
4
9
  export interface HtHandoffNode extends HtBaseNode {
@@ -8,5 +13,10 @@ export interface HtHandoffNode extends HtBaseNode {
8
13
  payload: HtPayloadLocale[]
9
14
  has_auto_assign: boolean
10
15
  has_queue_position_changed_notifications_enabled: boolean
16
+ has_differentiated_close_types: boolean
17
+ resolved_by_agent?: HtNodeLink
18
+ discarded_by_agent?: HtNodeLink
19
+ discarded_by_user?: HtNodeLink
20
+ discarded_by_system?: HtNodeLink
11
21
  }
12
22
  }
@@ -21,6 +21,7 @@ export enum HtNodeWithContentType {
21
21
  CAPTURE_USER_INPUT = 'capture-user-input',
22
22
  CUSTOM_CONDITION = 'custom-condition',
23
23
  DO_NOTHING = 'do-nothing',
24
+ CONTACT_REASONS_CONDITION = 'contact-reasons-condition',
24
25
  }
25
26
 
26
27
  export enum HtNodeWithoutContentType {
@@ -3,6 +3,7 @@ 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'
7
8
  import type { HtDoNothingNode } from './do-nothing'
8
9
  // import { HtChannelConditionalNode } from './channel-conditional'
@@ -50,6 +51,7 @@ export type HtNodeWithContent =
50
51
  | HtCaptureUserInputNode
51
52
  | HtCustomConditionalV2Node
52
53
  | HtDoNothingNode
54
+ | HtContactReasonsNode
53
55
  // | HtChannelConditionalNode
54
56
  // | HtCountryConditionalNode
55
57
  // | HtCustomConditionalNode
@@ -5,6 +5,7 @@ import { FlowBotAction } from './flow-bot-action'
5
5
  import { FlowCaptureUserInput } from './flow-capture-user-input'
6
6
  import { FlowCarousel } from './flow-carousel'
7
7
  import { FlowChannelConditional } from './flow-channel-conditional'
8
+ import { FlowContactReasonsConditional } from './flow-contact-reasons-conditional'
8
9
  import { FlowCountryConditional } from './flow-country-conditional'
9
10
  import { FlowCustomConditional } from './flow-custom-conditional'
10
11
  import { FlowDoNothing } from './flow-do-nothing'
@@ -34,6 +35,7 @@ export {
34
35
  FlowCaptureUserInput,
35
36
  FlowCarousel,
36
37
  FlowChannelConditional,
38
+ FlowContactReasonsConditional,
37
39
  FlowCountryConditional,
38
40
  FlowCustomConditional,
39
41
  FlowCustomConditionalV2,
@@ -75,3 +77,4 @@ export type FlowContent =
75
77
  | FlowAiAgentRouter
76
78
  | FlowCustomConditionalV2
77
79
  | FlowDoNothing
80
+ | FlowContactReasonsConditional
@@ -7,6 +7,7 @@ import {
7
7
  FlowBotAction,
8
8
  FlowCarousel,
9
9
  FlowChannelConditional,
10
+ FlowContactReasonsConditional,
10
11
  type FlowContent,
11
12
  FlowCountryConditional,
12
13
  FlowCustomConditional,
@@ -122,6 +123,12 @@ export class FlowFactory {
122
123
  case HtNodeWithContentType.DO_NOTHING:
123
124
  return FlowDoNothing.fromHubtypeCMS(hubtypeContent)
124
125
 
126
+ case HtNodeWithContentType.CONTACT_REASONS_CONDITION:
127
+ return FlowContactReasonsConditional.fromHubtypeCMS(
128
+ hubtypeContent,
129
+ this.botContext
130
+ )
131
+
125
132
  default:
126
133
  return undefined
127
134
  }