@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
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 {
@@ -38,14 +39,29 @@ export enum FlowBuilderJSONVersion {
38
39
  }
39
40
 
40
41
  export interface KnowledgeBaseResponse {
42
+ inferenceId: string
43
+ question: string
41
44
  answer: string
42
45
  hasKnowledge: boolean
46
+ isFaithuful: boolean
43
47
  sources: {
48
+ knowledgeBaseId: string
44
49
  knowledgeSourceId: string
45
- page?: number
50
+ knowledgeChunkId: string
46
51
  }[]
47
52
  }
48
53
 
54
+ export interface SmartIntentResponse {
55
+ data: {
56
+ smart_intent_title: string
57
+ text: string
58
+ smart_intents_used: {
59
+ title: string
60
+ description: string
61
+ }[]
62
+ }
63
+ }
64
+
49
65
  export interface PayloadParamsBase {
50
66
  followUpContentID?: string
51
67
  }
@@ -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 = await 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,97 @@
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) {
15
- return undefined
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
16
36
  }
17
- const eventArgs = {
18
- confidence_successful: true,
37
+
38
+ private getNodeByKeyword(
39
+ userInput: string,
40
+ keywordNodes: HtKeywordNode[]
41
+ ): HtKeywordNode | undefined {
42
+ const matchedKeywordNode = keywordNodes.find(node =>
43
+ this.matchKeywords(userInput, node)
44
+ )
45
+
46
+ return matchedKeywordNode?.target ? matchedKeywordNode : undefined
47
+ }
48
+
49
+ private matchKeywords(userInput: string, node: HtKeywordNode): boolean {
50
+ const result = node.content.keywords.find(keywords => {
51
+ if (keywords.locale === this.locale) {
52
+ this.keywordNodeId = node.id
53
+ return this.inputMatchesAnyKeyword(userInput, keywords.values)
54
+ }
55
+
56
+ return false
57
+ })
58
+
59
+ return Boolean(result)
60
+ }
61
+
62
+ private inputMatchesAnyKeyword(
63
+ userInput: string,
64
+ keywords: string[]
65
+ ): boolean {
66
+ return keywords.some(keyword => {
67
+ const regExpMatchArray = keyword.match(REG_EXP_PATTERN)
68
+
69
+ if (regExpMatchArray) {
70
+ const keywordAsRegExp = this.resolveKeywordAsRegExp(regExpMatchArray)
71
+ const match = userInput.match(keywordAsRegExp)
72
+ this.isRegExp = true
73
+ this.matchedKeyword = match ? match[0] : undefined
74
+ } else {
75
+ this.isRegExp = false
76
+ this.matchedKeyword = userInput.includes(keyword) ? keyword : undefined
77
+ }
78
+
79
+ return this.matchedKeyword !== undefined
80
+ })
81
+ }
82
+
83
+ private resolveKeywordAsRegExp(regExpMatchArray: RegExpMatchArray): RegExp {
84
+ const [, pattern, flags] = regExpMatchArray
85
+ return new RegExp(pattern, flags)
86
+ }
87
+
88
+ private async trackKeywordEvent() {
89
+ const eventArgs = {
90
+ nluKeywordId: this.keywordNodeId,
91
+ nluKeywordName: this.matchedKeyword,
92
+ nluKeywordIsRegex: this.isRegExp,
93
+ nluKeywordMessageId: this.request.input.message_id,
94
+ }
95
+ await trackEvent(this.request, EventAction.Keyword, eventArgs)
19
96
  }
20
- await trackEvent(request, EventName.botKeywordsModel, eventArgs)
21
- return keywordNode
22
97
  }
@@ -3,44 +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.length,
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
- return smartIntentNodes.find(
39
- smartIntentNode =>
40
- smartIntentNode.content.title === response.data.intent_name
41
- )
42
- } catch (e) {
43
- console.error(e)
44
- return undefined
45
73
  }
46
74
  }