@botonic/plugin-flow-builder 0.26.2-alpha.0 → 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 +38 -18
- 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 +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 +34 -12
- 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 +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 -28
- 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 +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 +32 -11
- 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 +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 -26
- package/lib/esm/user-input/smart-intent.js.map +1 -1
- package/package.json +2 -2
- package/src/action/index.tsx +58 -24
- package/src/action/knowledge-bases.ts +16 -5
- 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 +36 -13
- package/src/index.ts +39 -19
- 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 -29
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
|
|
@@ -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,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 { EventAction, getNodeEventArgs, trackEvent } 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,19 @@ 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 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)
|
|
148
164
|
}
|
|
149
165
|
|
|
150
166
|
async getContentsByNode(
|
|
@@ -260,6 +276,10 @@ export default class BotonicPluginFlowBuilder implements Plugin {
|
|
|
260
276
|
const payloadParams = JSON.parse(payload.split(SEPARATOR)[1] || '{}')
|
|
261
277
|
return payloadParams
|
|
262
278
|
}
|
|
279
|
+
|
|
280
|
+
getFlowName(flowId: string): string {
|
|
281
|
+
return this.cmsApi.getFlowName(flowId)
|
|
282
|
+
}
|
|
263
283
|
}
|
|
264
284
|
|
|
265
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
|
}
|
package/src/types.ts
CHANGED
|
@@ -12,12 +12,13 @@ export interface BotonicPluginFlowBuilderOptions {
|
|
|
12
12
|
getAccessToken: () => string
|
|
13
13
|
trackEvent?: (
|
|
14
14
|
request: ActionRequest,
|
|
15
|
-
|
|
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
|
}
|
package/src/user-input/index.ts
CHANGED
|
@@ -7,24 +7,30 @@ import {
|
|
|
7
7
|
HtSmartIntentNode,
|
|
8
8
|
} from '../content-fields/hubtype-fields'
|
|
9
9
|
import { getIntentNodeByInput } from './intent'
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
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
|
|
20
|
+
const keywordMatcher = new KeywordMatcher({
|
|
20
21
|
cmsApi,
|
|
21
22
|
locale,
|
|
22
23
|
request,
|
|
23
|
-
|
|
24
|
-
)
|
|
24
|
+
})
|
|
25
|
+
const keywordNode = await keywordMatcher.getNodeByInput(request.input.data)
|
|
25
26
|
if (keywordNode) return keywordNode
|
|
26
27
|
|
|
27
|
-
const
|
|
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)
|
package/src/user-input/intent.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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,
|