@botonic/plugin-flow-builder 0.56.1 → 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 +2 -2
  2. package/lib/cjs/api.js +17 -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 -67
  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 +2 -2
  38. package/lib/esm/api.js +17 -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 -67
  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 +19 -19
  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 -117
  87. package/src/types.ts +1 -2
package/src/api.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { PluginPreRequest } from '@botonic/core'
1
+ import type { BotContext } from '@botonic/core'
2
2
  import axios from 'axios'
3
3
 
4
4
  import {
@@ -36,7 +36,7 @@ export class FlowBuilderApi {
36
36
  url: string
37
37
  flowUrl: string
38
38
  flow: HtFlowBuilderData
39
- request: PluginPreRequest
39
+ botContext: BotContext
40
40
 
41
41
  private constructor() {}
42
42
 
@@ -44,12 +44,12 @@ export class FlowBuilderApi {
44
44
  const newApi = new FlowBuilderApi()
45
45
 
46
46
  newApi.url = options.url
47
- newApi.request = options.request
47
+ newApi.botContext = options.botContext
48
48
  // TODO: Refactor later to combine logic from `FlowBuilderApi.create`, `resolveFlowUrl` and `getAccessToken` to be in one place
49
49
  if (process.env.NODE_ENV === ProcessEnvNodeEnvs.DEVELOPMENT) {
50
50
  await newApi.updateSessionWithUserInfo(options.accessToken)
51
51
  }
52
- const updatedRequest = newApi.request
52
+ const updatedRequest = newApi.botContext
53
53
  newApi.flowUrl = options.flowUrl.replace(
54
54
  '{bot_id}',
55
55
  updatedRequest.session.bot.id
@@ -70,8 +70,8 @@ export class FlowBuilderApi {
70
70
  const response = await axios.get(url, {
71
71
  headers: { Authorization: `Bearer ${token}` },
72
72
  })
73
- this.request.session.organization_id = response.data.organization_id
74
- 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
75
75
  }
76
76
 
77
77
  getNodeByFlowId(id: string): HtNodeWithContent {
@@ -265,23 +265,23 @@ export class FlowBuilderApi {
265
265
  }
266
266
 
267
267
  getCaptureUserInputId(): string | undefined {
268
- return this.request.session.capture_user_input?.node_id
268
+ return this.botContext.session.capture_user_input?.node_id
269
269
  }
270
270
 
271
271
  setCaptureUserInputId(id: string): void {
272
- if (this.request.session.capture_user_input) {
273
- 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
274
274
  }
275
- this.request.session.capture_user_input = { node_id: id }
275
+ this.botContext.session.capture_user_input = { node_id: id }
276
276
  }
277
277
 
278
278
  removeCaptureUserInputId(): void {
279
- this.request.session.capture_user_input = undefined
279
+ this.botContext.session.capture_user_input = undefined
280
280
  }
281
281
 
282
282
  setUserExtraDataVariable(key: string, value: string): void {
283
- if (this.request.session.user.extra_data) {
284
- 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
285
285
  }
286
286
  }
287
287
 
@@ -294,18 +294,18 @@ export class FlowBuilderApi {
294
294
  }
295
295
 
296
296
  getSessionWhatsappRequestContactId(): string | undefined {
297
- return this.request.session.whatsapp_request_contact?.id
297
+ return this.botContext.session.whatsapp_request_contact?.id
298
298
  }
299
299
 
300
300
  setWhatsappRequestContactId(id: string): void {
301
- if (this.request.session.whatsapp_request_contact) {
302
- 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
303
303
  }
304
- this.request.session.whatsapp_request_contact = { id }
304
+ this.botContext.session.whatsapp_request_contact = { id }
305
305
  }
306
306
 
307
307
  removeWhatsappRequestContactId(): void {
308
- this.request.session.whatsapp_request_contact = undefined
308
+ this.botContext.session.whatsapp_request_contact = undefined
309
309
  }
310
310
 
311
311
  getWhatsappRequestContactInfoNode():
@@ -325,7 +325,7 @@ export class FlowBuilderApi {
325
325
  const defaultLocaleCode = this.flow.default_locale_code
326
326
 
327
327
  return new FlowLocale(
328
- this.request,
328
+ this.botContext,
329
329
  flowLocales,
330
330
  defaultLocaleCode
331
331
  ).resolve()
@@ -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
  }