@botonic/plugin-flow-builder 0.26.2-alpha.0 → 0.27.0-alpha.1

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 (89) hide show
  1. package/lib/cjs/action/index.js +41 -19
  2. package/lib/cjs/action/index.js.map +1 -1
  3. package/lib/cjs/action/knowledge-bases.js +32 -7
  4. package/lib/cjs/action/knowledge-bases.js.map +1 -1
  5. package/lib/cjs/api.d.ts +2 -4
  6. package/lib/cjs/api.js +7 -32
  7. package/lib/cjs/api.js.map +1 -1
  8. package/lib/cjs/constants.d.ts +1 -1
  9. package/lib/cjs/constants.js +2 -2
  10. package/lib/cjs/constants.js.map +1 -1
  11. package/lib/cjs/content-fields/flow-handoff.js +7 -19
  12. package/lib/cjs/content-fields/flow-handoff.js.map +1 -1
  13. package/lib/cjs/content-fields/index.d.ts +1 -0
  14. package/lib/cjs/content-fields/index.js +3 -1
  15. package/lib/cjs/content-fields/index.js.map +1 -1
  16. package/lib/cjs/functions/conditional-queue-status.d.ts +7 -1
  17. package/lib/cjs/functions/conditional-queue-status.js +24 -11
  18. package/lib/cjs/functions/conditional-queue-status.js.map +1 -1
  19. package/lib/cjs/index.d.ts +6 -3
  20. package/lib/cjs/index.js +22 -13
  21. package/lib/cjs/index.js.map +1 -1
  22. package/lib/cjs/tracking.d.ts +27 -16
  23. package/lib/cjs/tracking.js +45 -20
  24. package/lib/cjs/tracking.js.map +1 -1
  25. package/lib/cjs/types.d.ts +19 -2
  26. package/lib/cjs/types.js.map +1 -1
  27. package/lib/cjs/user-input/index.d.ts +2 -1
  28. package/lib/cjs/user-input/index.js +9 -3
  29. package/lib/cjs/user-input/index.js.map +1 -1
  30. package/lib/cjs/user-input/intent.js +12 -8
  31. package/lib/cjs/user-input/intent.js.map +1 -1
  32. package/lib/cjs/user-input/keyword.d.ts +21 -1
  33. package/lib/cjs/user-input/keyword.js +66 -14
  34. package/lib/cjs/user-input/keyword.js.map +1 -1
  35. package/lib/cjs/user-input/smart-intent.d.ts +18 -1
  36. package/lib/cjs/user-input/smart-intent.js +46 -28
  37. package/lib/cjs/user-input/smart-intent.js.map +1 -1
  38. package/lib/esm/action/index.js +42 -20
  39. package/lib/esm/action/index.js.map +1 -1
  40. package/lib/esm/action/knowledge-bases.js +33 -8
  41. package/lib/esm/action/knowledge-bases.js.map +1 -1
  42. package/lib/esm/api.d.ts +2 -4
  43. package/lib/esm/api.js +8 -33
  44. package/lib/esm/api.js.map +1 -1
  45. package/lib/esm/constants.d.ts +1 -1
  46. package/lib/esm/constants.js +1 -1
  47. package/lib/esm/constants.js.map +1 -1
  48. package/lib/esm/content-fields/flow-handoff.js +7 -19
  49. package/lib/esm/content-fields/flow-handoff.js.map +1 -1
  50. package/lib/esm/content-fields/index.d.ts +1 -0
  51. package/lib/esm/content-fields/index.js +1 -0
  52. package/lib/esm/content-fields/index.js.map +1 -1
  53. package/lib/esm/functions/conditional-queue-status.d.ts +7 -1
  54. package/lib/esm/functions/conditional-queue-status.js +22 -10
  55. package/lib/esm/functions/conditional-queue-status.js.map +1 -1
  56. package/lib/esm/index.d.ts +6 -3
  57. package/lib/esm/index.js +23 -14
  58. package/lib/esm/index.js.map +1 -1
  59. package/lib/esm/tracking.d.ts +27 -16
  60. package/lib/esm/tracking.js +42 -19
  61. package/lib/esm/tracking.js.map +1 -1
  62. package/lib/esm/types.d.ts +19 -2
  63. package/lib/esm/types.js.map +1 -1
  64. package/lib/esm/user-input/index.d.ts +2 -1
  65. package/lib/esm/user-input/index.js +11 -5
  66. package/lib/esm/user-input/index.js.map +1 -1
  67. package/lib/esm/user-input/intent.js +13 -9
  68. package/lib/esm/user-input/intent.js.map +1 -1
  69. package/lib/esm/user-input/keyword.d.ts +21 -1
  70. package/lib/esm/user-input/keyword.js +65 -13
  71. package/lib/esm/user-input/keyword.js.map +1 -1
  72. package/lib/esm/user-input/smart-intent.d.ts +18 -1
  73. package/lib/esm/user-input/smart-intent.js +44 -26
  74. package/lib/esm/user-input/smart-intent.js.map +1 -1
  75. package/package.json +2 -2
  76. package/src/action/index.tsx +71 -25
  77. package/src/action/knowledge-bases.ts +44 -10
  78. package/src/api.ts +11 -57
  79. package/src/constants.ts +1 -1
  80. package/src/content-fields/flow-handoff.tsx +8 -28
  81. package/src/content-fields/index.ts +1 -0
  82. package/src/functions/conditional-queue-status.ts +28 -13
  83. package/src/index.ts +36 -19
  84. package/src/tracking.ts +53 -17
  85. package/src/types.ts +18 -2
  86. package/src/user-input/index.ts +13 -7
  87. package/src/user-input/intent.ts +17 -10
  88. package/src/user-input/keyword.ts +90 -15
  89. package/src/user-input/smart-intent.ts +57 -29
