@botonic/plugin-flow-builder 0.56.1 → 0.57.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/api.d.ts +2 -2
- package/lib/cjs/api.js +17 -17
- package/lib/cjs/api.js.map +1 -1
- package/lib/cjs/bot-context-manager.d.ts +17 -0
- package/lib/cjs/bot-context-manager.js +93 -0
- package/lib/cjs/bot-context-manager.js.map +1 -0
- package/lib/cjs/constants.d.ts +2 -1
- package/lib/cjs/constants.js +3 -2
- package/lib/cjs/constants.js.map +1 -1
- package/lib/cjs/content-fields/contact-reasons-conditional-matcher.d.ts +4 -0
- package/lib/cjs/content-fields/contact-reasons-conditional-matcher.js +12 -0
- package/lib/cjs/content-fields/contact-reasons-conditional-matcher.js.map +1 -0
- package/lib/cjs/content-fields/flow-contact-reasons-conditional.d.ts +14 -0
- package/lib/cjs/content-fields/flow-contact-reasons-conditional.js +65 -0
- package/lib/cjs/content-fields/flow-contact-reasons-conditional.js.map +1 -0
- package/lib/cjs/content-fields/flow-handoff.d.ts +7 -1
- package/lib/cjs/content-fields/flow-handoff.js +39 -3
- package/lib/cjs/content-fields/flow-handoff.js.map +1 -1
- package/lib/cjs/content-fields/hubtype-fields/contact-reasons-conditional.d.ts +16 -0
- package/lib/cjs/content-fields/hubtype-fields/contact-reasons-conditional.js +3 -0
- package/lib/cjs/content-fields/hubtype-fields/contact-reasons-conditional.js.map +1 -0
- package/lib/cjs/content-fields/hubtype-fields/handoff.d.ts +11 -1
- package/lib/cjs/content-fields/hubtype-fields/handoff.js +7 -0
- package/lib/cjs/content-fields/hubtype-fields/handoff.js.map +1 -1
- package/lib/cjs/content-fields/hubtype-fields/node-types.d.ts +2 -1
- package/lib/cjs/content-fields/hubtype-fields/node-types.js +1 -0
- package/lib/cjs/content-fields/hubtype-fields/node-types.js.map +1 -1
- package/lib/cjs/content-fields/hubtype-fields/nodes.d.ts +2 -1
- package/lib/cjs/content-fields/index.d.ts +3 -2
- package/lib/cjs/content-fields/index.js +3 -1
- package/lib/cjs/content-fields/index.js.map +1 -1
- package/lib/cjs/flow-factory.js +2 -0
- package/lib/cjs/flow-factory.js.map +1 -1
- package/lib/cjs/index.d.ts +5 -9
- package/lib/cjs/index.js +16 -67
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/types.d.ts +2 -2
- package/lib/cjs/types.js.map +1 -1
- package/lib/esm/api.d.ts +2 -2
- package/lib/esm/api.js +17 -17
- package/lib/esm/api.js.map +1 -1
- package/lib/esm/bot-context-manager.d.ts +17 -0
- package/lib/esm/bot-context-manager.js +93 -0
- package/lib/esm/bot-context-manager.js.map +1 -0
- package/lib/esm/constants.d.ts +2 -1
- package/lib/esm/constants.js +3 -2
- package/lib/esm/constants.js.map +1 -1
- package/lib/esm/content-fields/contact-reasons-conditional-matcher.d.ts +4 -0
- package/lib/esm/content-fields/contact-reasons-conditional-matcher.js +12 -0
- package/lib/esm/content-fields/contact-reasons-conditional-matcher.js.map +1 -0
- package/lib/esm/content-fields/flow-contact-reasons-conditional.d.ts +14 -0
- package/lib/esm/content-fields/flow-contact-reasons-conditional.js +65 -0
- package/lib/esm/content-fields/flow-contact-reasons-conditional.js.map +1 -0
- package/lib/esm/content-fields/flow-handoff.d.ts +7 -1
- package/lib/esm/content-fields/flow-handoff.js +39 -3
- package/lib/esm/content-fields/flow-handoff.js.map +1 -1
- package/lib/esm/content-fields/hubtype-fields/contact-reasons-conditional.d.ts +16 -0
- package/lib/esm/content-fields/hubtype-fields/contact-reasons-conditional.js +3 -0
- package/lib/esm/content-fields/hubtype-fields/contact-reasons-conditional.js.map +1 -0
- package/lib/esm/content-fields/hubtype-fields/handoff.d.ts +11 -1
- package/lib/esm/content-fields/hubtype-fields/handoff.js +7 -0
- package/lib/esm/content-fields/hubtype-fields/handoff.js.map +1 -1
- package/lib/esm/content-fields/hubtype-fields/node-types.d.ts +2 -1
- package/lib/esm/content-fields/hubtype-fields/node-types.js +1 -0
- package/lib/esm/content-fields/hubtype-fields/node-types.js.map +1 -1
- package/lib/esm/content-fields/hubtype-fields/nodes.d.ts +2 -1
- package/lib/esm/content-fields/index.d.ts +3 -2
- package/lib/esm/content-fields/index.js +3 -1
- package/lib/esm/content-fields/index.js.map +1 -1
- package/lib/esm/flow-factory.js +2 -0
- package/lib/esm/flow-factory.js.map +1 -1
- package/lib/esm/index.d.ts +5 -9
- package/lib/esm/index.js +16 -67
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/types.d.ts +2 -2
- package/lib/esm/types.js.map +1 -1
- package/package.json +2 -2
- package/src/api.ts +19 -19
- package/src/bot-context-manager.ts +153 -0
- package/src/constants.ts +2 -1
- package/src/content-fields/contact-reasons-conditional-matcher.ts +24 -0
- package/src/content-fields/flow-contact-reasons-conditional.tsx +90 -0
- package/src/content-fields/flow-handoff.tsx +54 -7
- package/src/content-fields/hubtype-fields/contact-reasons-conditional.ts +18 -0
- package/src/content-fields/hubtype-fields/handoff.ts +17 -1
- package/src/content-fields/hubtype-fields/node-types.ts +1 -0
- package/src/content-fields/hubtype-fields/nodes.ts +2 -0
- package/src/content-fields/index.ts +3 -0
- package/src/flow-factory.ts +7 -0
- package/src/index.ts +26 -117
- package/src/types.ts +1 -2
package/lib/esm/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AiAgentArgs, BotContext, GuardrailRule, InferenceResponse,
|
|
1
|
+
import type { AiAgentArgs, BotContext, GuardrailRule, InferenceResponse, ResolvedPlugins } from '@botonic/core';
|
|
2
2
|
import type { FlowContent } from './content-fields';
|
|
3
3
|
import type { HtFlowBuilderData, HtRatingButton } from './content-fields/hubtype-fields';
|
|
4
4
|
import type { FlowBuilderContentMessage } from './structured-output/flow-builder-content';
|
|
@@ -31,7 +31,7 @@ export interface FlowBuilderApiOptions {
|
|
|
31
31
|
flowUrl: string;
|
|
32
32
|
flow?: HtFlowBuilderData;
|
|
33
33
|
accessToken: string;
|
|
34
|
-
|
|
34
|
+
botContext: BotContext;
|
|
35
35
|
}
|
|
36
36
|
export declare enum ProcessEnvNodeEnvs {
|
|
37
37
|
PRODUCTION = "production",
|
package/lib/esm/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":";;;AAuEA,IAAY,kBAGX;AAHD,WAAY,kBAAkB;IAC5B,+CAAyB,CAAA;IACzB,iDAA2B,CAAA;AAC7B,CAAC,EAHW,kBAAkB,kCAAlB,kBAAkB,QAG7B;AAED,IAAY,sBAGX;AAHD,WAAY,sBAAsB;IAChC,yCAAe,CAAA;IACf,2CAAiB,CAAA;AACnB,CAAC,EAHW,sBAAsB,sCAAtB,sBAAsB,QAGjC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botonic/plugin-flow-builder",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.57.0-alpha.1",
|
|
4
4
|
"main": "./lib/cjs/index.js",
|
|
5
5
|
"module": "./lib/esm/index.js",
|
|
6
6
|
"description": "Use Flow Builder to show your contents",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"format": "biome format --write src/ tests/"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@botonic/react": "^0.
|
|
18
|
+
"@botonic/react": "^0.57.0-alpha.1",
|
|
19
19
|
"axios": "^1.18.1",
|
|
20
20
|
"uuid": "^10.0.0"
|
|
21
21
|
},
|
package/src/api.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { BotContext } from '@botonic/core'
|
|
2
2
|
import axios from 'axios'
|
|
3
3
|
|
|
4
4
|
import {
|
|
@@ -36,7 +36,7 @@ export class FlowBuilderApi {
|
|
|
36
36
|
url: string
|
|
37
37
|
flowUrl: string
|
|
38
38
|
flow: HtFlowBuilderData
|
|
39
|
-
|
|
39
|
+
botContext: BotContext
|
|
40
40
|
|
|
41
41
|
private constructor() {}
|
|
42
42
|
|
|
@@ -44,12 +44,12 @@ export class FlowBuilderApi {
|
|
|
44
44
|
const newApi = new FlowBuilderApi()
|
|
45
45
|
|
|
46
46
|
newApi.url = options.url
|
|
47
|
-
newApi.
|
|
47
|
+
newApi.botContext = options.botContext
|
|
48
48
|
// TODO: Refactor later to combine logic from `FlowBuilderApi.create`, `resolveFlowUrl` and `getAccessToken` to be in one place
|
|
49
49
|
if (process.env.NODE_ENV === ProcessEnvNodeEnvs.DEVELOPMENT) {
|
|
50
50
|
await newApi.updateSessionWithUserInfo(options.accessToken)
|
|
51
51
|
}
|
|
52
|
-
const updatedRequest = newApi.
|
|
52
|
+
const updatedRequest = newApi.botContext
|
|
53
53
|
newApi.flowUrl = options.flowUrl.replace(
|
|
54
54
|
'{bot_id}',
|
|
55
55
|
updatedRequest.session.bot.id
|
|
@@ -70,8 +70,8 @@ export class FlowBuilderApi {
|
|
|
70
70
|
const response = await axios.get(url, {
|
|
71
71
|
headers: { Authorization: `Bearer ${token}` },
|
|
72
72
|
})
|
|
73
|
-
this.
|
|
74
|
-
this.
|
|
73
|
+
this.botContext.session.organization_id = response.data.organization_id
|
|
74
|
+
this.botContext.session.bot.id = response.data.bot_id
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
getNodeByFlowId(id: string): HtNodeWithContent {
|
|
@@ -265,23 +265,23 @@ export class FlowBuilderApi {
|
|
|
265
265
|
}
|
|
266
266
|
|
|
267
267
|
getCaptureUserInputId(): string | undefined {
|
|
268
|
-
return this.
|
|
268
|
+
return this.botContext.session.capture_user_input?.node_id
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
setCaptureUserInputId(id: string): void {
|
|
272
|
-
if (this.
|
|
273
|
-
this.
|
|
272
|
+
if (this.botContext.session.capture_user_input) {
|
|
273
|
+
this.botContext.session.capture_user_input.node_id = id
|
|
274
274
|
}
|
|
275
|
-
this.
|
|
275
|
+
this.botContext.session.capture_user_input = { node_id: id }
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
removeCaptureUserInputId(): void {
|
|
279
|
-
this.
|
|
279
|
+
this.botContext.session.capture_user_input = undefined
|
|
280
280
|
}
|
|
281
281
|
|
|
282
282
|
setUserExtraDataVariable(key: string, value: string): void {
|
|
283
|
-
if (this.
|
|
284
|
-
this.
|
|
283
|
+
if (this.botContext.session.user.extra_data) {
|
|
284
|
+
this.botContext.session.user.extra_data[key] = value
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
287
|
|
|
@@ -294,18 +294,18 @@ export class FlowBuilderApi {
|
|
|
294
294
|
}
|
|
295
295
|
|
|
296
296
|
getSessionWhatsappRequestContactId(): string | undefined {
|
|
297
|
-
return this.
|
|
297
|
+
return this.botContext.session.whatsapp_request_contact?.id
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
setWhatsappRequestContactId(id: string): void {
|
|
301
|
-
if (this.
|
|
302
|
-
this.
|
|
301
|
+
if (this.botContext.session.whatsapp_request_contact) {
|
|
302
|
+
this.botContext.session.whatsapp_request_contact.id = id
|
|
303
303
|
}
|
|
304
|
-
this.
|
|
304
|
+
this.botContext.session.whatsapp_request_contact = { id }
|
|
305
305
|
}
|
|
306
306
|
|
|
307
307
|
removeWhatsappRequestContactId(): void {
|
|
308
|
-
this.
|
|
308
|
+
this.botContext.session.whatsapp_request_contact = undefined
|
|
309
309
|
}
|
|
310
310
|
|
|
311
311
|
getWhatsappRequestContactInfoNode():
|
|
@@ -325,7 +325,7 @@ export class FlowBuilderApi {
|
|
|
325
325
|
const defaultLocaleCode = this.flow.default_locale_code
|
|
326
326
|
|
|
327
327
|
return new FlowLocale(
|
|
328
|
-
this.
|
|
328
|
+
this.botContext,
|
|
329
329
|
flowLocales,
|
|
330
330
|
defaultLocaleCode
|
|
331
331
|
).resolve()
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BotContext,
|
|
3
|
+
INPUT,
|
|
4
|
+
PROVIDER,
|
|
5
|
+
WhatsappInputOrigin,
|
|
6
|
+
} from '@botonic/core'
|
|
7
|
+
|
|
8
|
+
import type { FlowBuilderApi } from './api'
|
|
9
|
+
import {
|
|
10
|
+
EMPTY_PAYLOAD,
|
|
11
|
+
ON_CLOSE_HANDOFF_PAYLOAD,
|
|
12
|
+
SEPARATOR,
|
|
13
|
+
SOURCE_INFO_SEPARATOR,
|
|
14
|
+
} from './constants'
|
|
15
|
+
import { FlowHandoff } from './content-fields'
|
|
16
|
+
import {
|
|
17
|
+
type HtBotActionNode,
|
|
18
|
+
type HtHandoffNode,
|
|
19
|
+
HtNodeWithContentType,
|
|
20
|
+
} from './content-fields/hubtype-fields'
|
|
21
|
+
import { getNextPayloadByUserInput } from './user-input'
|
|
22
|
+
import type { SmartIntentsInferenceConfig } from './user-input/smart-intent'
|
|
23
|
+
import { inputHasTextOrTranscript } from './utils/input'
|
|
24
|
+
|
|
25
|
+
export class BotContextManager {
|
|
26
|
+
constructor(
|
|
27
|
+
private readonly cmsApi: FlowBuilderApi,
|
|
28
|
+
private readonly smartIntentsConfig: SmartIntentsInferenceConfig
|
|
29
|
+
) {}
|
|
30
|
+
|
|
31
|
+
async prepareInputForRoutes(botContext: BotContext): Promise<void> {
|
|
32
|
+
// When AI Agent is executed in Whatsapp, button payloads come as referral and must be converted to text being processed by the agent.
|
|
33
|
+
this.convertWhatsappAiAgentEmptyPayloads(botContext)
|
|
34
|
+
|
|
35
|
+
// If the payload is a push-flow payload, set first interaction to false
|
|
36
|
+
this.updateFirstInteractionOnPushFlowPayload(botContext)
|
|
37
|
+
|
|
38
|
+
this.resolveWhatsappContactRequestPayload(botContext)
|
|
39
|
+
this.resolveOnCloseHandoffPayload(botContext)
|
|
40
|
+
|
|
41
|
+
await this.resolvePayloadFromUserTextInput(botContext)
|
|
42
|
+
|
|
43
|
+
this.cmsApi.removeCaptureUserInputId()
|
|
44
|
+
this.removePayloadSourceSuffix(botContext)
|
|
45
|
+
this.resolveBotActionPayload(botContext)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private async updateFirstInteractionOnPushFlowPayload(
|
|
49
|
+
botContext: BotContext
|
|
50
|
+
): Promise<void> {
|
|
51
|
+
if (this.cmsApi.isPushFlowPayload(botContext.input.payload)) {
|
|
52
|
+
botContext.session.is_first_interaction = false
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private async resolvePayloadFromUserTextInput(
|
|
57
|
+
botContext: BotContext
|
|
58
|
+
): Promise<void> {
|
|
59
|
+
const checkUserTextInput =
|
|
60
|
+
inputHasTextOrTranscript(botContext.input) && !botContext.input.payload
|
|
61
|
+
|
|
62
|
+
if (!checkUserTextInput) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const resolvedLocale = this.cmsApi.getResolvedLocale()
|
|
67
|
+
const nextPayload = await getNextPayloadByUserInput(
|
|
68
|
+
this.cmsApi,
|
|
69
|
+
resolvedLocale,
|
|
70
|
+
botContext,
|
|
71
|
+
this.smartIntentsConfig
|
|
72
|
+
)
|
|
73
|
+
botContext.input.payload = nextPayload
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private resolveWhatsappContactRequestPayload(botContext: BotContext): void {
|
|
77
|
+
if (
|
|
78
|
+
botContext.input.type !== INPUT.CONTACT ||
|
|
79
|
+
botContext.input.origin !== WhatsappInputOrigin.ContactRequest
|
|
80
|
+
) {
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const whatsappRequestContactInfoNode =
|
|
85
|
+
this.cmsApi.getWhatsappRequestContactInfoNode()
|
|
86
|
+
|
|
87
|
+
if (whatsappRequestContactInfoNode) {
|
|
88
|
+
botContext.input.payload = this.cmsApi.getPayload(
|
|
89
|
+
whatsappRequestContactInfoNode.content.button.target
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.cmsApi.removeWhatsappRequestContactId()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private resolveOnCloseHandoffPayload(botContext: BotContext): void {
|
|
97
|
+
if (botContext.input.payload?.startsWith(ON_CLOSE_HANDOFF_PAYLOAD)) {
|
|
98
|
+
const handoffNodeId = botContext.input.payload.split(SEPARATOR)[1]
|
|
99
|
+
const handoffNode = this.cmsApi.getNodeById<HtHandoffNode>(handoffNodeId)
|
|
100
|
+
if (handoffNode && handoffNode.type === HtNodeWithContentType.HANDOFF) {
|
|
101
|
+
const flowHandoff = FlowHandoff.fromHubtypeCMS(
|
|
102
|
+
handoffNode,
|
|
103
|
+
this.cmsApi.getResolvedLocale(),
|
|
104
|
+
this.cmsApi
|
|
105
|
+
)
|
|
106
|
+
botContext.input.payload = flowHandoff.resolveOnClosePayload(botContext)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private convertWhatsappAiAgentEmptyPayloads(botContext: BotContext): void {
|
|
112
|
+
if (botContext.session.user.provider === PROVIDER.WHATSAPP) {
|
|
113
|
+
const shouldUseReferral =
|
|
114
|
+
botContext.input.referral &&
|
|
115
|
+
botContext.input.payload?.startsWith(EMPTY_PAYLOAD)
|
|
116
|
+
|
|
117
|
+
if (shouldUseReferral) {
|
|
118
|
+
botContext.input.type = INPUT.TEXT
|
|
119
|
+
botContext.input.data = botContext.input.referral
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private removePayloadSourceSuffix(botContext: BotContext): void {
|
|
125
|
+
if (botContext.input.payload) {
|
|
126
|
+
botContext.input.payload = this.removeSourceSuffix(
|
|
127
|
+
botContext.input.payload
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private resolveBotActionPayload(botContext: BotContext): void {
|
|
133
|
+
if (
|
|
134
|
+
botContext.input.payload &&
|
|
135
|
+
this.cmsApi.isBotAction(botContext.input.payload)
|
|
136
|
+
) {
|
|
137
|
+
const cmsBotAction = this.cmsApi.getNodeById<HtBotActionNode>(
|
|
138
|
+
botContext.input.payload
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
botContext.input.payload =
|
|
142
|
+
this.cmsApi.createPayloadWithParams(cmsBotAction)
|
|
143
|
+
|
|
144
|
+
// Re-execute convertWhatsappAiAgentEmptyPayloads function to handle
|
|
145
|
+
// the case that a BotAction has a payload equals to EMPTY_PAYLOAD
|
|
146
|
+
this.convertWhatsappAiAgentEmptyPayloads(botContext)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private removeSourceSuffix(payload: string): string {
|
|
151
|
+
return payload.split(SOURCE_INFO_SEPARATOR)[0]
|
|
152
|
+
}
|
|
153
|
+
}
|
package/src/constants.ts
CHANGED
|
@@ -2,7 +2,6 @@ export const FLOW_BUILDER_API_URL_PROD =
|
|
|
2
2
|
process.env.HUBTYPE_API_URL || 'https://api.hubtype.com'
|
|
3
3
|
export const SEPARATOR = '|'
|
|
4
4
|
export const SOURCE_INFO_SEPARATOR = `${SEPARATOR}source_`
|
|
5
|
-
export const EMPTY_PAYLOAD = 'fb-empty-payload'
|
|
6
5
|
export const VARIABLE_PATTERN_GLOBAL = /{([^{}]+)}/g
|
|
7
6
|
export const ACCESS_TOKEN_VARIABLE_KEY = '_access_token'
|
|
8
7
|
export const REG_EXP_PATTERN = /^\/(.*)\/([gimyus]*)$/
|
|
@@ -10,6 +9,8 @@ export const UUID_REGEXP =
|
|
|
10
9
|
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
11
10
|
|
|
12
11
|
/* ********** PAYLOADS ********** */
|
|
12
|
+
export const EMPTY_PAYLOAD = 'fb-empty-payload'
|
|
13
|
+
export const ON_CLOSE_HANDOFF_PAYLOAD = 'fb-on-close-handoff'
|
|
13
14
|
export const AGENT_RATING_PAYLOAD = 'agent-rating'
|
|
14
15
|
export const PUSH_FLOW_PAYLOAD = 'push-flow'
|
|
15
16
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { HubtypeCaseContactReason } from '@botonic/core'
|
|
2
|
+
|
|
3
|
+
import type { HtContactReasonBranch } from './hubtype-fields/contact-reasons-conditional'
|
|
4
|
+
|
|
5
|
+
export function contactReasonMatchesBranch(
|
|
6
|
+
sessionContactReason: HubtypeCaseContactReason,
|
|
7
|
+
branch: HtContactReasonBranch
|
|
8
|
+
): boolean {
|
|
9
|
+
return (
|
|
10
|
+
sessionContactReason.project_id === branch.project_id &&
|
|
11
|
+
sessionContactReason.name === branch.name
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function findMatchingContactReasonBranch(
|
|
16
|
+
sessionContactReasons: HubtypeCaseContactReason[],
|
|
17
|
+
contactReasonBranches: HtContactReasonBranch[]
|
|
18
|
+
): HtContactReasonBranch | undefined {
|
|
19
|
+
return contactReasonBranches.find(branch =>
|
|
20
|
+
sessionContactReasons.some(sessionContactReason =>
|
|
21
|
+
contactReasonMatchesBranch(sessionContactReason, branch)
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BotContext,
|
|
3
|
+
EventAction,
|
|
4
|
+
type EventConditionalContactReasons,
|
|
5
|
+
} from '@botonic/core'
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
getCommonFlowContentEventArgsForContentId,
|
|
9
|
+
trackEvent,
|
|
10
|
+
} from '../tracking'
|
|
11
|
+
import { findMatchingContactReasonBranch } from './contact-reasons-conditional-matcher'
|
|
12
|
+
import { ContentFieldsBase } from './content-fields-base'
|
|
13
|
+
import type { HtNodeLink } from './hubtype-fields/common'
|
|
14
|
+
import type {
|
|
15
|
+
HtContactReasonBranch,
|
|
16
|
+
HtContactReasonsNode,
|
|
17
|
+
} from './hubtype-fields/contact-reasons-conditional'
|
|
18
|
+
|
|
19
|
+
export class FlowContactReasonsConditional extends ContentFieldsBase {
|
|
20
|
+
public contactReasons: HtContactReasonBranch[] = []
|
|
21
|
+
public conditionalResult: string = ''
|
|
22
|
+
public defaultTarget: HtNodeLink
|
|
23
|
+
|
|
24
|
+
static fromHubtypeCMS(
|
|
25
|
+
component: HtContactReasonsNode,
|
|
26
|
+
botContext: BotContext
|
|
27
|
+
): FlowContactReasonsConditional {
|
|
28
|
+
const newContactReasonsConditional = new FlowContactReasonsConditional(
|
|
29
|
+
component.id
|
|
30
|
+
)
|
|
31
|
+
newContactReasonsConditional.contactReasons =
|
|
32
|
+
component.content.contact_reasons
|
|
33
|
+
newContactReasonsConditional.defaultTarget =
|
|
34
|
+
component.content.default_target
|
|
35
|
+
newContactReasonsConditional.setFollowUp(botContext)
|
|
36
|
+
return newContactReasonsConditional
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private setFollowUp(botContext: BotContext): void {
|
|
40
|
+
const sessionContactReasons =
|
|
41
|
+
botContext.session._hubtype_case_contact_reasons
|
|
42
|
+
|
|
43
|
+
const matchingBranch = sessionContactReasons
|
|
44
|
+
? findMatchingContactReasonBranch(
|
|
45
|
+
sessionContactReasons,
|
|
46
|
+
this.contactReasons
|
|
47
|
+
)
|
|
48
|
+
: undefined
|
|
49
|
+
|
|
50
|
+
if (matchingBranch) {
|
|
51
|
+
this.conditionalResult = matchingBranch.name
|
|
52
|
+
this.followUp = matchingBranch.target
|
|
53
|
+
} else {
|
|
54
|
+
this.conditionalResult = 'default'
|
|
55
|
+
this.followUp = this.defaultTarget
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async trackFlow(botContext: BotContext): Promise<void> {
|
|
60
|
+
const { flowThreadId, flowId, flowName, flowNodeId, flowNodeContentId } =
|
|
61
|
+
getCommonFlowContentEventArgsForContentId(botContext, this.id)
|
|
62
|
+
const eventContactReasonsConditional: EventConditionalContactReasons = {
|
|
63
|
+
action: EventAction.ConditionalContactReasons,
|
|
64
|
+
flowThreadId,
|
|
65
|
+
flowId,
|
|
66
|
+
flowName,
|
|
67
|
+
flowNodeId,
|
|
68
|
+
flowNodeContentId,
|
|
69
|
+
flowNodeIsMeaningful: false,
|
|
70
|
+
contactReasons:
|
|
71
|
+
botContext.session._hubtype_case_contact_reasons?.map(
|
|
72
|
+
contactReason => contactReason.name
|
|
73
|
+
) || [],
|
|
74
|
+
result: this.conditionalResult,
|
|
75
|
+
}
|
|
76
|
+
const { action, ...eventArgs } = eventContactReasonsConditional
|
|
77
|
+
await trackEvent(botContext, action, eventArgs)
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async processContent(botContext: BotContext): Promise<void> {
|
|
82
|
+
await this.filterContent(botContext, this)
|
|
83
|
+
await this.trackFlow(botContext)
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
toBotonic() {
|
|
88
|
+
return <></>
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -6,17 +6,27 @@ import {
|
|
|
6
6
|
isWebchat,
|
|
7
7
|
} from '@botonic/core'
|
|
8
8
|
import { WebchatSettings } from '@botonic/react'
|
|
9
|
-
|
|
10
9
|
import type { FlowBuilderApi } from '../api'
|
|
10
|
+
import { ON_CLOSE_HANDOFF_PAYLOAD, SEPARATOR } from '../constants'
|
|
11
11
|
import { getCommonFlowContentEventArgsForContentId } from '../tracking'
|
|
12
12
|
import { ContentFieldsBase } from './content-fields-base'
|
|
13
|
-
import
|
|
13
|
+
import {
|
|
14
|
+
DiscardType,
|
|
15
|
+
type HtHandoffNode,
|
|
16
|
+
type HtNodeLink,
|
|
17
|
+
type HtQueueLocale,
|
|
18
|
+
} from './hubtype-fields'
|
|
14
19
|
|
|
15
20
|
export class FlowHandoff extends ContentFieldsBase {
|
|
16
21
|
public queue?: HtQueueLocale
|
|
17
22
|
public onFinishPayload?: string
|
|
18
23
|
public handoffAutoAssign: boolean
|
|
19
24
|
public hasQueuePositionChangedNotificationsEnabled: boolean
|
|
25
|
+
public hasDifferentiatedCloseTypes: boolean
|
|
26
|
+
public resolvedByAgent?: HtNodeLink
|
|
27
|
+
public discardedByAgent?: HtNodeLink
|
|
28
|
+
public discardedByUser?: HtNodeLink
|
|
29
|
+
public discardedBySystem?: HtNodeLink
|
|
20
30
|
public isTestIntegration: boolean
|
|
21
31
|
|
|
22
32
|
static fromHubtypeCMS(
|
|
@@ -30,14 +40,21 @@ export class FlowHandoff extends ContentFieldsBase {
|
|
|
30
40
|
locale,
|
|
31
41
|
cmsHandoff.content.queue
|
|
32
42
|
)
|
|
43
|
+
newHandoff.hasDifferentiatedCloseTypes =
|
|
44
|
+
cmsHandoff.content.has_differentiated_close_types
|
|
45
|
+
newHandoff.resolvedByAgent = cmsHandoff.content.resolved_by_agent
|
|
46
|
+
newHandoff.discardedByAgent = cmsHandoff.content.discarded_by_agent
|
|
47
|
+
newHandoff.discardedByUser = cmsHandoff.content.discarded_by_user
|
|
48
|
+
newHandoff.discardedBySystem = cmsHandoff.content.discarded_by_system
|
|
49
|
+
newHandoff.handoffAutoAssign = cmsHandoff.content.has_auto_assign
|
|
50
|
+
newHandoff.hasQueuePositionChangedNotificationsEnabled =
|
|
51
|
+
cmsHandoff.content.has_queue_position_changed_notifications_enabled
|
|
52
|
+
|
|
33
53
|
newHandoff.onFinishPayload = FlowHandoff.getOnFinishPayload(
|
|
34
54
|
cmsHandoff,
|
|
35
55
|
cmsApi
|
|
36
56
|
)
|
|
37
|
-
newHandoff.
|
|
38
|
-
newHandoff.hasQueuePositionChangedNotificationsEnabled =
|
|
39
|
-
cmsHandoff.content.has_queue_position_changed_notifications_enabled
|
|
40
|
-
newHandoff.followUp = cmsHandoff.follow_up
|
|
57
|
+
newHandoff.followUp = undefined
|
|
41
58
|
|
|
42
59
|
return newHandoff
|
|
43
60
|
}
|
|
@@ -46,13 +63,43 @@ export class FlowHandoff extends ContentFieldsBase {
|
|
|
46
63
|
cmsHandoff: HtHandoffNode,
|
|
47
64
|
cmsApi: FlowBuilderApi
|
|
48
65
|
): string | undefined {
|
|
49
|
-
if (cmsHandoff.
|
|
66
|
+
if (cmsHandoff.content.has_differentiated_close_types) {
|
|
67
|
+
return `${ON_CLOSE_HANDOFF_PAYLOAD}${SEPARATOR}${cmsHandoff.id}`
|
|
68
|
+
} else if (cmsHandoff.target?.id) {
|
|
50
69
|
return cmsApi.getPayload(cmsHandoff.target)
|
|
51
70
|
}
|
|
52
71
|
|
|
53
72
|
return undefined
|
|
54
73
|
}
|
|
55
74
|
|
|
75
|
+
resolveOnClosePayload(botContext: BotContext): string {
|
|
76
|
+
const contactReasons =
|
|
77
|
+
botContext.session._hubtype_case_contact_reasons || []
|
|
78
|
+
|
|
79
|
+
if (contactReasons.some(reason => reason.name === DiscardType.ByAgent)) {
|
|
80
|
+
if (this.discardedByAgent?.id) {
|
|
81
|
+
return this.discardedByAgent.id
|
|
82
|
+
}
|
|
83
|
+
console.error('No discarded by agent target found')
|
|
84
|
+
}
|
|
85
|
+
if (contactReasons.some(reason => reason.name === DiscardType.ByUser)) {
|
|
86
|
+
if (this.discardedByUser?.id) {
|
|
87
|
+
return this.discardedByUser.id
|
|
88
|
+
}
|
|
89
|
+
console.error('No discarded by user target found')
|
|
90
|
+
}
|
|
91
|
+
if (contactReasons.some(reason => reason.name === DiscardType.BySystem)) {
|
|
92
|
+
if (this.discardedBySystem?.id) {
|
|
93
|
+
return this.discardedBySystem.id
|
|
94
|
+
}
|
|
95
|
+
console.error('No discarded by system target found')
|
|
96
|
+
}
|
|
97
|
+
if (this.resolvedByAgent?.id) {
|
|
98
|
+
return this.resolvedByAgent.id
|
|
99
|
+
}
|
|
100
|
+
throw new Error('No resolved by agent target found')
|
|
101
|
+
}
|
|
102
|
+
|
|
56
103
|
async doHandoff(botContext: BotContext): Promise<void> {
|
|
57
104
|
const handOffBuilder = new HandOffBuilder(botContext.session)
|
|
58
105
|
handOffBuilder.withAutoAssignOnWaiting(this.handoffAutoAssign)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { HtBaseNode, HtNodeLink } from './common'
|
|
2
|
+
import type { HtNodeWithContentType } from './node-types'
|
|
3
|
+
|
|
4
|
+
export interface HtContactReasonBranch {
|
|
5
|
+
id: string
|
|
6
|
+
name: string
|
|
7
|
+
project_id: string
|
|
8
|
+
project_name: string
|
|
9
|
+
target: HtNodeLink
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface HtContactReasonsNode extends HtBaseNode {
|
|
13
|
+
type: HtNodeWithContentType.CONTACT_REASONS_CONDITION
|
|
14
|
+
content: {
|
|
15
|
+
contact_reasons: HtContactReasonBranch[]
|
|
16
|
+
default_target: HtNodeLink
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
HtBaseNode,
|
|
3
|
+
HtNodeLink,
|
|
4
|
+
HtPayloadLocale,
|
|
5
|
+
HtQueueLocale,
|
|
6
|
+
} from './common'
|
|
2
7
|
import type { HtNodeWithContentType } from './node-types'
|
|
3
8
|
|
|
9
|
+
export enum DiscardType {
|
|
10
|
+
ByAgent = 'discarded',
|
|
11
|
+
ByUser = 'discarded_by_user',
|
|
12
|
+
BySystem = 'discarded_by_system',
|
|
13
|
+
}
|
|
14
|
+
|
|
4
15
|
export interface HtHandoffNode extends HtBaseNode {
|
|
5
16
|
type: HtNodeWithContentType.HANDOFF
|
|
6
17
|
content: {
|
|
@@ -8,5 +19,10 @@ export interface HtHandoffNode extends HtBaseNode {
|
|
|
8
19
|
payload: HtPayloadLocale[]
|
|
9
20
|
has_auto_assign: boolean
|
|
10
21
|
has_queue_position_changed_notifications_enabled: boolean
|
|
22
|
+
has_differentiated_close_types: boolean
|
|
23
|
+
resolved_by_agent?: HtNodeLink
|
|
24
|
+
discarded_by_agent?: HtNodeLink
|
|
25
|
+
discarded_by_user?: HtNodeLink
|
|
26
|
+
discarded_by_system?: HtNodeLink
|
|
11
27
|
}
|
|
12
28
|
}
|
|
@@ -21,6 +21,7 @@ export enum HtNodeWithContentType {
|
|
|
21
21
|
CAPTURE_USER_INPUT = 'capture-user-input',
|
|
22
22
|
CUSTOM_CONDITION = 'custom-condition',
|
|
23
23
|
DO_NOTHING = 'do-nothing',
|
|
24
|
+
CONTACT_REASONS_CONDITION = 'contact-reasons-condition',
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
export enum HtNodeWithoutContentType {
|
|
@@ -3,6 +3,7 @@ import type { HtAiAgentRouterNode } from './ai-agent-router'
|
|
|
3
3
|
import type { HtBotActionNode } from './bot-action'
|
|
4
4
|
import type { HtCaptureUserInputNode } from './capture-user-input'
|
|
5
5
|
import type { HtCarouselNode } from './carousel'
|
|
6
|
+
import type { HtContactReasonsNode } from './contact-reasons-conditional'
|
|
6
7
|
import type { HtCustomConditionalV2Node } from './custom-conditional-v2'
|
|
7
8
|
import type { HtDoNothingNode } from './do-nothing'
|
|
8
9
|
// import { HtChannelConditionalNode } from './channel-conditional'
|
|
@@ -50,6 +51,7 @@ export type HtNodeWithContent =
|
|
|
50
51
|
| HtCaptureUserInputNode
|
|
51
52
|
| HtCustomConditionalV2Node
|
|
52
53
|
| HtDoNothingNode
|
|
54
|
+
| HtContactReasonsNode
|
|
53
55
|
// | HtChannelConditionalNode
|
|
54
56
|
// | HtCountryConditionalNode
|
|
55
57
|
// | HtCustomConditionalNode
|
|
@@ -5,6 +5,7 @@ import { FlowBotAction } from './flow-bot-action'
|
|
|
5
5
|
import { FlowCaptureUserInput } from './flow-capture-user-input'
|
|
6
6
|
import { FlowCarousel } from './flow-carousel'
|
|
7
7
|
import { FlowChannelConditional } from './flow-channel-conditional'
|
|
8
|
+
import { FlowContactReasonsConditional } from './flow-contact-reasons-conditional'
|
|
8
9
|
import { FlowCountryConditional } from './flow-country-conditional'
|
|
9
10
|
import { FlowCustomConditional } from './flow-custom-conditional'
|
|
10
11
|
import { FlowDoNothing } from './flow-do-nothing'
|
|
@@ -34,6 +35,7 @@ export {
|
|
|
34
35
|
FlowCaptureUserInput,
|
|
35
36
|
FlowCarousel,
|
|
36
37
|
FlowChannelConditional,
|
|
38
|
+
FlowContactReasonsConditional,
|
|
37
39
|
FlowCountryConditional,
|
|
38
40
|
FlowCustomConditional,
|
|
39
41
|
FlowCustomConditionalV2,
|
|
@@ -75,3 +77,4 @@ export type FlowContent =
|
|
|
75
77
|
| FlowAiAgentRouter
|
|
76
78
|
| FlowCustomConditionalV2
|
|
77
79
|
| FlowDoNothing
|
|
80
|
+
| FlowContactReasonsConditional
|
package/src/flow-factory.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
FlowBotAction,
|
|
8
8
|
FlowCarousel,
|
|
9
9
|
FlowChannelConditional,
|
|
10
|
+
FlowContactReasonsConditional,
|
|
10
11
|
type FlowContent,
|
|
11
12
|
FlowCountryConditional,
|
|
12
13
|
FlowCustomConditional,
|
|
@@ -122,6 +123,12 @@ export class FlowFactory {
|
|
|
122
123
|
case HtNodeWithContentType.DO_NOTHING:
|
|
123
124
|
return FlowDoNothing.fromHubtypeCMS(hubtypeContent)
|
|
124
125
|
|
|
126
|
+
case HtNodeWithContentType.CONTACT_REASONS_CONDITION:
|
|
127
|
+
return FlowContactReasonsConditional.fromHubtypeCMS(
|
|
128
|
+
hubtypeContent,
|
|
129
|
+
this.botContext
|
|
130
|
+
)
|
|
131
|
+
|
|
125
132
|
default:
|
|
126
133
|
return undefined
|
|
127
134
|
}
|