@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
package/src/types.ts CHANGED
@@ -12,12 +12,13 @@ export interface BotonicPluginFlowBuilderOptions {
12
12
  getAccessToken: () => string
13
13
  trackEvent?: (
14
14
  request: ActionRequest,
15
- eventName: string,
15
+ eventAction: string,
16
16
  args?: Record<string, any>
17
17
  ) => Promise<void>
18
18
  getKnowledgeBaseResponse?: (
19
19
  request: ActionRequest
20
20
  ) => Promise<KnowledgeBaseResponse>
21
+ smartIntentsConfig?: { numSmartIntentsToUse: number }
21
22
  }
22
23
 
23
24
  export interface FlowBuilderApiOptions {
@@ -46,6 +47,17 @@ export interface KnowledgeBaseResponse {
46
47
  }[]
47
48
  }
48
49
 
50
+ export interface SmartIntentResponse {
51
+ data: {
52
+ smart_intent_title: string
53
+ text: string
54
+ smart_intents_used: {
55
+ title: string
56
+ description: string
57
+ }[]
58
+ }
59
+ }
60
+
49
61
  export interface PayloadParamsBase {
50
62
  followUpContentID?: string
51
63
  }
@@ -7,24 +7,30 @@ import {
7
7
  HtSmartIntentNode,
8
8
  } from '../content-fields/hubtype-fields'
9
9
  import { getIntentNodeByInput } from './intent'
10
- import { getKeywordNodeByInput } from './keyword'
11
- import { getSmartIntentNodeByInput } from './smart-intent'
10
+ import { KeywordMatcher } from './keyword'
11
+ import { SmartIntentsApi, SmartIntentsInferenceConfig } from './smart-intent'
12
12
 