@@ -5,7 +5,8 @@ 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 { EventName, trackEvent } from '../tracking'
8
+ import BotonicPluginFlowBuilder from '../index'
9
+ import { EventAction, trackEvent, trackFlowContent } from '../tracking'
9
10
  import { createNodeFromKnowledgeBase } from './knowledge-bases'
10
11
 
11
12
  export type FlowBuilderActionProps = {
@@ -18,20 +19,14 @@ 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
33
26
  ) as FlowHandoff
34
- if (handoffContent) await handoffContent.doHandoff(request)
27
+ if (handoffContent) {
28
+ await handoffContent.doHandoff(request)
29
+ }
35
30
 
36
31
  return { contents }
37
32
  }
@@ -55,26 +50,73 @@ export class FlowBuilderMultichannelAction extends FlowBuilderAction {
55
50
  }
56
51
  }
57
52
 
58
- async function getTargetNode(cmsApi: FlowBuilderApi, request: ActionRequest) {
53
+ async function getContents(request: ActionRequest): Promise<FlowContent[]> {
54
+ const flowBuilderPlugin = getFlowBuilderPlugin(request.plugins)
55
+ const cmsApi = flowBuilderPlugin.cmsApi
56
+ const locale = flowBuilderPlugin.getLocale(request.session)
57
+ const resolvedLocale = flowBuilderPlugin.cmsApi.getResolvedLocale(locale)
58
+ const context = {
59
+ cmsApi,
60
+ flowBuilderPlugin,
61
+ request,
62
+ resolvedLocale,
63
+ }
64
+
59
65
  if (request.session.is_first_interaction) {
60
- const startNode = cmsApi.getStartNode()
61
- await trackEvent(request, EventName.botStart)
62
- return startNode
66
+ return await flowBuilderPlugin.getStartContents(resolvedLocale)
67
+ }
68
+
69
+ if (request.input.payload) {
70
+ return await getContentsByPayload(context)
63
71
  }
64
- const contentId = request.input.payload
65
72
 
66
- const targetNode = contentId
67
- ? cmsApi.getNodeById<HtNodeWithContent>(contentId)
73
+ return await getContentsByFallback(context)
74
+ }
75
+
76
+ interface FlowBuilderContext {
77
+ cmsApi: FlowBuilderApi
78
+ flowBuilderPlugin: BotonicPluginFlowBuilder
79
+ request: ActionRequest
80
+ resolvedLocale: string
81
+ }
82
+
83
+ async function getContentsByPayload({
84
+ cmsApi,
85
+ flowBuilderPlugin,
86
+ request,
87
+ resolvedLocale,
88
+ }: FlowBuilderContext): Promise<FlowContent[]> {
89
+ const targetNode = request.input.payload
90
+ ? cmsApi.getNodeById<HtNodeWithContent>(request.input.payload)
68
91
  : undefined
69
92
 
70
93
  if (targetNode) {
71
- const eventArgs = {
72
- faq_name: targetNode.code,
73
- }
74
- await trackEvent(request, EventName.botFaq, eventArgs)
75
- return targetNode
94
+ const contents = await flowBuilderPlugin.getContentsByNode(
95
+ targetNode,
96
+ resolvedLocale
97
+ )
98
+ await trackFlowContent(request, contents)
99
+
100
+ return contents
76
101
  }
77
- return await getFallbackNode(cmsApi, request)
102
+
103
+ return []
104
+ }
105
+
106
+ async function getContentsByFallback({
107
+ cmsApi,
108
+ flowBuilderPlugin,
109
+ request,
110
+ resolvedLocale,
111
+ }: FlowBuilderContext): Promise<FlowContent[]> {
112
+ const fallbackNode = await getFallbackNode(cmsApi, request)
113
+ const fallbackContents = await flowBuilderPlugin.getContentsByNode(
114
+ fallbackNode,
115
+ resolvedLocale
116
+ )
117
+ await trackFlowContent(request, fallbackContents)
118
+
119
+ return fallbackContents
78
120
  }
79
121
 
80
122
  async function getFallbackNode(cmsApi: FlowBuilderApi, request: ActionRequest) {
@@ -95,6 +137,10 @@ async function getFallbackNode(cmsApi: FlowBuilderApi, request: ActionRequest) {
95
137
  const fallbackNode = cmsApi.getFallbackNode(isFirstFallbackOption)
96
138
  request.session.user.extra_data.isFirstFallbackOption = !isFirstFallbackOption
97
139
 
98
- await trackEvent(request, EventName.fallback)
140
+ trackEvent(request, EventAction.Fallback, {
141
+ fallbackOut: isFirstFallbackOption ? 1 : 2,
142
+ fallbackMessageId: request.input.message_id,
143
+ })
144
+
99
145
  return fallbackNode
100
146
  }
@@ -9,7 +9,8 @@ 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, KnowledgebaseFailReason, trackEvent } from '../tracking'
13
+ import { KnowledgeBaseResponse } from '../types'
13
14
 
14
15
  export async function createNodeFromKnowledgeBase(
15
16
  cmsApi: FlowBuilderApi,
@@ -27,14 +28,12 @@ export async function createNodeFromKnowledgeBase(
27
28
  try {
28
29
  const knowledgeBaseResponse =
29
30
  await flowBuilderPlugin.getKnowledgeBaseResponse(request)
30
- if (knowledgeBaseResponse.hasKnowledge) {
31
- await trackEvent(request, EventName.botAiKnowledgeBase, {
32
- answer: knowledgeBaseResponse.answer,
33
- knowledge_source_ids: knowledgeBaseResponse.sources.map(
34
- source => source.knowledgeSourceId
35
- ),
36
- })
31
+ await trackKnowledgeBase(knowledgeBaseResponse, request)
37
32
 
33
+ if (
34
+ knowledgeBaseResponse.hasKnowledge &&
35
+ knowledgeBaseResponse.isFaithuful
36
+ ) {
38
37
  const knowledgeBaseNode: HtTextNode = {
39
38
  type: HtNodeWithContentType.TEXT,
40
39
  content: {
@@ -59,9 +58,44 @@ export async function createNodeFromKnowledgeBase(
59
58
  return knowledgeBaseNode
60
59
  }
61
60
  } catch (e) {
62
- console.log('Hubtype knowledge base api error: ', { e })
63
- return undefined
61
+ console.error('Hubtype knowledge base api error: ', { e })
64
62
  }
65
63
  }
66
64
  return undefined
67
65
  }
66
+
67
+ async function trackKnowledgeBase(
68
+ response: KnowledgeBaseResponse,
69
+ request: ActionRequest
70
+ ) {
71
+ /* TODO:
72
+ In order to have all these parameters in the base knowlege response
73
+ it is necessary to use the new endpoint to which the knowledge sources
74
+ have to be indicated. For now this will not work, we need to finish
75
+ the knowldege base node in the flow builder frontend.
76
+ */
77
+ const knowledgebaseInferenceId = response.inferenceId
78
+ const knowledgebaseSourcesIds = response.sources.map(
79
+ source => source.knowledgeSourceId
80
+ )
81
+ const knowledgebaseChunksIds = response.sources.map(
82
+ source => source.knowledgeChunkId
83
+ )
84
+ const knowledgebaseMessageId = request.input.message_id
85
+
86
+ let knowledgebaseFailReason: KnowledgebaseFailReason | undefined
87
+ if (!response.isFaithuful) {
88
+ knowledgebaseFailReason = KnowledgebaseFailReason.Hallucination
89
+ }
90
+ if (!response.hasKnowledge) {
91
+ knowledgebaseFailReason = KnowledgebaseFailReason.NoKnowledge
92
+ }
93
+
94
+ await trackEvent(request, EventAction.Knowledgebase, {
95
+ knowledgebaseInferenceId,
96
+ knowledgebaseFailReason,
97
+ knowledgebaseSourcesIds,
98
+ knowledgebaseChunksIds,
99
+ knowledgebaseMessageId,
100
+ })
101
+ }
package/src/api.ts CHANGED
@@ -1,11 +1,7 @@
1
1
  import { Input, PluginPreRequest } from '@botonic/core'
2
2
  import axios from 'axios'
3
3
 
4
- import {
5
- BOT_ACTION_PAYLOAD_SEPARATOR,
6
- REG_EXP_PATTERN,
7
- SEPARATOR,
8
- } from './constants'
4
+ import { BOT_ACTION_PAYLOAD_PREFIX, SEPARATOR } from './constants'
9
5
  import {
10
6
  HtBotActionNode,
11
7
  HtFallbackNode,
@@ -154,57 +150,10 @@ export class FlowBuilderApi {
154
150
  return predictedConfidence >= nodeConfidence
155
151
  }
156
152
 
157
- getNodeByKeyword(
158
- userInput: string,
159
- locale: string
160
- ): HtKeywordNode | undefined {
161
- try {
162
- const keywordNodes = this.flow.nodes.filter(
163
- node => node.type === HtNodeWithContentType.KEYWORD
164
- ) as HtKeywordNode[]
165
- const matchedKeywordNodes = keywordNodes.filter(node =>
166
- this.matchKeywords(node, userInput, locale)
167
- )
168
- if (matchedKeywordNodes.length > 0 && matchedKeywordNodes[0].target) {
169
- return matchedKeywordNodes[0]
170
- }
171
- } catch (error) {
172
- console.error(`Error getting node by keyword '${userInput}': `, error)
173
- }
174
-
175
- return undefined
176
- }
177
-
178
- private matchKeywords(
179
- node: HtKeywordNode,
180
- input: string,
181
- locale: string
182
- ): boolean {
183
- const result = node.content.keywords.find(
184
- keywords =>
185
- keywords.locale === locale &&
186
- this.inputMatchesAnyKeyword(input, keywords.values)
187
- )
188
- return Boolean(result)
189
- }
190
-
191
- private inputMatchesAnyKeyword(input: string, keywords: string[]): boolean {
192
- return keywords.some(keyword => {
193
- const regExpMatchArray = keyword.match(REG_EXP_PATTERN)
194
- if (regExpMatchArray) {
195
- return this.resolveKeywordAsRegExp(regExpMatchArray, input)
196
- }
197
- return input.includes(keyword)
198
- })
199
- }
200
-
201
- private resolveKeywordAsRegExp(
202
- regExpMatchArray: RegExpMatchArray,
203
- input: string
204
- ) {
205
- const [, pattern, flags] = regExpMatchArray
206
- const keywordAsRegExp = new RegExp(pattern, flags)
207
- return input.match(keywordAsRegExp)
153
+ getKeywordNodes(): HtKeywordNode[] {
154
+ return this.flow.nodes.filter(
155
+ node => node.type === HtNodeWithContentType.KEYWORD
156
+ ) as HtKeywordNode[]
208
157
  }
209
158
 
210
159
  getPayload(target?: HtNodeLink): string | undefined {
@@ -213,7 +162,7 @@ export class FlowBuilderApi {
213
162
  }
214
163
 
215
164
  if (target.type === HtNodeWithoutContentType.BOT_ACTION) {
216
- return `${BOT_ACTION_PAYLOAD_SEPARATOR}${target.id}`
165
+ return `${BOT_ACTION_PAYLOAD_PREFIX}${target.id}`
217
166
  }
218
167
 
219
168
  return target.id
@@ -250,6 +199,11 @@ export class FlowBuilderApi {
250
199
  }
251
200
  }
252
201
 
202
+ getFlowName(flowId: string): string {
203
+ const flow = this.flow.flows.find(flow => flow.id === flowId)
204
+ return flow ? flow.name : ''
205
+ }
206
+
253
207
  getResolvedLocale(locale: string): string {
254
208
  if (this.flow.locales.find(flowLocale => flowLocale === locale)) {
255
209
  return locale
package/src/constants.ts CHANGED
@@ -2,7 +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_SEPARATOR = `ba${SEPARATOR}`
5
+ export const BOT_ACTION_PAYLOAD_PREFIX = `ba${SEPARATOR}`
6
6
  export const VARIABLE_PATTERN = /{([^}]+)}/g
7
7
  export const ACCESS_TOKEN_VARIABLE_KEY = '_access_token'
8
8
  export const REG_EXP_PATTERN = /^\/(.*)\/([gimyus]*)$/
@@ -3,10 +3,8 @@ import { ActionRequest, WebchatSettings } from '@botonic/react'
3
3
  import React from 'react'
4
4
 
5
5
  import { FlowBuilderApi } from '../api'
6
- import { getQueueAvailability } from '../functions/conditional-queue-status'
7
- import { EventName, trackEvent } from '../tracking'
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
  }
@@ -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
@@ -27,7 +27,32 @@ export async function conditionalQueueStatus({
27
27
  return data.open ? QueueStatusResult.OPEN : QueueStatusResult.CLOSED
28
28
  }
29
29
 
30
- interface AvailabilityData {
30
+ export class HubtypeQueuesApi {
31
+ public queueId: string
32
+ public checkAvailableAgents: boolean
33
+
34
+ constructor(queueId: string, checkAvailableAgents?: boolean) {
35
+ this.queueId = queueId
36
+ this.checkAvailableAgents = checkAvailableAgents || false
37
+ }
38
+
39
+ async getAvailability(): Promise<AvailabilityData> {
40
+ const response = await axios.get(
41
+ `${HUBTYPE_API_URL}/public/v1/queues/${this.queueId}/availability/`,
42
+ // TODO: Make it configurable in the future
43
+ {
44
+ params: {
45
+ check_queue_schedule: true,
46
+ check_waiting_cases: false,
47
+ check_available_agents: this.checkAvailableAgents,
48
+ },
49
+ }
50
+ )
51
+ return response.data
52
+ }
53
+ }
54
+
55
+ export interface AvailabilityData {
31
56
  available: boolean
32
57
  waiting_cases: number
33
58
  availability_threshold_waiting_cases: number
@@ -40,16 +65,6 @@ export async function getQueueAvailability(
40
65
  queueId: string,
41
66
  checkAvailableAgents = false
42
67
  ): 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
68
+ const queuesApi = new HubtypeQueuesApi(queueId, checkAvailableAgents)
69
+ return await queuesApi.getAvailability()
55
70
  }
package/src/index.ts CHANGED
@@ -1,9 +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 {
6
- BOT_ACTION_PAYLOAD_SEPARATOR,
7
+ BOT_ACTION_PAYLOAD_PREFIX,
7
8
  FLOW_BUILDER_API_URL_PROD,
8
9
  SEPARATOR,
9
10
  SOURCE_INFO_SEPARATOR,
@@ -29,6 +30,7 @@ import {
29
30
  HtNodeWithContentType,
30
31
  } from './content-fields/hubtype-fields'
31
32
  import { DEFAULT_FUNCTIONS } from './functions'
33
+ import { trackFlowContent } from './tracking'
32
34
  import {
33
35
  BotonicPluginFlowBuilderOptions,
34
36
  FlowBuilderJSONVersion,
@@ -36,8 +38,8 @@ import {
36
38
  PayloadParamsBase,
37
39
  } from './types'
38
40
  import { getNodeByUserInput } from './user-input'
41
+ import { SmartIntentsInferenceConfig } from './user-input/smart-intent'
39
42
  import { resolveGetAccessToken } from './utils'
40
-
41
43
  export default class BotonicPluginFlowBuilder implements Plugin {
42
44
  public cmsApi: FlowBuilderApi
43
45
  private flowUrl: string
@@ -48,12 +50,13 @@ export default class BotonicPluginFlowBuilder implements Plugin {
48
50
  public getLocale: (session: Session) => string
49
51
  public trackEvent?: (
50
52
  request: ActionRequest,
51
- eventName: string,
53
+ eventAction: string,
52
54
  args?: Record<string, any>
53
55
  ) => Promise<void>
54
56
  public getKnowledgeBaseResponse?: (
55
57
  request: ActionRequest
56
58
  ) => Promise<KnowledgeBaseResponse>
59
+ public smartIntentsConfig: SmartIntentsInferenceConfig
57
60
 
58
61
  constructor(readonly options: BotonicPluginFlowBuilderOptions) {
59
62
  const apiUrl = options.apiUrl || FLOW_BUILDER_API_URL_PROD
@@ -64,6 +67,10 @@ export default class BotonicPluginFlowBuilder implements Plugin {
64
67
  this.getAccessToken = resolveGetAccessToken(options)
65
68
  this.trackEvent = options.trackEvent
66
69
  this.getKnowledgeBaseResponse = options.getKnowledgeBaseResponse
70
+ this.smartIntentsConfig = {
71
+ ...options?.smartIntentsConfig,
72
+ useLatest: jsonVersion === FlowBuilderJSONVersion.LATEST,
73
+ }
67
74
  const customFunctions = options.customFunctions || {}
68
75
  this.functions = { ...DEFAULT_FUNCTIONS, ...customFunctions }
69
76
  }
@@ -88,7 +95,8 @@ export default class BotonicPluginFlowBuilder implements Plugin {
88
95
  const nodeByUserInput = await getNodeByUserInput(
89
96
  this.cmsApi,
90
97
  resolvedLocale,
91
- request as unknown as ActionRequest
98
+ request as unknown as ActionRequest,
99
+ this.smartIntentsConfig
92
100
  )
93
101
  request.input.payload = this.cmsApi.getPayload(nodeByUserInput?.target)
94
102
  }
@@ -98,25 +106,22 @@ export default class BotonicPluginFlowBuilder implements Plugin {
98
106
 
99
107
  private updateRequestBeforeRoutes(request: PluginPreRequest) {
100
108
  if (request.input.payload) {
101
- request.input.payload = this.removeSourceSeparatorFromPayload(
102
- request.input.payload
103
- )
104
- request.input.payload = this.updateBotActionPayload(request.input.payload)
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
+ }
105
114
  }
106
115
  }
107
116
 
108
- private removeSourceSeparatorFromPayload(payload: string): string {
109
- return payload?.split(SOURCE_INFO_SEPARATOR)[0]
117
+ private removeSourceSufix(payload: string): string {
118
+ return payload.split(SOURCE_INFO_SEPARATOR)[0]
110
119
  }
111
120
 
112
- private updateBotActionPayload(payload: string): string {
113
- if (payload.startsWith(BOT_ACTION_PAYLOAD_SEPARATOR)) {
114
- const botActionId = payload.split(SEPARATOR)[1]
115
- const botActionNode =
116
- this.cmsApi.getNodeById<HtBotActionNode>(botActionId)
117
- return this.cmsApi.createPayloadWithParams(botActionNode)
118
- }
119
- return payload
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)
120
125
  }
121
126
 
122
127
  async getContentsByContentID(
@@ -143,8 +148,16 @@ export default class BotonicPluginFlowBuilder implements Plugin {
143
148
  }
144
149
 
145
150
  async getStartContents(locale: string): Promise<FlowContent[]> {
151
+ const resolvedLocale = this.cmsApi.getResolvedLocale(locale)
146
152
  const startNode = this.cmsApi.getStartNode()
147
- return await this.getContentsByNode(startNode, locale)
153
+ this.currentRequest.session.flow_thread_id = uuid()
154
+ const contents = await this.getContentsByNode(startNode, resolvedLocale)
155
+
156
+ await trackFlowContent(
157
+ this.currentRequest as unknown as ActionRequest,
158
+ contents
159
+ )
160
+ return contents
148
161
  }
149
162
 
150
163
  async getContentsByNode(
@@ -260,6 +273,10 @@ export default class BotonicPluginFlowBuilder implements Plugin {
260
273
  const payloadParams = JSON.parse(payload.split(SEPARATOR)[1] || '{}')
261
274
  return payloadParams
262
275
  }
276
+
277
+ getFlowName(flowId: string): string {
278
+ return this.cmsApi.getFlowName(flowId)
279
+ }
263
280
  }
264
281
 
265
282
  export * from './action'
package/src/tracking.ts CHANGED
@@ -1,32 +1,68 @@
1
1
  import { ActionRequest } from '@botonic/react'
2
2
 
3
+ import { FlowContent } from './content-fields'
4
+ import { HtNodeWithContent } from './content-fields/hubtype-fields'
3
5
  import { getFlowBuilderPlugin } from './helpers'
4
6
 
7
+ export enum EventAction {
8
+ FlowNode = 'flow_node',
9
+ Keyword = 'nlu_keyword',
10
+ Intent = 'nlu_intent',
11
+ IntentSmart = 'nlu_intent_smart',
12
+ Knowledgebase = 'knowledgebase',
13
+ Fallback = 'fallback',
14
+ }
15
+
16
+ export enum KnowledgebaseFailReason {
17
+ NoKnowledge = 'no_knowledge',
18
+ Hallucination = 'hallucination',
19
+ }
20
+
5
21
  export async function trackEvent(
6
22
  request: ActionRequest,
7
- eventName: EventName,
23
+ eventAction: EventAction,
8
24
  args?: Record<string, any>
9
25
  ): Promise<void> {
10
26
  const flowBuilderPlugin = getFlowBuilderPlugin(request.plugins)
11
27
  if (flowBuilderPlugin.trackEvent) {
12
- await flowBuilderPlugin.trackEvent(request, eventName, args)
28
+ await flowBuilderPlugin.trackEvent(request, eventAction, args)
13
29
  }
14
30
  return
15
31
  }
16
32
 
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',
29
- handoffOption = 'handoff_option',
30
- handoffSuccess = 'handoff_success',
31
- handoffFail = 'handoff_fail',
33
+ export async function trackFlowContent(
34
+ request: ActionRequest,
35
+ contents: FlowContent[]
36
+ ) {
37
+ const flowBuilderPlugin = getFlowBuilderPlugin(request.plugins)
38
+ const cmsApi = flowBuilderPlugin.cmsApi
39
+ const firstNodeContent = cmsApi.getNodeById<HtNodeWithContent>(contents[0].id)
40
+ const flowName = flowBuilderPlugin.getFlowName(firstNodeContent.flow_id)
41
+ const eventArgs = getContentEventArgs(request, {
42
+ code: firstNodeContent.code,
43
+ flowId: firstNodeContent.flow_id,
44
+ flowName,
45
+ id: firstNodeContent.id,
46
+ })
47
+ await trackEvent(request, EventAction.FlowNode, eventArgs)
48
+ }
49
+
50
+ export function getContentEventArgs(
51
+ request: ActionRequest,
52
+ contentInfo: {
53
+ code: string
54
+ flowId: string
55
+ flowName: string
56
+ id: string
57
+ }
58
+ ) {
59
+ const flowBuilderPlugin = getFlowBuilderPlugin(request.plugins)
60
+ return {
61
+ flowThreadId: request.session.flow_thread_id,
62
+ flowId: contentInfo.flowId,
63
+ flowName: flowBuilderPlugin.getFlowName(contentInfo.flowId),
64
+ flowNodeId: contentInfo.id,
65
+ flowNodeContentId: contentInfo.code,
66
+ flowNodeIsMeaningful: undefined, //node?.isMeaningful,
67
+ }
32
68
  }