@botonic/plugin-flow-builder 0.26.1 → 0.27.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 (99) hide show
  1. package/lib/cjs/action/index.js +39 -19
  2. package/lib/cjs/action/index.js.map +1 -1
  3. package/lib/cjs/action/knowledge-bases.js +14 -4
  4. package/lib/cjs/action/knowledge-bases.js.map +1 -1
  5. package/lib/cjs/api.d.ts +4 -6
  6. package/lib/cjs/api.js +7 -33
  7. package/lib/cjs/api.js.map +1 -1
  8. package/lib/cjs/constants.d.ts +1 -0
  9. package/lib/cjs/constants.js +2 -1
  10. package/lib/cjs/constants.js.map +1 -1
  11. package/lib/cjs/content-fields/flow-button.d.ts +0 -1
  12. package/lib/cjs/content-fields/flow-button.js +0 -10
  13. package/lib/cjs/content-fields/flow-button.js.map +1 -1
  14. package/lib/cjs/content-fields/flow-handoff.js +7 -19
  15. package/lib/cjs/content-fields/flow-handoff.js.map +1 -1
  16. package/lib/cjs/content-fields/hubtype-fields/button.d.ts +1 -2
  17. package/lib/cjs/content-fields/index.d.ts +1 -0
  18. package/lib/cjs/content-fields/index.js +3 -1
  19. package/lib/cjs/content-fields/index.js.map +1 -1
  20. package/lib/cjs/functions/conditional-queue-status.d.ts +7 -1
  21. package/lib/cjs/functions/conditional-queue-status.js +34 -12
  22. package/lib/cjs/functions/conditional-queue-status.js.map +1 -1
  23. package/lib/cjs/index.d.ts +7 -1
  24. package/lib/cjs/index.js +29 -6
  25. package/lib/cjs/index.js.map +1 -1
  26. package/lib/cjs/tracking.d.ts +18 -14
  27. package/lib/cjs/tracking.js +27 -20
  28. package/lib/cjs/tracking.js.map +1 -1
  29. package/lib/cjs/types.d.ts +14 -1
  30. package/lib/cjs/types.js.map +1 -1
  31. package/lib/cjs/user-input/index.d.ts +2 -1
  32. package/lib/cjs/user-input/index.js +9 -3
  33. package/lib/cjs/user-input/index.js.map +1 -1
  34. package/lib/cjs/user-input/intent.js +12 -8
  35. package/lib/cjs/user-input/intent.js.map +1 -1
  36. package/lib/cjs/user-input/keyword.d.ts +21 -1
  37. package/lib/cjs/user-input/keyword.js +68 -13
  38. package/lib/cjs/user-input/keyword.js.map +1 -1
  39. package/lib/cjs/user-input/smart-intent.d.ts +18 -1
  40. package/lib/cjs/user-input/smart-intent.js +46 -29
  41. package/lib/cjs/user-input/smart-intent.js.map +1 -1
  42. package/lib/esm/action/index.js +39 -19
  43. package/lib/esm/action/index.js.map +1 -1
  44. package/lib/esm/action/knowledge-bases.js +15 -5
  45. package/lib/esm/action/knowledge-bases.js.map +1 -1
  46. package/lib/esm/api.d.ts +4 -6
  47. package/lib/esm/api.js +8 -34
  48. package/lib/esm/api.js.map +1 -1
  49. package/lib/esm/constants.d.ts +1 -0
  50. package/lib/esm/constants.js +1 -0
  51. package/lib/esm/constants.js.map +1 -1
  52. package/lib/esm/content-fields/flow-button.d.ts +0 -1
  53. package/lib/esm/content-fields/flow-button.js +0 -10
  54. package/lib/esm/content-fields/flow-button.js.map +1 -1
  55. package/lib/esm/content-fields/flow-handoff.js +7 -19
  56. package/lib/esm/content-fields/flow-handoff.js.map +1 -1
  57. package/lib/esm/content-fields/hubtype-fields/button.d.ts +1 -2
  58. package/lib/esm/content-fields/index.d.ts +1 -0
  59. package/lib/esm/content-fields/index.js +1 -0
  60. package/lib/esm/content-fields/index.js.map +1 -1
  61. package/lib/esm/functions/conditional-queue-status.d.ts +7 -1
  62. package/lib/esm/functions/conditional-queue-status.js +32 -11
  63. package/lib/esm/functions/conditional-queue-status.js.map +1 -1
  64. package/lib/esm/index.d.ts +7 -1
  65. package/lib/esm/index.js +30 -7
  66. package/lib/esm/index.js.map +1 -1
  67. package/lib/esm/tracking.d.ts +18 -14
  68. package/lib/esm/tracking.js +25 -19
  69. package/lib/esm/tracking.js.map +1 -1
  70. package/lib/esm/types.d.ts +14 -1
  71. package/lib/esm/types.js.map +1 -1
  72. package/lib/esm/user-input/index.d.ts +2 -1
  73. package/lib/esm/user-input/index.js +11 -5
  74. package/lib/esm/user-input/index.js.map +1 -1
  75. package/lib/esm/user-input/intent.js +13 -9
  76. package/lib/esm/user-input/intent.js.map +1 -1
  77. package/lib/esm/user-input/keyword.d.ts +21 -1
  78. package/lib/esm/user-input/keyword.js +67 -12
  79. package/lib/esm/user-input/keyword.js.map +1 -1
  80. package/lib/esm/user-input/smart-intent.d.ts +18 -1
  81. package/lib/esm/user-input/smart-intent.js +44 -27
  82. package/lib/esm/user-input/smart-intent.js.map +1 -1
  83. package/package.json +3 -3
  84. package/src/action/index.tsx +58 -24
  85. package/src/action/knowledge-bases.ts +16 -5
  86. package/src/api.ts +12 -55
  87. package/src/constants.ts +1 -0
  88. package/src/content-fields/flow-button.tsx +0 -10
  89. package/src/content-fields/flow-handoff.tsx +8 -28
  90. package/src/content-fields/hubtype-fields/button.ts +1 -7
  91. package/src/content-fields/index.ts +1 -0
  92. package/src/functions/conditional-queue-status.ts +36 -13
  93. package/src/index.ts +48 -7
  94. package/src/tracking.ts +25 -14
  95. package/src/types.ts +13 -1
  96. package/src/user-input/index.ts +13 -7
  97. package/src/user-input/intent.ts +17 -10
  98. package/src/user-input/keyword.ts +93 -14
  99. package/src/user-input/smart-intent.ts +57 -30