13
13
  export async function getNodeByUserInput(
14
14
  cmsApi: FlowBuilderApi,
15
15
  locale: string,
16
- request: ActionRequest
16
+ request: ActionRequest,
17
+ smartIntentsConfig: SmartIntentsInferenceConfig
17
18
  ): Promise<HtSmartIntentNode | HtIntentNode | HtKeywordNode | undefined> {
18
19
  if (request.input.data) {
19
- const keywordNode = await getKeywordNodeByInput(
20
+ const keywordMatcher = new KeywordMatcher({
20
21
  cmsApi,
21
22
  locale,
22
23
  request,
23
- request.input.data
24
- )
24
+ })
25
+ const keywordNode = await keywordMatcher.getNodeByInput(request.input.data)
25
26
  if (keywordNode) return keywordNode
26
27
 
27
- const smartIntentNode = await getSmartIntentNodeByInput(cmsApi, request)
28
+ const smartIntentsApi = new SmartIntentsApi(
29
+ cmsApi,
30
+ request,
31
+ smartIntentsConfig
32
+ )
33
+ const smartIntentNode = smartIntentsApi.getNodeByInput()
28
34
  if (smartIntentNode) return smartIntentNode
29
35
 
30
36
  const intentNode = await getIntentNodeByInput(cmsApi, locale, request)
@@ -2,7 +2,7 @@ import { ActionRequest } from '@botonic/react'
2
2
 
3
3
  import { FlowBuilderApi } from '../api'
4
4
  import { HtIntentNode } from '../content-fields/hubtype-fields'
5
- import { EventName, trackEvent } from '../tracking'
5
+ import { EventAction, trackEvent } from '../tracking'
6
6
 
7
7
  export async function getIntentNodeByInput(
8
8
  cmsApi: FlowBuilderApi,
@@ -10,24 +10,31 @@ export async function getIntentNodeByInput(
10
10
  request: ActionRequest
11
11
  ): Promise<HtIntentNode | undefined> {
12
12
  const intentNode = cmsApi.getIntentNode(request.input, locale)
13
- const eventArgs = {
14
- intent: request.input.intent as string,
15
- confidence: request.input.confidence as number,
16
- confidence_successful: true,
17
- }
13
+
18
14
  if (request.input.confidence && request.input.intent && intentNode) {
15
+ await trackIntentEvent(request, intentNode)
16
+
19
17
  if (isIntentValid(intentNode, request, cmsApi)) {
20
- await trackEvent(request, EventName.botAiModel, eventArgs)
21
18
  return intentNode
22
19
  }
23
-
24
- eventArgs.confidence_successful = false
25
- await trackEvent(request, EventName.botAiModel, eventArgs)
26
20
  }
27
21
 
28
22
  return undefined
29
23
  }
30
24
 
25
+ async function trackIntentEvent(
26
+ request: ActionRequest,
27
+ intentNode: HtIntentNode
28
+ ) {
29
+ const eventArgs = {
30
+ nluIntentLabel: request.input.intent,
31
+ nluIntentConfidence: request.input.confidence,
32
+ nluIntentThreshold: intentNode?.content.confidence,
33
+ nluIntentMessageId: request.input.message_id,
34
+ }
35
+ await trackEvent(request, EventAction.intent, eventArgs)
36
+ }
37
+
31
38
  function isIntentValid(
32
39
  intentNode: HtIntentNode,
33
40
  request: ActionRequest,
@@ -1,22 +1,101 @@
1
1
  import { ActionRequest } from '@botonic/react'
2
2
 
3
3
  import { FlowBuilderApi } from '../api'
4
+ import { REG_EXP_PATTERN } from '../constants'
4
5
  import { HtKeywordNode } from '../content-fields/hubtype-fields'
5
- import { EventName, trackEvent } from '../tracking'
6
-
7
- export async function getKeywordNodeByInput(
8
- cmsApi: FlowBuilderApi,
9
- locale: string,
10
- request: ActionRequest,
11
- userInput: string
12
- ): Promise<HtKeywordNode | undefined> {
13
- const keywordNode = cmsApi.getNodeByKeyword(userInput, locale)
14
- if (!keywordNode) {
6
+ import { EventAction, trackEvent } from '../tracking'
7
+
8
+ interface KeywordProps {
9
+ cmsApi: FlowBuilderApi
10
+ locale: string
11
+ request: ActionRequest
12
+ }
13
+ export class KeywordMatcher {
14
+ public cmsApi: FlowBuilderApi
15
+ public locale: string
16
+ public request: ActionRequest
17
+ public isRegExp: boolean
18
+ public matchedKeyword?: string
19
+ public keywordNodeId?: string
20
+
21
+ constructor({ cmsApi, locale, request }: KeywordProps) {
22
+ this.cmsApi = cmsApi
23
+ this.locale = locale
24
+ this.request = request
25
+ this.isRegExp = false
26
+ }
27
+
28
+ async getNodeByInput(userInput: string): Promise<HtKeywordNode | undefined> {
29
+ const keywordNodes = this.cmsApi.getKeywordNodes()
30
+ const keywordNode = this.getNodeByKeyword(userInput, keywordNodes)
31
+ if (!keywordNode) {
32
+ return undefined
33
+ }
34
+ this.trackKeywordEvent()
35
+ return keywordNode
36
+ }
37
+
38
+ private getNodeByKeyword(
39
+ userInput: string,
40
+ keywordNodes: HtKeywordNode[]
41
+ ): HtKeywordNode | undefined {
42
+ const matchedKeywordNodes = keywordNodes.filter(node =>
43
+ this.matchKeywords(userInput, node)
44
+ )
45
+
46
+ if (matchedKeywordNodes.length > 0 && matchedKeywordNodes[0].target) {
47
+ return matchedKeywordNodes[0]
48
+ }
49
+
15
50
  return undefined
16
51
  }
17
- const eventArgs = {
18
- confidence_successful: true,
52
+
53
+ private matchKeywords(userInput: string, node: HtKeywordNode): boolean {
54
+ const result = node.content.keywords.find(keywords => {
55
+ if (keywords.locale === this.locale) {
56
+ this.keywordNodeId = node.id
57
+ return this.inputMatchesAnyKeyword(userInput, keywords.values)
58
+ }
59
+
60
+ return false
61
+ })
62
+
63
+ return Boolean(result)
64
+ }
65
+
66
+ private inputMatchesAnyKeyword(
67
+ userInput: string,
68
+ keywords: string[]
69
+ ): boolean {
70
+ return keywords.some(keyword => {
71
+ const regExpMatchArray = keyword.match(REG_EXP_PATTERN)
72
+
73
+ if (regExpMatchArray) {
74
+ const keywordAsRegExp = this.resolveKeywordAsRegExp(regExpMatchArray)
75
+ const match = userInput.match(keywordAsRegExp)
76
+ this.isRegExp = true
77
+ this.matchedKeyword = match ? match[0] : undefined
78
+ } else {
79
+ this.isRegExp = false
80
+ this.matchedKeyword = userInput.includes(keyword) ? keyword : undefined
81
+ }
82
+
83
+ return this.matchedKeyword !== undefined
84
+ })
85
+ }
86
+
87
+ private resolveKeywordAsRegExp(regExpMatchArray: RegExpMatchArray): RegExp {
88
+ const [, pattern, flags] = regExpMatchArray
89
+ return new RegExp(pattern, flags)
90
+ }
91
+
92
+ private async trackKeywordEvent() {
93
+ const eventArgs = {
94
+ nluKeywordId: this.keywordNodeId,
95
+ nluKeywordName: this.matchedKeyword,
96
+ nluKeywordIsRegex: this.isRegExp,
97
+ nluKeywordMessageId: this.request.input.message_id,
98
+ }
99
+ await trackEvent(this.request, EventAction.keyword, eventArgs)
19
100
  }
20
- await trackEvent(request, EventName.botKeywordsModel, eventArgs)
21
- return keywordNode
22
101
  }
@@ -3,45 +3,72 @@ import axios from 'axios'
3
3
 
4
4
  import { FlowBuilderApi } from '../api'
5
5
  import { HtSmartIntentNode } from '../content-fields/hubtype-fields/smart-intent'
6
+ import { EventAction, trackEvent } from '../tracking'
7
+ import { SmartIntentResponse } from '../types'
6
8
 
7
- export async function getSmartIntentNodeByInput(
8
- cmsApi: FlowBuilderApi,
9
- request: ActionRequest
10
- ): Promise<HtSmartIntentNode | undefined> {
11
- const smartIntentNodes = cmsApi.getSmartIntentNodes()
12
- if (smartIntentNodes.length === 0) {
9
+ export interface SmartIntentsInferenceParams {
10
+ bot_id: string
11
+ text: string
12
+ num_smart_intents_to_use?: number
13
+ use_latest: boolean
14
+ }
15
+
16
+ export interface SmartIntentsInferenceConfig {
17
+ useLatest: boolean
18
+ numSmartIntentsToUse?: number
19
+ }
20
+
21
+ export class SmartIntentsApi {
22
+ constructor(
23
+ public cmsApi: FlowBuilderApi,
24
+ public currentRequest: ActionRequest,
25
+ public smartIntentsConfig: SmartIntentsInferenceConfig
26
+ ) {}
27
+
28
+ async getNodeByInput(): Promise<HtSmartIntentNode | undefined> {
29
+ if (!this.currentRequest.input.data) return undefined
30
+ const smartIntentNodes = this.cmsApi.getSmartIntentNodes()
31
+ if (!smartIntentNodes.length) return undefined
32
+
33
+ const params = {
34
+ bot_id: this.currentRequest.session.bot.id,
35
+ text: this.currentRequest.input.data,
36
+ num_smart_intents_to_use: this.smartIntentsConfig.numSmartIntentsToUse,
37
+ use_latest: this.smartIntentsConfig.useLatest,
38
+ }
39
+
40
+ try {
41
+ const response = await this.getInference(params)
42
+ const smartIntentNode = smartIntentNodes.find(
43
+ smartIntentNode =>
44
+ smartIntentNode.content.title === response.data.smart_intent_title
45
+ )
46
+ if (smartIntentNode) {
47
+ trackEvent(this.currentRequest, EventAction.intentSmart, {
48
+ nluIntentSmartTitle: response.data.smart_intent_title,
49
+ nluIntentSmartNumUsed: response.data.smart_intents_used,
50
+ nluIntentSmartMessageId: this.currentRequest.input.message_id,
51
+ })
52
+ return smartIntentNode
53
+ }
54
+ } catch (e) {
55
+ console.error(e)
56
+ }
13
57
  return undefined
14
58
  }
15
59
 
16
- const intentsInferenceParams = smartIntentNodes.map(smartIntentNode => {
17
- return {
18
- name: smartIntentNode.content.title,
19
- definition: smartIntentNode.content.description,
20
- }
21
- })
22
- intentsInferenceParams.push({
23
- name: 'Other',
24
- definition: 'The text does not belong to any other intent.',
25
- })
26
-
27
- try {
28
- const response = await axios({
60
+ private async getInference(
61
+ inferenceParams: SmartIntentsInferenceParams
62
+ ): Promise<SmartIntentResponse> {
63
+ return await axios({
29
64
  method: 'POST',
30
- url: `${process.env.HUBTYPE_API_URL}/external/v1/ai/smart_intents/inference/`,
65
+ url: `${process.env.HUBTYPE_API_URL}/external/v2/ai/smart_intents/inference/`,
31
66
  headers: {
32
- Authorization: `Bearer ${request.session._access_token}`,
67
+ Authorization: `Bearer ${this.currentRequest.session._access_token}`,
33
68
  'Content-Type': 'application/json',
34
69
  },
35
- data: { text: request.input.data, intents: intentsInferenceParams },
70
+ data: inferenceParams,
36
71
  timeout: 10000,
37
72
  })
38
- console.log({ response })
39
- return smartIntentNodes.find(
40
- smartIntentNode =>
41
- smartIntentNode.content.title === response.data.intent_name
42
- )
43
- } catch (e) {
44
- console.error(e)
45
- return undefined
46
73
  }
47
74
  }