@botonic/plugin-flow-builder 0.57.0-rslib-esm.2 → 0.58.0-alpha-new-stack.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/action/ai-agent-from-user-input.js +8 -0
- package/lib/action/ai-agent-from-user-input.js.map +1 -1
- package/lib/action/first-interaction.js +10 -0
- package/lib/action/first-interaction.js.map +1 -1
- package/lib/action/get-contents.js +5 -2
- package/lib/action/get-contents.js.map +1 -1
- package/lib/action/index.js +8 -1
- package/lib/action/index.js.map +1 -1
- package/lib/api.d.ts +8 -3
- package/lib/api.js +37 -13
- package/lib/api.js.map +1 -1
- package/lib/bot-context-manager.d.ts +17 -0
- package/lib/bot-context-manager.js +101 -0
- package/lib/bot-context-manager.js.map +1 -0
- package/lib/constants.d.ts +2 -1
- package/lib/constants.js +4 -3
- package/lib/constants.js.map +1 -1
- package/lib/content-fields/contact-reasons-conditional-matcher.d.ts +4 -0
- package/lib/content-fields/contact-reasons-conditional-matcher.js +10 -0
- package/lib/content-fields/contact-reasons-conditional-matcher.js.map +1 -0
- package/lib/content-fields/flow-ai-agent-base.js +1 -1
- package/lib/content-fields/flow-ai-agent-base.js.map +1 -1
- package/lib/content-fields/flow-ai-agent-router.js +2 -1
- package/lib/content-fields/flow-ai-agent-router.js.map +1 -1
- package/lib/content-fields/flow-ai-agent.js +2 -1
- package/lib/content-fields/flow-ai-agent.js.map +1 -1
- package/lib/content-fields/flow-contact-reasons-conditional.d.ts +14 -0
- package/lib/content-fields/flow-contact-reasons-conditional.js +68 -0
- package/lib/content-fields/flow-contact-reasons-conditional.js.map +1 -0
- package/lib/content-fields/flow-do-nothing.d.ts +9 -0
- package/lib/content-fields/flow-do-nothing.js +33 -0
- package/lib/content-fields/flow-do-nothing.js.map +1 -0
- package/lib/content-fields/flow-handoff.d.ts +7 -1
- package/lib/content-fields/flow-handoff.js +41 -3
- package/lib/content-fields/flow-handoff.js.map +1 -1
- package/lib/content-fields/flow-whatsapp-request-contact-info.js +1 -0
- package/lib/content-fields/flow-whatsapp-request-contact-info.js.map +1 -1
- package/lib/content-fields/hubtype-fields/contact-reasons-conditional.d.ts +16 -0
- package/lib/content-fields/hubtype-fields/contact-reasons-conditional.js +4 -0
- package/lib/content-fields/hubtype-fields/contact-reasons-conditional.js.map +1 -0
- package/lib/content-fields/hubtype-fields/do-nothing.d.ts +6 -0
- package/lib/content-fields/hubtype-fields/do-nothing.js +4 -0
- package/lib/content-fields/hubtype-fields/do-nothing.js.map +1 -0
- package/lib/content-fields/hubtype-fields/handoff.d.ts +11 -1
- package/lib/content-fields/hubtype-fields/handoff.js +7 -1
- package/lib/content-fields/hubtype-fields/handoff.js.map +1 -1
- package/lib/content-fields/hubtype-fields/index.d.ts +1 -0
- package/lib/content-fields/hubtype-fields/index.js +2 -0
- package/lib/content-fields/hubtype-fields/index.js.map +1 -1
- package/lib/content-fields/hubtype-fields/node-types.d.ts +3 -1
- package/lib/content-fields/hubtype-fields/node-types.js +2 -0
- package/lib/content-fields/hubtype-fields/node-types.js.map +1 -1
- package/lib/content-fields/hubtype-fields/nodes.d.ts +3 -1
- package/lib/content-fields/hubtype-fields/nodes.js.map +1 -1
- package/lib/content-fields/hubtype-fields/whatsapp-request-contact-info.d.ts +2 -0
- package/lib/content-fields/hubtype-fields/whatsapp-request-contact-info.js.map +1 -1
- package/lib/content-fields/index.d.ts +4 -2
- package/lib/content-fields/index.js +6 -0
- package/lib/content-fields/index.js.map +1 -1
- package/lib/flow-factory.js +5 -1
- package/lib/flow-factory.js.map +1 -1
- package/lib/index.d.ts +5 -8
- package/lib/index.js +17 -55
- package/lib/index.js.map +1 -1
- package/lib/types.d.ts +2 -2
- package/lib/types.js.map +1 -1
- package/package.json +3 -3
- package/src/action/ai-agent-from-user-input.ts +7 -0
- package/src/action/first-interaction.ts +6 -0
- package/src/action/get-contents.ts +17 -2
- package/src/action/index.tsx +7 -0
- package/src/api.ts +52 -15
- 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-ai-agent-base.tsx +1 -1
- package/src/content-fields/flow-ai-agent-router.tsx +1 -0
- package/src/content-fields/flow-ai-agent.tsx +1 -0
- package/src/content-fields/flow-contact-reasons-conditional.tsx +90 -0
- package/src/content-fields/flow-do-nothing.tsx +29 -0
- package/src/content-fields/flow-handoff.tsx +54 -7
- package/src/content-fields/flow-whatsapp-request-contact-info.tsx +2 -0
- package/src/content-fields/hubtype-fields/contact-reasons-conditional.ts +18 -0
- package/src/content-fields/hubtype-fields/do-nothing.ts +7 -0
- package/src/content-fields/hubtype-fields/handoff.ts +17 -1
- package/src/content-fields/hubtype-fields/index.ts +1 -0
- package/src/content-fields/hubtype-fields/node-types.ts +2 -0
- package/src/content-fields/hubtype-fields/nodes.ts +4 -0
- package/src/content-fields/hubtype-fields/whatsapp-request-contact-info.ts +2 -0
- package/src/content-fields/index.ts +6 -0
- package/src/flow-factory.ts +11 -0
- package/src/index.ts +26 -88
- package/src/types.ts +1 -2
package/src/api.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { BotContext } from '@botonic/core'
|
|
2
2
|
import axios from 'axios'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
AI_AGENTS_FLOW_NAME,
|
|
6
|
+
PUSH_FLOW_PAYLOAD,
|
|
7
|
+
SEPARATOR,
|
|
8
|
+
UUID_REGEXP,
|
|
9
|
+
} from './constants'
|
|
5
10
|
import {
|
|
6
11
|
type HtBotActionNode,
|
|
7
12
|
type HtCaptureUserInputNode,
|
|
@@ -18,6 +23,7 @@ import {
|
|
|
18
23
|
type HtRatingButton,
|
|
19
24
|
type HtRatingNode,
|
|
20
25
|
type HtSmartIntentNode,
|
|
26
|
+
type HtWhatsappRequestContactInfoNode,
|
|
21
27
|
} from './content-fields/hubtype-fields'
|
|
22
28
|
import {
|
|
23
29
|
type FlowBuilderApiOptions,
|
|
@@ -30,7 +36,7 @@ export class FlowBuilderApi {
|
|
|
30
36
|
url: string
|
|
31
37
|
flowUrl: string
|
|
32
38
|
flow: HtFlowBuilderData
|
|
33
|
-
|
|
39
|
+
botContext: BotContext
|
|
34
40
|
|
|
35
41
|
private constructor() {}
|
|
36
42
|
|
|
@@ -38,12 +44,12 @@ export class FlowBuilderApi {
|
|
|
38
44
|
const newApi = new FlowBuilderApi()
|
|
39
45
|
|
|
40
46
|
newApi.url = options.url
|
|
41
|
-
newApi.
|
|
47
|
+
newApi.botContext = options.botContext
|
|
42
48
|
// TODO: Refactor later to combine logic from `FlowBuilderApi.create`, `resolveFlowUrl` and `getAccessToken` to be in one place
|
|
43
49
|
if (process.env.NODE_ENV === ProcessEnvNodeEnvs.DEVELOPMENT) {
|
|
44
50
|
await newApi.updateSessionWithUserInfo(options.accessToken)
|
|
45
51
|
}
|
|
46
|
-
const updatedRequest = newApi.
|
|
52
|
+
const updatedRequest = newApi.botContext
|
|
47
53
|
newApi.flowUrl = options.flowUrl.replace(
|
|
48
54
|
'{bot_id}',
|
|
49
55
|
updatedRequest.session.bot.id
|
|
@@ -64,8 +70,8 @@ export class FlowBuilderApi {
|
|
|
64
70
|
const response = await axios.get(url, {
|
|
65
71
|
headers: { Authorization: `Bearer ${token}` },
|
|
66
72
|
})
|
|
67
|
-
this.
|
|
68
|
-
this.
|
|
73
|
+
this.botContext.session.organization_id = response.data.organization_id
|
|
74
|
+
this.botContext.session.bot.id = response.data.bot_id
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
getNodeByFlowId(id: string): HtNodeWithContent {
|
|
@@ -259,23 +265,23 @@ export class FlowBuilderApi {
|
|
|
259
265
|
}
|
|
260
266
|
|
|
261
267
|
getCaptureUserInputId(): string | undefined {
|
|
262
|
-
return this.
|
|
268
|
+
return this.botContext.session.capture_user_input?.node_id
|
|
263
269
|
}
|
|
264
270
|
|
|
265
271
|
setCaptureUserInputId(id: string): void {
|
|
266
|
-
if (this.
|
|
267
|
-
this.
|
|
272
|
+
if (this.botContext.session.capture_user_input) {
|
|
273
|
+
this.botContext.session.capture_user_input.node_id = id
|
|
268
274
|
}
|
|
269
|
-
this.
|
|
275
|
+
this.botContext.session.capture_user_input = { node_id: id }
|
|
270
276
|
}
|
|
271
277
|
|
|
272
278
|
removeCaptureUserInputId(): void {
|
|
273
|
-
this.
|
|
279
|
+
this.botContext.session.capture_user_input = undefined
|
|
274
280
|
}
|
|
275
281
|
|
|
276
282
|
setUserExtraDataVariable(key: string, value: string): void {
|
|
277
|
-
if (this.
|
|
278
|
-
this.
|
|
283
|
+
if (this.botContext.session.user.extra_data) {
|
|
284
|
+
this.botContext.session.user.extra_data[key] = value
|
|
279
285
|
}
|
|
280
286
|
}
|
|
281
287
|
|
|
@@ -287,14 +293,45 @@ export class FlowBuilderApi {
|
|
|
287
293
|
return this.getNodeById<HtCaptureUserInputNode>(captureUserInputId)
|
|
288
294
|
}
|
|
289
295
|
|
|
296
|
+
getSessionWhatsappRequestContactId(): string | undefined {
|
|
297
|
+
return this.botContext.session.whatsapp_request_contact?.id
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
setWhatsappRequestContactId(id: string): void {
|
|
301
|
+
if (this.botContext.session.whatsapp_request_contact) {
|
|
302
|
+
this.botContext.session.whatsapp_request_contact.id = id
|
|
303
|
+
}
|
|
304
|
+
this.botContext.session.whatsapp_request_contact = { id }
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
removeWhatsappRequestContactId(): void {
|
|
308
|
+
this.botContext.session.whatsapp_request_contact = undefined
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
getWhatsappRequestContactInfoNode():
|
|
312
|
+
| HtWhatsappRequestContactInfoNode
|
|
313
|
+
| undefined {
|
|
314
|
+
const whatsappRequestContactId = this.getSessionWhatsappRequestContactId()
|
|
315
|
+
if (!whatsappRequestContactId) {
|
|
316
|
+
return undefined
|
|
317
|
+
}
|
|
318
|
+
return this.getNodeById<HtWhatsappRequestContactInfoNode>(
|
|
319
|
+
whatsappRequestContactId
|
|
320
|
+
)
|
|
321
|
+
}
|
|
322
|
+
|
|
290
323
|
getResolvedLocale(): string {
|
|
291
324
|
const flowLocales = this.flow.locales
|
|
292
325
|
const defaultLocaleCode = this.flow.default_locale_code
|
|
293
326
|
|
|
294
327
|
return new FlowLocale(
|
|
295
|
-
this.
|
|
328
|
+
this.botContext,
|
|
296
329
|
flowLocales,
|
|
297
330
|
defaultLocaleCode
|
|
298
331
|
).resolve()
|
|
299
332
|
}
|
|
333
|
+
|
|
334
|
+
isPushFlowPayload(payload?: string): boolean {
|
|
335
|
+
return payload?.startsWith(PUSH_FLOW_PAYLOAD) ?? false
|
|
336
|
+
}
|
|
300
337
|
}
|
|
@@ -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
|
+
}
|
|
@@ -118,7 +118,7 @@ export abstract class FlowAiAgentBase extends ContentFieldsBase {
|
|
|
118
118
|
botContext: BotContext,
|
|
119
119
|
previousContents?: FlowContent[]
|
|
120
120
|
): Promise<void> {
|
|
121
|
-
if (this.
|
|
121
|
+
if (!this.aiAgentResponse) {
|
|
122
122
|
await this.resolveAIAgentResponse(botContext, previousContents)
|
|
123
123
|
}
|
|
124
124
|
if (this.jsxElements.length === 0) {
|
|
@@ -194,6 +194,7 @@ export class FlowAiAgentRouter extends FlowAiAgentBase {
|
|
|
194
194
|
inputGuardrailsTriggered: [],
|
|
195
195
|
outputGuardrailsTriggered:
|
|
196
196
|
this.aiAgentResponse?.outputGuardrailsTriggered ?? [],
|
|
197
|
+
doNothing: this.aiAgentResponse?.doNothing ?? false,
|
|
197
198
|
}
|
|
198
199
|
const { action, ...specialistEventArgs } = specialistEvent
|
|
199
200
|
await trackEvent(botContext, action, specialistEventArgs)
|
|
@@ -94,6 +94,7 @@ export class FlowAiAgent extends FlowAiAgentBase {
|
|
|
94
94
|
this.aiAgentResponse?.inputGuardrailsTriggered ?? [],
|
|
95
95
|
outputGuardrailsTriggered: [], //aiAgentResponse.outputGuardrailsTriggered,
|
|
96
96
|
error: this.aiAgentResponse?.error ?? false,
|
|
97
|
+
doNothing: this.aiAgentResponse?.doNothing ?? false,
|
|
97
98
|
}
|
|
98
99
|
const { action, ...eventArgs } = event
|
|
99
100
|
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { BotContext } from '@botonic/core'
|
|
2
|
+
|
|
3
|
+
import { trackOneContent } from '../tracking'
|
|
4
|
+
import { ContentFieldsBase } from './content-fields-base'
|
|
5
|
+
import type { HtDoNothingNode } from './hubtype-fields'
|
|
6
|
+
|
|
7
|
+
export class FlowDoNothing extends ContentFieldsBase {
|
|
8
|
+
static fromHubtypeCMS(cmsDoNothing: HtDoNothingNode): FlowDoNothing {
|
|
9
|
+
const newDoNothing = new FlowDoNothing(cmsDoNothing.id)
|
|
10
|
+
newDoNothing.code = cmsDoNothing.code
|
|
11
|
+
newDoNothing.followUp = cmsDoNothing.follow_up
|
|
12
|
+
|
|
13
|
+
return newDoNothing
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async trackFlow(botContext: BotContext): Promise<void> {
|
|
17
|
+
await trackOneContent(botContext, this)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async processContent(botContext: BotContext): Promise<void> {
|
|
21
|
+
await this.filterContent(botContext, this)
|
|
22
|
+
await this.trackFlow(botContext)
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
toBotonic(): JSX.Element {
|
|
27
|
+
return <></>
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -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)
|
|
@@ -35,6 +35,8 @@ export class FlowWhatsappRequestContactInfoNode extends ContentFieldsBase {
|
|
|
35
35
|
message: RequestContactInfoMessage,
|
|
36
36
|
botContext: BotContext
|
|
37
37
|
): JSX.Element {
|
|
38
|
+
botContext.session.whatsapp_request_contact = undefined
|
|
39
|
+
|
|
38
40
|
if (!isWhatsapp(botContext.session)) {
|
|
39
41
|
return <Text key={id}>{message.content.text}</Text>
|
|
40
42
|
}
|
|
@@ -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
|
}
|
|
@@ -20,6 +20,8 @@ export enum HtNodeWithContentType {
|
|
|
20
20
|
GO_TO_FLOW = 'go-to-flow',
|
|
21
21
|
CAPTURE_USER_INPUT = 'capture-user-input',
|
|
22
22
|
CUSTOM_CONDITION = 'custom-condition',
|
|
23
|
+
DO_NOTHING = 'do-nothing',
|
|
24
|
+
CONTACT_REASONS_CONDITION = 'contact-reasons-condition',
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export enum HtNodeWithoutContentType {
|
|
@@ -3,7 +3,9 @@ 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'
|
|
8
|
+
import type { HtDoNothingNode } from './do-nothing'
|
|
7
9
|
// import { HtChannelConditionalNode } from './channel-conditional'
|
|
8
10
|
// import { HtCountryConditionalNode } from './country-conditional'
|
|
9
11
|
// import { HtCustomConditionalNode } from './custom-conditional'
|
|
@@ -48,6 +50,8 @@ export type HtNodeWithContent =
|
|
|
48
50
|
| HtWhatsappTemplateNode
|
|
49
51
|
| HtCaptureUserInputNode
|
|
50
52
|
| HtCustomConditionalV2Node
|
|
53
|
+
| HtDoNothingNode
|
|
54
|
+
| HtContactReasonsNode
|
|
51
55
|
// | HtChannelConditionalNode
|
|
52
56
|
// | HtCountryConditionalNode
|
|
53
57
|
// | HtCustomConditionalNode
|