@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.
- package/lib/cjs/action/index.js +41 -19
- package/lib/cjs/action/index.js.map +1 -1
- package/lib/cjs/action/knowledge-bases.js +32 -7
- package/lib/cjs/action/knowledge-bases.js.map +1 -1
- package/lib/cjs/api.d.ts +2 -4
- package/lib/cjs/api.js +7 -32
- package/lib/cjs/api.js.map +1 -1
- package/lib/cjs/constants.d.ts +1 -1
- package/lib/cjs/constants.js +2 -2
- package/lib/cjs/constants.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/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 +24 -11
- package/lib/cjs/functions/conditional-queue-status.js.map +1 -1
- package/lib/cjs/index.d.ts +6 -3
- package/lib/cjs/index.js +22 -13
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/tracking.d.ts +27 -16
- package/lib/cjs/tracking.js +45 -20
- package/lib/cjs/tracking.js.map +1 -1
- package/lib/cjs/types.d.ts +19 -2
- 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 +66 -14
- 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 -28
- package/lib/cjs/user-input/smart-intent.js.map +1 -1
- package/lib/esm/action/index.js +42 -20
- package/lib/esm/action/index.js.map +1 -1
- package/lib/esm/action/knowledge-bases.js +33 -8
- package/lib/esm/action/knowledge-bases.js.map +1 -1
- package/lib/esm/api.d.ts +2 -4
- package/lib/esm/api.js +8 -33
- package/lib/esm/api.js.map +1 -1
- package/lib/esm/constants.d.ts +1 -1
- package/lib/esm/constants.js +1 -1
- package/lib/esm/constants.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/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 +22 -10
- package/lib/esm/functions/conditional-queue-status.js.map +1 -1
- package/lib/esm/index.d.ts +6 -3
- package/lib/esm/index.js +23 -14
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/tracking.d.ts +27 -16
- package/lib/esm/tracking.js +42 -19
- package/lib/esm/tracking.js.map +1 -1
- package/lib/esm/types.d.ts +19 -2
- 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 +65 -13
- 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 -26
- package/lib/esm/user-input/smart-intent.js.map +1 -1
- package/package.json +2 -2
- package/src/action/index.tsx +71 -25
- package/src/action/knowledge-bases.ts +44 -10
- package/src/api.ts +11 -57
- package/src/constants.ts +1 -1
- package/src/content-fields/flow-handoff.tsx +8 -28
- package/src/content-fields/index.ts +1 -0
- package/src/functions/conditional-queue-status.ts +28 -13
- package/src/index.ts +36 -19
- package/src/tracking.ts +53 -17
- package/src/types.ts +18 -2
- package/src/user-input/index.ts +13 -7
- package/src/user-input/intent.ts +17 -10
- package/src/user-input/keyword.ts +90 -15
- package/src/user-input/smart-intent.ts +57 -29
package/src/action/index.tsx
CHANGED
|
@@ -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
|
|
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
|
|
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)
|
|
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
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
94
|
+
const contents = await flowBuilderPlugin.getContentsByNode(
|
|
95
|
+
targetNode,
|
|
96
|
+
resolvedLocale
|
|
97
|
+
)
|
|
98
|
+
await trackFlowContent(request, contents)
|
|
99
|
+
|
|
100
|
+
return contents
|
|
76
101
|
}
|
|
77
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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 `${
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
102
|
-
|
|
103
|
-
)
|
|
104
|
-
|
|
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
|
|
109
|
-
return payload
|
|
117
|
+
private removeSourceSufix(payload: string): string {
|
|
118
|
+
return payload.split(SOURCE_INFO_SEPARATOR)[0]
|
|
110
119
|
}
|
|
111
120
|
|
|
112
|
-
private
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
28
|
+
await flowBuilderPlugin.trackEvent(request, eventAction, args)
|
|
13
29
|
}
|
|
14
30
|
return
|
|
15
31
|
}
|
|
16
32
|
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
}
|