@@ -5,8 +5,9 @@ import { FlowBuilderApi } from '../api'
5
5
  import { FlowContent, FlowHandoff } from '../content-fields'
6
6
  import { HtNodeWithContent } from '../content-fields/hubtype-fields'
7
7
  import { getFlowBuilderPlugin } from '../helpers'
8
+ import BotonicPluginFlowBuilder from '../index'
9
+ import { EventAction, getNodeEventArgs, trackEvent } from '../tracking'
8
10
  import { createNodeFromKnowledgeBase } from './knowledge-bases'
9
- import { EventName, trackEvent } from '../tracking'
10
11
 
11
12
  export type FlowBuilderActionProps = {
12
13
  contents: FlowContent[]
@@ -18,15 +19,7 @@ export class FlowBuilderAction extends React.Component<FlowBuilderActionProps> {
18
19
  static async botonicInit(
19
20
  request: ActionRequest
20
21
  ): Promise<FlowBuilderActionProps> {
21
- const flowBuilderPlugin = getFlowBuilderPlugin(request.plugins)
22
- const locale = flowBuilderPlugin.getLocale(request.session)
23
-
24
- const targetNode = await getTargetNode(flowBuilderPlugin.cmsApi, request)
25
-
26
- const contents = await flowBuilderPlugin.getContentsByNode(
27
- targetNode,
28
- locale
29
- )
22
+ const contents = await getContents(request)
30
23
 
31
24
  const handoffContent = contents.find(
32
25
  content => content instanceof FlowHandoff
@@ -55,26 +48,64 @@ export class FlowBuilderMultichannelAction extends FlowBuilderAction {
55
48
  }
56
49
  }
57
50
 
58
- async function getTargetNode(cmsApi: FlowBuilderApi, request: ActionRequest) {
51
+ async function getContents(request: ActionRequest): Promise<FlowContent[]> {
52
+ const flowBuilderPlugin = getFlowBuilderPlugin(request.plugins)
53
+ const cmsApi = flowBuilderPlugin.cmsApi
54
+ const locale = flowBuilderPlugin.getLocale(request.session)
55
+ const resolvedLocale = flowBuilderPlugin.cmsApi.getResolvedLocale(locale)
56
+ const context = {
57
+ cmsApi,
58
+ flowBuilderPlugin,
59
+ request,
60
+ resolvedLocale,
61
+ }
62
+
59
63
  if (request.session.is_first_interaction) {
60
- const startNode = cmsApi.getStartNode()
61
- await trackEvent(request, EventName.botStart)
62
- return startNode
64
+ return await flowBuilderPlugin.getStartContents(resolvedLocale)
65
+ }
66
+
67
+ if (request.input.payload) {
68
+ return await getContentsByPayload(context)
63
69
  }
64
- const contentId = request.input.payload
65
70
 
66
- const targetNode = contentId
67
- ? cmsApi.getNodeById<HtNodeWithContent>(contentId)
71
+ return await getContentsByFallback(context)
72
+ }
73
+
74
+ interface FlowBuilderContext {
75
+ cmsApi: FlowBuilderApi
76
+ flowBuilderPlugin: BotonicPluginFlowBuilder
77
+ request: ActionRequest
78
+ resolvedLocale: string
79
+ }
80
+
81
+ async function getContentsByPayload({
82
+ cmsApi,
83
+ flowBuilderPlugin,
84
+ request,
85
+ resolvedLocale,
86
+ }: FlowBuilderContext): Promise<FlowContent[]> {
87
+ const targetNode = request.input.payload
88
+ ? cmsApi.getNodeById<HtNodeWithContent>(request.input.payload)
68
89
  : undefined
69
90
 
70
91
  if (targetNode) {
71
- const eventArgs = {
72
- faq_name: targetNode.code,
73
- }
74
- await trackEvent(request, EventName.botFaq, eventArgs)
75
- return targetNode
92
+ const eventArgs = getNodeEventArgs(request, targetNode)
93
+ await trackEvent(request, EventAction.flowNode, eventArgs)
94
+ return await flowBuilderPlugin.getContentsByNode(targetNode, resolvedLocale)
76
95
  }
77
- return await getFallbackNode(cmsApi, request)
96
+ return []
97
+ }
98
+
99
+ async function getContentsByFallback({
100
+ cmsApi,
101
+ flowBuilderPlugin,
102
+ request,
103
+ resolvedLocale,
104
+ }: FlowBuilderContext): Promise<FlowContent[]> {
105
+ const fallbackNode = await getFallbackNode(cmsApi, request)
106
+ const eventArgs = getNodeEventArgs(request, fallbackNode)
107
+ await trackEvent(request, EventAction.flowNode, eventArgs)
108
+ return await flowBuilderPlugin.getContentsByNode(fallbackNode, resolvedLocale)
78
109
  }
79
110
 
80
111
  async function getFallbackNode(cmsApi: FlowBuilderApi, request: ActionRequest) {
@@ -95,6 +126,9 @@ async function getFallbackNode(cmsApi: FlowBuilderApi, request: ActionRequest) {
95
126
  const fallbackNode = cmsApi.getFallbackNode(isFirstFallbackOption)
96
127
  request.session.user.extra_data.isFirstFallbackOption = !isFirstFallbackOption
97
128
 
98
- await trackEvent(request, EventName.fallback)
129
+ trackEvent(request, EventAction.fallback, {
130
+ fallbackAttempt: isFirstFallbackOption ? 1 : 2,
131
+ })
132
+
99
133
  return fallbackNode
100
134
  }
@@ -9,7 +9,7 @@ import {
9
9
  HtTextNode,
10
10
  } from '../content-fields/hubtype-fields'
11
11
  import { getFlowBuilderPlugin } from '../helpers'
12
- import { EventName, trackEvent } from '../tracking'
12
+ import { EventAction, trackEvent } from '../tracking'
13
13
 
14
14
  export async function createNodeFromKnowledgeBase(
15
15
  cmsApi: FlowBuilderApi,
@@ -28,11 +28,11 @@ export async function createNodeFromKnowledgeBase(
28
28
  const knowledgeBaseResponse =
29
29
  await flowBuilderPlugin.getKnowledgeBaseResponse(request)
30
30
  if (knowledgeBaseResponse.hasKnowledge) {
31
- await trackEvent(request, EventName.botAiKnowledgeBase, {
32
- answer: knowledgeBaseResponse.answer,
33
- knowledge_source_ids: knowledgeBaseResponse.sources.map(
31
+ await trackEvent(request, EventAction.knowledgebase, {
32
+ knowledgebaseSourcesIds: knowledgeBaseResponse.sources.map(
34
33
  source => source.knowledgeSourceId
35
34
  ),
35
+ knowledgebaseChunksIds: [],
36
36
  })
37
37
 
38
38
  const knowledgeBaseNode: HtTextNode = {
@@ -58,8 +58,19 @@ export async function createNodeFromKnowledgeBase(
58
58
  }
59
59
  return knowledgeBaseNode
60
60
  }
61
+ await trackEvent(request, EventAction.knowledgebase, {
62
+ knowledgebaseFailReason:
63
+ 'The knowledge base does not have enough knowledge',
64
+ knowledgeSourceIds: [],
65
+ knowledgebaseChunksIds: [],
66
+ })
61
67
  } catch (e) {
62
- console.log('Hubtype knowledge base api error: ', { e })
68
+ console.error('Hubtype knowledge base api error: ', { e })
69
+ await trackEvent(request, EventAction.knowledgebase, {
70
+ knowledgebaseFailReason: 'Knowledge base api error',
71
+ knowledgeSourceIds: [],
72
+ knowledgebaseChunksIds: [],
73
+ })
63
74
  return undefined
64
75
  }
65
76
  }
package/src/api.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Input, PluginPreRequest } from '@botonic/core'
2
2
  import axios from 'axios'
3
3
 
4
- import { REG_EXP_PATTERN, SEPARATOR } from './constants'
4
+ import { BOT_ACTION_PAYLOAD_PREFIX, SEPARATOR } from './constants'
5
5
  import {
6
6
  HtBotActionNode,
7
7
  HtFallbackNode,
@@ -150,57 +150,10 @@ export class FlowBuilderApi {
150
150
  return predictedConfidence >= nodeConfidence
151
151
  }
152
152
 
153
- getNodeByKeyword(
154
- userInput: string,
155
- locale: string
156
- ): HtKeywordNode | undefined {
157
- try {
158
- const keywordNodes = this.flow.nodes.filter(
159
- node => node.type === HtNodeWithContentType.KEYWORD
160
- ) as HtKeywordNode[]
161
- const matchedKeywordNodes = keywordNodes.filter(node =>
162
- this.matchKeywords(node, userInput, locale)
163
- )
164
- if (matchedKeywordNodes.length > 0 && matchedKeywordNodes[0].target) {
165
- return matchedKeywordNodes[0]
166
- }
167
- } catch (error) {
168
- console.error(`Error getting node by keyword '${userInput}': `, error)
169
- }
170
-
171
- return undefined
172
- }
173
-
174
- private matchKeywords(
175
- node: HtKeywordNode,
176
- input: string,
177
- locale: string
178
- ): boolean {
179
- const result = node.content.keywords.find(
180
- keywords =>
181
- keywords.locale === locale &&
182
- this.inputMatchesAnyKeyword(input, keywords.values)
183
- )
184
- return Boolean(result)
185
- }
186
-
187
- private inputMatchesAnyKeyword(input: string, keywords: string[]): boolean {
188
- return keywords.some(keyword => {
189
- const regExpMatchArray = keyword.match(REG_EXP_PATTERN)
190
- if (regExpMatchArray) {
191
- return this.resolveKeywordAsRegExp(regExpMatchArray, input)
192
- }
193
- return input.includes(keyword)
194
- })
195
- }
196
-
197
- private resolveKeywordAsRegExp(
198
- regExpMatchArray: RegExpMatchArray,
199
- input: string
200
- ) {
201
- const [, pattern, flags] = regExpMatchArray
202
- const keywordAsRegExp = new RegExp(pattern, flags)
203
- return input.match(keywordAsRegExp)
153
+ getKeywordNodes(): HtKeywordNode[] {
154
+ return this.flow.nodes.filter(
155
+ node => node.type === HtNodeWithContentType.KEYWORD
156
+ ) as HtKeywordNode[]
204
157
  }
205
158
 
206
159
  getPayload(target?: HtNodeLink): string | undefined {
@@ -209,14 +162,13 @@ export class FlowBuilderApi {
209
162
  }
210
163
 
211
164
  if (target.type === HtNodeWithoutContentType.BOT_ACTION) {
212
- const botActionNode = this.getNodeById<HtBotActionNode>(target.id)
213
- return this.createPayloadWithParams(botActionNode)
165
+ return `${BOT_ACTION_PAYLOAD_PREFIX}${target.id}`
214
166
  }
215
167
 
216
168
  return target.id
217
169
  }
218
170
 
219
- private createPayloadWithParams(botActionNode: HtBotActionNode): string {
171
+ createPayloadWithParams(botActionNode: HtBotActionNode): string {
220
172
  const payloadId = botActionNode.content.payload_id
221
173
  const payloadNode = this.getNodeById<HtPayloadNode>(payloadId)
222
174
  const customParams = JSON.parse(
@@ -247,6 +199,11 @@ export class FlowBuilderApi {
247
199
  }
248
200
  }
249
201
 
202
+ getFlowName(flowId: string): string {
203
+ const flow = this.flow.flows.find(flow => flow.id === flowId)
204
+ return flow ? flow.name : ''
205
+ }
206
+
250
207
  getResolvedLocale(locale: string): string {
251
208
  if (this.flow.locales.find(flowLocale => flowLocale === locale)) {
252
209
  return locale
package/src/constants.ts CHANGED
@@ -2,6 +2,7 @@ export const FLOW_BUILDER_API_URL_PROD =
2
2
  'https://api.ent0.flowbuilder.prod.hubtype.com'
3
3
  export const SEPARATOR = '|'
4
4
  export const SOURCE_INFO_SEPARATOR = `${SEPARATOR}source_`
5
+ export const BOT_ACTION_PAYLOAD_PREFIX = `ba${SEPARATOR}`
5
6
  export const VARIABLE_PATTERN = /{([^}]+)}/g
6
7
  export const ACCESS_TOKEN_VARIABLE_KEY = '_access_token'
7
8
  export const REG_EXP_PATTERN = /^\/(.*)\/([gimyus]*)$/
@@ -21,7 +21,6 @@ export class FlowButton extends ContentFieldsBase {
21
21
  locale: string,
22
22
  cmsApi: FlowBuilderApi
23
23
  ): FlowButton {
24
- const payloadId = this.getPayloadId(cmsButton, locale)
25
24
  const urlId = this.getUrlId(cmsButton, locale)
26
25
 
27
26
  const newButton = new FlowButton(cmsButton.id)
@@ -30,11 +29,6 @@ export class FlowButton extends ContentFieldsBase {
30
29
  newButton.payload = cmsApi.getPayload(cmsButton.target)
31
30
  }
32
31
 
33
- // OLD PAYLOAD
34
- if (cmsButton.payload && payloadId) {
35
- const payloadNode = cmsApi.getNodeById<HtPayloadNode>(payloadId)
36
- newButton.payload = payloadNode.content.payload
37
- }
38
32
  if (cmsButton.url && urlId) {
39
33
  const urlNode = cmsApi.getNodeById<HtUrlNode>(urlId)
40
34
  newButton.url = urlNode.content.url
@@ -43,10 +37,6 @@ export class FlowButton extends ContentFieldsBase {
43
37
  return newButton
44
38
  }
45
39
 
46
- static getPayloadId(cmsButton: HtButton, locale: string): string | undefined {
47
- return cmsButton.payload.find(payload => payload.locale === locale)?.id
48
- }
49
-
50
40
  static getUrlId(cmsButton: HtButton, locale: string): string | undefined {
51
41
  return cmsButton.url.find(url => url.locale === locale)?.id
52
42
  }
@@ -2,11 +2,9 @@ import { HandOffBuilder } from '@botonic/core'
2
2
  import { ActionRequest, WebchatSettings } from '@botonic/react'
3
3
  import React from 'react'
4
4
 
5
- import { EventName, trackEvent } from '../tracking'
6
5
  import { FlowBuilderApi } from '../api'
7
- import { getQueueAvailability } from '../functions/conditional-queue-status'
8
6
  import { ContentFieldsBase } from './content-fields-base'
9
- import { HtHandoffNode, HtPayloadNode, HtQueueLocale } from './hubtype-fields'
7
+ import { HtHandoffNode, HtQueueLocale } from './hubtype-fields'
10
8
 
11
9
  export class FlowHandoff extends ContentFieldsBase {
12
10
  public code: string
@@ -22,11 +20,7 @@ export class FlowHandoff extends ContentFieldsBase {
22
20
  const newHandoff = new FlowHandoff(cmsHandoff.id)
23
21
  newHandoff.code = cmsHandoff.code
24
22
  newHandoff.queue = this.getQueueByLocale(locale, cmsHandoff.content.queue)
25
- newHandoff.onFinishPayload = this.getOnFinishPayload(
26
- cmsHandoff,
27
- locale,
28
- cmsApi
29
- )
23
+ newHandoff.onFinishPayload = this.getOnFinishPayload(cmsHandoff, cmsApi)
30
24
  newHandoff.handoffAutoAssign = cmsHandoff.content.has_auto_assign
31
25
 
32
26
  return newHandoff
@@ -34,43 +28,29 @@ export class FlowHandoff extends ContentFieldsBase {
34
28
 
35
29
  private static getOnFinishPayload(
36
30
  cmsHandoff: HtHandoffNode,
37
- locale: string,
38
31
  cmsApi: FlowBuilderApi
39
32
  ): string | undefined {
40
33
  if (cmsHandoff.target?.id) {
41
34
  return cmsApi.getPayload(cmsHandoff.target)
42
35
  }
43
36
 
44
- // OLD PAYLOAD
45
- const payloadId = cmsHandoff.content.payload.find(
46
- payload => payload.locale === locale
47
- )?.id
48
- if (payloadId) {
49
- return cmsApi.getNodeById<HtPayloadNode>(payloadId).content.payload
50
- }
51
-
52
37
  return undefined
53
38
  }
54
39
 
55
40
  async doHandoff(request: ActionRequest): Promise<void> {
56
- // @ts-ignore
57
41
  const handOffBuilder = new HandOffBuilder(request.session)
58
42
  handOffBuilder.withAutoAssignOnWaiting(this.handoffAutoAssign)
43
+
59
44
  if (this.onFinishPayload) {
60
45
  handOffBuilder.withOnFinishPayload(this.onFinishPayload)
61
46
  }
62
- if (this.queue) {
63
- const availabilityData = await getQueueAvailability(this.queue.id)
64
- const eventArgs = {
65
- queue_open: availabilityData.open,
66
- queue_id: this.queue.id,
67
- available_agents: availabilityData.available_agents > 0,
68
- threshold_reached:
69
- availabilityData.availability_threshold_waiting_cases > 0,
70
- }
71
- await trackEvent(request, EventName.handoffSuccess, eventArgs)
72
47
 
48
+ if (this.queue) {
73
49
  handOffBuilder.withQueue(this.queue.id)
50
+ handOffBuilder.withBotEvent({
51
+ language: request.session.user.extra_data.language,
52
+ country: request.session.user.extra_data.country,
53
+ })
74
54
  await handOffBuilder.handOff()
75
55
  }
76
56
  }
@@ -1,15 +1,9 @@
1
- import {
2
- HtNodeLink,
3
- HtPayloadLocale,
4
- HtTextLocale,
5
- HtUrlLocale,
6
- } from './common'
1
+ import { HtNodeLink, HtTextLocale, HtUrlLocale } from './common'
7
2
 
8
3
  export interface HtButton {
9
4
  id: string
10
5
  text: HtTextLocale[]
11
6
  url: HtUrlLocale[]
12
- payload: HtPayloadLocale[]
13
7
  target?: HtNodeLink
14
8
  hidden: string[]
15
9
  }
@@ -11,6 +11,7 @@ export { FlowButton } from './flow-button'
11
11
  export { FlowElement } from './flow-element'
12
12
  export { FlowCarousel, FlowImage, FlowText, FlowVideo, FlowWhatsappButtonList }
13
13
  export { FlowHandoff } from './flow-handoff'
14
+ export { FlowWhatsappCtaUrlButtonNode } from './flow-whatsapp-cta-url-button'
14
15
 
15
16
  export type FlowContent =
16
17
  | FlowCarousel
@@ -1,6 +1,8 @@
1
1
  import { ActionRequest } from '@botonic/react'
2
2
  import axios from 'axios'
3
3
 
4
+ // import { EventAction, trackEvent } from '../tracking'
5
+
4
6
  const HUBTYPE_API_URL = process.env.HUBTYPE_API_URL || 'https://api.hubtype.com'
5
7
 
6
8
  type ConditionalQueueStatusArgs = {
@@ -17,17 +19,48 @@ enum QueueStatusResult {
17
19
  }
18
20
 
19
21
  export async function conditionalQueueStatus({
22
+ // request,
20
23
  queue_id,
24
+ // queue_name,
21
25
  check_available_agents,
22
26
  }: ConditionalQueueStatusArgs): Promise<QueueStatusResult> {
23
27
  const data = await getQueueAvailability(queue_id, check_available_agents)
28
+ // await trackEvent(request, EventAction.handoffOption, {
29
+ // queueId: queue_id,
30
+ // queueName: queue_name,
31
+ // })
24
32
  if (check_available_agents && data.open && data.available_agents === 0) {
25
33
  return QueueStatusResult.OPEN_WITHOUT_AGENTS
26
34
  }
27
35
  return data.open ? QueueStatusResult.OPEN : QueueStatusResult.CLOSED
28
36
  }
29
37
 
30
- interface AvailabilityData {
38
+ export class HubtypeQueuesApi {
39
+ public queueId: string
40
+ public checkAvailableAgents: boolean
41
+
42
+ constructor(queueId: string, checkAvailableAgents?: boolean) {
43
+ this.queueId = queueId
44
+ this.checkAvailableAgents = checkAvailableAgents || false
45
+ }
46
+
47
+ async getAvailability(): Promise<AvailabilityData> {
48
+ const response = await axios.get(
49
+ `${HUBTYPE_API_URL}/public/v1/queues/${this.queueId}/availability/`,
50
+ // TODO: Make it configurable in the future
51
+ {
52
+ params: {
53
+ check_queue_schedule: true,
54
+ check_waiting_cases: false,
55
+ check_available_agents: this.checkAvailableAgents,
56
+ },
57
+ }
58
+ )
59
+ return response.data
60
+ }
61
+ }
62
+
63
+ export interface AvailabilityData {
31
64
  available: boolean
32
65
  waiting_cases: number
33
66
  availability_threshold_waiting_cases: number
@@ -40,16 +73,6 @@ export async function getQueueAvailability(
40
73
  queueId: string,
41
74
  checkAvailableAgents = false
42
75
  ): Promise<AvailabilityData> {
43
- const response = await axios.get(
44
- `${HUBTYPE_API_URL}/public/v1/queues/${queueId}/availability/`,
45
- // TODO: Make it configurable in the future
46
- {
47
- params: {
48
- check_queue_schedule: true,
49
- check_waiting_cases: false,
50
- check_available_agents: checkAvailableAgents,
51
- },
52
- }
53
- )
54
- return response.data
76
+ const queuesApi = new HubtypeQueuesApi(queueId, checkAvailableAgents)
77
+ return await queuesApi.getAvailability()
55
78
  }
package/src/index.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import { Plugin, PluginPreRequest, Session } from '@botonic/core'
2
2
  import { ActionRequest } from '@botonic/react'
3
+ import { v4 as uuid } from 'uuid'
3
4
 
4
5
  import { FlowBuilderApi } from './api'
5
6
  import {
7
+ BOT_ACTION_PAYLOAD_PREFIX,
6
8
  FLOW_BUILDER_API_URL_PROD,
7
9
  SEPARATOR,
8
10
  SOURCE_INFO_SEPARATOR,
@@ -18,6 +20,7 @@ import {
18
20
  } from './content-fields'
19
21
  import { FlowWhatsappCtaUrlButtonNode } from './content-fields/flow-whatsapp-cta-url-button'
20
22
  import {
23
+ HtBotActionNode,
21
24
  HtFlowBuilderData,
22
25
  HtFunctionArgument,
23
26
  HtFunctionArguments,
@@ -27,6 +30,7 @@ import {
27
30
  HtNodeWithContentType,
28
31
  } from './content-fields/hubtype-fields'
29
32
  import { DEFAULT_FUNCTIONS } from './functions'
33
+ import { EventAction, getNodeEventArgs, trackEvent } from './tracking'
30
34
  import {
31
35
  BotonicPluginFlowBuilderOptions,
32
36
  FlowBuilderJSONVersion,
@@ -34,8 +38,8 @@ import {
34
38
  PayloadParamsBase,
35
39
  } from './types'
36
40
  import { getNodeByUserInput } from './user-input'
41
+ import { SmartIntentsInferenceConfig } from './user-input/smart-intent'
37
42
  import { resolveGetAccessToken } from './utils'
38
-
39
43
  export default class BotonicPluginFlowBuilder implements Plugin {
40
44
  public cmsApi: FlowBuilderApi
41
45
  private flowUrl: string
@@ -46,12 +50,13 @@ export default class BotonicPluginFlowBuilder implements Plugin {
46
50
  public getLocale: (session: Session) => string
47
51
  public trackEvent?: (
48
52
  request: ActionRequest,
49
- eventName: string,
53
+ eventAction: string,
50
54
  args?: Record<string, any>
51
55
  ) => Promise<void>
52
56
  public getKnowledgeBaseResponse?: (
53
57
  request: ActionRequest
54
58
  ) => Promise<KnowledgeBaseResponse>
59
+ public smartIntentsConfig: SmartIntentsInferenceConfig
55
60
 
56
61
  constructor(readonly options: BotonicPluginFlowBuilderOptions) {
57
62
  const apiUrl = options.apiUrl || FLOW_BUILDER_API_URL_PROD
@@ -62,6 +67,10 @@ export default class BotonicPluginFlowBuilder implements Plugin {
62
67
  this.getAccessToken = resolveGetAccessToken(options)
63
68
  this.trackEvent = options.trackEvent
64
69
  this.getKnowledgeBaseResponse = options.getKnowledgeBaseResponse
70
+ this.smartIntentsConfig = {
71
+ ...options?.smartIntentsConfig,
72
+ useLatest: jsonVersion === FlowBuilderJSONVersion.LATEST,
73
+ }
65
74
  const customFunctions = options.customFunctions || {}
66
75
  this.functions = { ...DEFAULT_FUNCTIONS, ...customFunctions }
67
76
  }
@@ -86,18 +95,35 @@ export default class BotonicPluginFlowBuilder implements Plugin {
86
95
  const nodeByUserInput = await getNodeByUserInput(
87
96
  this.cmsApi,
88
97
  resolvedLocale,
89
- request as unknown as ActionRequest
98
+ request as unknown as ActionRequest,
99
+ this.smartIntentsConfig
90
100
  )
91
101
  request.input.payload = this.cmsApi.getPayload(nodeByUserInput?.target)
92
102
  }
93
103
 
104
+ this.updateRequestBeforeRoutes(request)
105
+ }
106
+
107
+ private updateRequestBeforeRoutes(request: PluginPreRequest) {
94
108
  if (request.input.payload) {
95
- request.input.payload = request.input.payload?.split(
96
- SOURCE_INFO_SEPARATOR
97
- )[0]
109
+ request.input.payload = this.removeSourceSufix(request.input.payload)
110
+
111
+ if (request.input.payload.startsWith(BOT_ACTION_PAYLOAD_PREFIX)) {
112
+ request.input.payload = this.replacePayload(request.input.payload)
113
+ }
98
114
  }
99
115
  }
100
116
 
117
+ private removeSourceSufix(payload: string): string {
118
+ return payload.split(SOURCE_INFO_SEPARATOR)[0]
119
+ }
120
+
121
+ private replacePayload(payload: string): string {
122
+ const botActionId = payload.split(SEPARATOR)[1]
123
+ const botActionNode = this.cmsApi.getNodeById<HtBotActionNode>(botActionId)
124
+ return this.cmsApi.createPayloadWithParams(botActionNode)
125
+ }
126
+
101
127
  async getContentsByContentID(
102
128
  contentID: string,
103
129
  locale: string,
@@ -122,8 +148,19 @@ export default class BotonicPluginFlowBuilder implements Plugin {
122
148
  }
123
149
 
124
150
  async getStartContents(locale: string): Promise<FlowContent[]> {
151
+ const resolvedLocale = this.cmsApi.getResolvedLocale(locale)
125
152
  const startNode = this.cmsApi.getStartNode()
126
- return await this.getContentsByNode(startNode, locale)
153
+ this.currentRequest.session.flow_thread_id = uuid()
154
+ const eventArgs = getNodeEventArgs(
155
+ this.currentRequest as unknown as ActionRequest,
156
+ startNode
157
+ )
158
+ await trackEvent(
159
+ this.currentRequest as unknown as ActionRequest,
160
+ EventAction.flowNode,
161
+ eventArgs
162
+ )
163
+ return await this.getContentsByNode(startNode, resolvedLocale)
127
164
  }
128
165
 
129
166
  async getContentsByNode(
@@ -239,6 +276,10 @@ export default class BotonicPluginFlowBuilder implements Plugin {
239
276
  const payloadParams = JSON.parse(payload.split(SEPARATOR)[1] || '{}')
240
277
  return payloadParams
241
278
  }
279
+
280
+ getFlowName(flowId: string): string {
281
+ return this.cmsApi.getFlowName(flowId)
282
+ }
242
283
  }
243
284
 
244
285
  export * from './action'
package/src/tracking.ts CHANGED
@@ -1,32 +1,43 @@
1
1
  import { ActionRequest } from '@botonic/react'
2
2
 
3
+ import { HtNodeWithContent } from './content-fields/hubtype-fields'
3
4
  import { getFlowBuilderPlugin } from './helpers'
4
5
 
5
6
  export async function trackEvent(
6
7
  request: ActionRequest,
7
- eventName: EventName,
8
+ eventAction: EventAction,
8
9
  args?: Record<string, any>
9
10
  ): Promise<void> {
10
11
  const flowBuilderPlugin = getFlowBuilderPlugin(request.plugins)
11
12
  if (flowBuilderPlugin.trackEvent) {
12
- await flowBuilderPlugin.trackEvent(request, eventName, args)
13
+ await flowBuilderPlugin.trackEvent(request, eventAction, args)
13
14
  }
14
15
  return
15
16
  }
16
17
 
17
- export enum EventName {
18
- botAgentRating = 'bot_agent_rating',
19
- botChannelRating = 'bot_channel_rating',
20
- botFaqUseful = 'bot_faq_useful',
21
- botRating = 'bot_rating',
22
- botFaq = 'bot_faq',
23
- botStart = 'bot_start',
24
- botOpen = 'bot_open',
25
- botAiModel = 'bot_ai_model',
26
- botAiKnowledgeBase = 'bot_ai_knowledge_base',
27
- botKeywordsModel = 'bot_keywords_model',
28
- fallback = 'fallback',
18
+ export function getNodeEventArgs(
19
+ request: ActionRequest,
20
+ node: HtNodeWithContent
21
+ ) {
22
+ const flowBuilderPlugin = getFlowBuilderPlugin(request.plugins)
23
+ return {
24
+ flowThreadId: request.session.flow_thread_id,
25
+ flowId: node.flow_id,
26
+ flowName: flowBuilderPlugin.getFlowName(node.flow_id),
27
+ flowNodeId: node.id,
28
+ flowNodeContentId: node.code,
29
+ flowNodeIsMeaningful: undefined, //node?.isMeaningful,
30
+ }
31
+ }
32
+
33
+ export enum EventAction {
34
+ flowNode = 'flow_node',
29
35
  handoffOption = 'handoff_option',
30
36
  handoffSuccess = 'handoff_success',
31
37
  handoffFail = 'handoff_fail',
38
+ keyword = 'nlu_keyword',
39
+ intent = 'nlu_intent',
40
+ intentSmart = 'nlu_intent_smart',
41
+ knowledgebase = 'knowledgebase',
42
+ fallback = 'fallback',
32
43
  }