@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.
- package/lib/cjs/action/index.js +39 -19
- package/lib/cjs/action/index.js.map +1 -1
- package/lib/cjs/action/knowledge-bases.js +14 -4
- package/lib/cjs/action/knowledge-bases.js.map +1 -1
- package/lib/cjs/api.d.ts +4 -6
- package/lib/cjs/api.js +7 -33
- package/lib/cjs/api.js.map +1 -1
- package/lib/cjs/constants.d.ts +1 -0
- package/lib/cjs/constants.js +2 -1
- package/lib/cjs/constants.js.map +1 -1
- package/lib/cjs/content-fields/flow-button.d.ts +0 -1
- package/lib/cjs/content-fields/flow-button.js +0 -10
- package/lib/cjs/content-fields/flow-button.js.map +1 -1
- package/lib/cjs/content-fields/flow-handoff.js +7 -19
- package/lib/cjs/content-fields/flow-handoff.js.map +1 -1
- package/lib/cjs/content-fields/hubtype-fields/button.d.ts +1 -2
- package/lib/cjs/content-fields/index.d.ts +1 -0
- package/lib/cjs/content-fields/index.js +3 -1
- package/lib/cjs/content-fields/index.js.map +1 -1
- package/lib/cjs/functions/conditional-queue-status.d.ts +7 -1
- package/lib/cjs/functions/conditional-queue-status.js +34 -12
- package/lib/cjs/functions/conditional-queue-status.js.map +1 -1
- package/lib/cjs/index.d.ts +7 -1
- package/lib/cjs/index.js +29 -6
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/tracking.d.ts +18 -14
- package/lib/cjs/tracking.js +27 -20
- package/lib/cjs/tracking.js.map +1 -1
- package/lib/cjs/types.d.ts +14 -1
- package/lib/cjs/types.js.map +1 -1
- package/lib/cjs/user-input/index.d.ts +2 -1
- package/lib/cjs/user-input/index.js +9 -3
- package/lib/cjs/user-input/index.js.map +1 -1
- package/lib/cjs/user-input/intent.js +12 -8
- package/lib/cjs/user-input/intent.js.map +1 -1
- package/lib/cjs/user-input/keyword.d.ts +21 -1
- package/lib/cjs/user-input/keyword.js +68 -13
- package/lib/cjs/user-input/keyword.js.map +1 -1
- package/lib/cjs/user-input/smart-intent.d.ts +18 -1
- package/lib/cjs/user-input/smart-intent.js +46 -29
- package/lib/cjs/user-input/smart-intent.js.map +1 -1
- package/lib/esm/action/index.js +39 -19
- package/lib/esm/action/index.js.map +1 -1
- package/lib/esm/action/knowledge-bases.js +15 -5
- package/lib/esm/action/knowledge-bases.js.map +1 -1
- package/lib/esm/api.d.ts +4 -6
- package/lib/esm/api.js +8 -34
- package/lib/esm/api.js.map +1 -1
- package/lib/esm/constants.d.ts +1 -0
- package/lib/esm/constants.js +1 -0
- package/lib/esm/constants.js.map +1 -1
- package/lib/esm/content-fields/flow-button.d.ts +0 -1
- package/lib/esm/content-fields/flow-button.js +0 -10
- package/lib/esm/content-fields/flow-button.js.map +1 -1
- package/lib/esm/content-fields/flow-handoff.js +7 -19
- package/lib/esm/content-fields/flow-handoff.js.map +1 -1
- package/lib/esm/content-fields/hubtype-fields/button.d.ts +1 -2
- package/lib/esm/content-fields/index.d.ts +1 -0
- package/lib/esm/content-fields/index.js +1 -0
- package/lib/esm/content-fields/index.js.map +1 -1
- package/lib/esm/functions/conditional-queue-status.d.ts +7 -1
- package/lib/esm/functions/conditional-queue-status.js +32 -11
- package/lib/esm/functions/conditional-queue-status.js.map +1 -1
- package/lib/esm/index.d.ts +7 -1
- package/lib/esm/index.js +30 -7
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/tracking.d.ts +18 -14
- package/lib/esm/tracking.js +25 -19
- package/lib/esm/tracking.js.map +1 -1
- package/lib/esm/types.d.ts +14 -1
- package/lib/esm/types.js.map +1 -1
- package/lib/esm/user-input/index.d.ts +2 -1
- package/lib/esm/user-input/index.js +11 -5
- package/lib/esm/user-input/index.js.map +1 -1
- package/lib/esm/user-input/intent.js +13 -9
- package/lib/esm/user-input/intent.js.map +1 -1
- package/lib/esm/user-input/keyword.d.ts +21 -1
- package/lib/esm/user-input/keyword.js +67 -12
- package/lib/esm/user-input/keyword.js.map +1 -1
- package/lib/esm/user-input/smart-intent.d.ts +18 -1
- package/lib/esm/user-input/smart-intent.js +44 -27
- package/lib/esm/user-input/smart-intent.js.map +1 -1
- package/package.json +3 -3
- package/src/action/index.tsx +58 -24
- package/src/action/knowledge-bases.ts +16 -5
- package/src/api.ts +12 -55
- package/src/constants.ts +1 -0
- package/src/content-fields/flow-button.tsx +0 -10
- package/src/content-fields/flow-handoff.tsx +8 -28
- package/src/content-fields/hubtype-fields/button.ts +1 -7
- package/src/content-fields/index.ts +1 -0
- package/src/functions/conditional-queue-status.ts +36 -13
- package/src/index.ts +48 -7
- package/src/tracking.ts +25 -14
- package/src/types.ts +13 -1
- package/src/user-input/index.ts +13 -7
- package/src/user-input/intent.ts +17 -10
- package/src/user-input/keyword.ts +93 -14
- package/src/user-input/smart-intent.ts +57 -30
package/src/action/index.tsx
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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,
|
|
32
|
-
|
|
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.
|
|
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 {
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
44
|
-
|
|
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
|
-
|
|
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
|
|
96
|
-
|
|
97
|
-
)
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
13
|
+
await flowBuilderPlugin.trackEvent(request, eventAction, args)
|
|
13
14
|
}
|
|
14
15
|
return
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
}
|