@botonic/plugin-flow-builder 0.21.0-alpha.9 → 0.21.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.
Files changed (63) hide show
  1. package/lib/cjs/action.js +21 -32
  2. package/lib/cjs/action.js.map +1 -1
  3. package/lib/cjs/content-fields/content-fields-base.d.ts +2 -1
  4. package/lib/cjs/content-fields/content-fields-base.js +6 -1
  5. package/lib/cjs/content-fields/content-fields-base.js.map +1 -1
  6. package/lib/cjs/content-fields/flow-button.js +4 -2
  7. package/lib/cjs/content-fields/flow-button.js.map +1 -1
  8. package/lib/cjs/content-fields/flow-video.d.ts +8 -0
  9. package/lib/cjs/content-fields/flow-video.js +26 -0
  10. package/lib/cjs/content-fields/flow-video.js.map +1 -0
  11. package/lib/cjs/content-fields/index.d.ts +1 -0
  12. package/lib/cjs/content-fields/index.js +3 -1
  13. package/lib/cjs/content-fields/index.js.map +1 -1
  14. package/lib/cjs/content-fields/types.d.ts +2 -1
  15. package/lib/cjs/flow-builder-models.d.ts +26 -4
  16. package/lib/cjs/flow-builder-models.js +2 -0
  17. package/lib/cjs/flow-builder-models.js.map +1 -1
  18. package/lib/cjs/handoff.d.ts +2 -1
  19. package/lib/cjs/handoff.js +21 -14
  20. package/lib/cjs/handoff.js.map +1 -1
  21. package/lib/cjs/helpers.d.ts +5 -0
  22. package/lib/cjs/helpers.js +14 -0
  23. package/lib/cjs/helpers.js.map +1 -0
  24. package/lib/cjs/index.d.ts +8 -3
  25. package/lib/cjs/index.js +35 -17
  26. package/lib/cjs/index.js.map +1 -1
  27. package/lib/esm/action.js +22 -33
  28. package/lib/esm/action.js.map +1 -1
  29. package/lib/esm/content-fields/content-fields-base.d.ts +2 -1
  30. package/lib/esm/content-fields/content-fields-base.js +6 -1
  31. package/lib/esm/content-fields/content-fields-base.js.map +1 -1
  32. package/lib/esm/content-fields/flow-button.js +4 -2
  33. package/lib/esm/content-fields/flow-button.js.map +1 -1
  34. package/lib/esm/content-fields/flow-video.d.ts +8 -0
  35. package/lib/esm/content-fields/flow-video.js +21 -0
  36. package/lib/esm/content-fields/flow-video.js.map +1 -0
  37. package/lib/esm/content-fields/index.d.ts +1 -0
  38. package/lib/esm/content-fields/index.js +1 -0
  39. package/lib/esm/content-fields/index.js.map +1 -1
  40. package/lib/esm/content-fields/types.d.ts +2 -1
  41. package/lib/esm/flow-builder-models.d.ts +26 -4
  42. package/lib/esm/flow-builder-models.js +2 -0
  43. package/lib/esm/flow-builder-models.js.map +1 -1
  44. package/lib/esm/handoff.d.ts +2 -1
  45. package/lib/esm/handoff.js +21 -14
  46. package/lib/esm/handoff.js.map +1 -1
  47. package/lib/esm/helpers.d.ts +5 -0
  48. package/lib/esm/helpers.js +10 -0
  49. package/lib/esm/helpers.js.map +1 -0
  50. package/lib/esm/index.d.ts +8 -3
  51. package/lib/esm/index.js +36 -18
  52. package/lib/esm/index.js.map +1 -1
  53. package/package.json +3 -4
  54. package/src/action.tsx +25 -35
  55. package/src/content-fields/content-fields-base.ts +11 -2
  56. package/src/content-fields/flow-button.tsx +3 -1
  57. package/src/content-fields/flow-video.tsx +22 -0
  58. package/src/content-fields/index.ts +1 -0
  59. package/src/content-fields/types.ts +2 -1
  60. package/src/flow-builder-models.ts +30 -2
  61. package/src/handoff.ts +25 -16
  62. package/src/helpers.ts +18 -0
  63. package/src/index.ts +52 -21
@@ -0,0 +1,22 @@
1
+ import { Video } from '@botonic/react'
2
+ import React from 'react'
3
+
4
+ import { VideoNode } from '../flow-builder-models'
5
+ import { ContentFieldsBase } from './content-fields-base'
6
+
7
+ export class FlowVideo extends ContentFieldsBase {
8
+ public src = ''
9
+ public code = ''
10
+
11
+ static fromHubtypeCMS(component: VideoNode, locale: string): FlowVideo {
12
+ const newVideo = new FlowVideo(component.id)
13
+ newVideo.code = component.code
14
+ newVideo.src = this.getVideoByLocale(locale, component.content.video)
15
+ return newVideo
16
+ }
17
+
18
+ toBotonic(index: number): JSX.Element {
19
+ // @ts-ignore
20
+ return <Video key={index} src={this.src} />
21
+ }
22
+ }
@@ -4,4 +4,5 @@ export { FlowCarousel } from './flow-carousel'
4
4
  export { FlowElement } from './flow-element'
5
5
  export { FlowImage } from './flow-image'
6
6
  export { FlowText } from './flow-text'
7
+ export { FlowVideo } from './flow-video'
7
8
  export type { FlowContent } from './types'
@@ -1,5 +1,6 @@
1
1
  import { FlowCarousel } from './flow-carousel'
2
2
  import { FlowImage } from './flow-image'
3
3
  import { FlowText } from './flow-text'
4
+ import { FlowVideo } from './flow-video'
4
5
 
5
- export type FlowContent = FlowText | FlowImage | FlowCarousel
6
+ export type FlowContent = FlowText | FlowImage | FlowCarousel | FlowVideo
@@ -18,6 +18,8 @@ export enum NodeType {
18
18
  URL = 'url',
19
19
  PAYLOAD = 'payload',
20
20
  FUNCTION = 'function',
21
+ FALLBACK = 'fallback',
22
+ VIDEO = 'video',
21
23
  }
22
24
 
23
25
  export interface BaseNode {
@@ -67,8 +69,8 @@ export interface Button {
67
69
  text: TextLocale[]
68
70
  target?: NodeLink
69
71
  hidden: string
70
- url?: UrlLocale
71
- payload?: PayloadLocale
72
+ url?: UrlLocale[]
73
+ payload?: PayloadLocale[]
72
74
  }
73
75
 
74
76
  export interface TextNodeContent {
@@ -207,6 +209,30 @@ export interface FunctionNode extends Node {
207
209
  content: FunctionNodeContent
208
210
  }
209
211
 
212
+ export interface FallbackNodeContent {
213
+ first_message: NodeLink
214
+ second_message: NodeLink
215
+ }
216
+ export interface FallbackNode extends Node {
217
+ type: NodeType.FALLBACK
218
+ content: FallbackNodeContent
219
+ }
220
+
221
+ export interface VideoLocale {
222
+ url: string
223
+ is_embedded?: boolean
224
+ locale: string
225
+ }
226
+
227
+ export interface VideoNodeContent {
228
+ video: VideoLocale[]
229
+ }
230
+
231
+ export interface VideoNode extends Node {
232
+ type: NodeType.VIDEO
233
+ content: VideoNodeContent
234
+ }
235
+
210
236
  export type NodeComponent =
211
237
  | TextNode
212
238
  | ImageNode
@@ -218,3 +244,5 @@ export type NodeComponent =
218
244
  | StartNode
219
245
  | PayloadNode
220
246
  | FunctionNode
247
+ | FallbackNode
248
+ | VideoNode
package/src/handoff.ts CHANGED
@@ -1,28 +1,37 @@
1
1
  import { HandOffBuilder } from '@botonic/core'
2
2
  import { ActionRequest } from '@botonic/react'
3
3
 
4
+ import { HandoffNode } from './flow-builder-models'
5
+ import { getFlowBuilderPlugin } from './helpers'
6
+
4
7
  export async function doHandoff(
5
8
  request: ActionRequest,
6
- queue: string,
7
- note?: string,
8
- agentEmail?: string
9
+ locale: string,
10
+ handoffNode: HandoffNode
9
11
  ): Promise<void> {
12
+ const flowBuilderPlugin = getFlowBuilderPlugin(request.plugins)
13
+ const handoffTargetNode = await flowBuilderPlugin.getHandoffContent(
14
+ handoffNode.target?.id
15
+ )
10
16
  // @ts-ignore
11
- const flowBuilderPlugin = request.plugins.hubtypeFlowBuilder as any
12
- const handoffContent = await flowBuilderPlugin.getHandoffContent()
17
+ const handOffBuilder = new HandOffBuilder(request.session) // handOffBuilder.withQueue(handoffNode.content.queue)
18
+ const handoffQueues = handoffNode.content.queue
19
+ const queueFound = handoffQueues.find(q => q.locale === locale)
20
+ if (queueFound) handOffBuilder.withQueue(queueFound.id)
21
+ // TODO: Retrieve params from FlowBuilder
22
+ // const handoffParams = {
23
+ // agentEmail: 'test@gmail.com',
24
+ // note: 'This is a note that will be attached to the case as a reminder',
25
+ // }
13
26
 
14
- // @ts-ignore
15
- const handOffBuilder = new HandOffBuilder(request.session)
16
- handOffBuilder.withQueue(queue)
27
+ // if (handoffParams.note) {
28
+ // handOffBuilder.withNote(handoffParams.note)
29
+ // }
17
30
 
18
- if (note) {
19
- handOffBuilder.withNote(note)
20
- }
31
+ // if (handoffParams.agentEmail) {
32
+ // handOffBuilder.withAgentEmail(handoffParams.agentEmail)
33
+ // }
21
34
 
22
- if (agentEmail) {
23
- handOffBuilder.withAgentEmail(agentEmail)
24
- }
25
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
26
- handOffBuilder.withOnFinishPayload(handoffContent.target?.id!)
35
+ handOffBuilder.withOnFinishPayload(handoffTargetNode.id)
27
36
  await handOffBuilder.handOff()
28
37
  }
package/src/helpers.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { Plugin } from '@botonic/core'
2
+ import BotonicPluginFlowBuilder from './index'
3
+
4
+ const FLOW_BUILDER_PLUGIN_NAME = 'BotonicPluginFlowBuilder'
5
+
6
+ export function getFlowBuilderPlugin(plugins: {
7
+ [id: string]: Plugin
8
+ }): BotonicPluginFlowBuilder {
9
+ const flowBuilderPlugin = Object.values(plugins).find(
10
+ // @ts-ignore
11
+ plugin => plugin.name === FLOW_BUILDER_PLUGIN_NAME
12
+ ) as BotonicPluginFlowBuilder
13
+ if (!flowBuilderPlugin)
14
+ throw new Error(
15
+ `You must include '@botonic/plugin-flow-builder' in your plugins file.`
16
+ )
17
+ return flowBuilderPlugin
18
+ }
package/src/index.ts CHANGED
@@ -12,14 +12,17 @@ import {
12
12
  FlowContent,
13
13
  FlowImage,
14
14
  FlowText,
15
+ FlowVideo,
15
16
  } from './content-fields'
16
17
  import {
18
+ FallbackNode,
17
19
  FlowBuilderData,
18
20
  FunctionNode,
19
21
  HandoffNode,
20
22
  IntentNode,
21
23
  KeywordNode,
22
24
  NodeComponent,
25
+ NodeLink,
23
26
  NodeType,
24
27
  StartNode,
25
28
  } from './flow-builder-models'
@@ -51,10 +54,10 @@ export default class BotonicPluginFlowBuilder implements Plugin {
51
54
  }
52
55
 
53
56
  async readFlowContent(): Promise<FlowBuilderData> {
54
- const response = await axios.get(this.flowUrl, {
57
+ const { data } = await axios.get(this.flowUrl, {
55
58
  headers: { Authorization: `Bearer ${this.getAccessToken()}` },
56
59
  })
57
- return response.data
60
+ return data
58
61
  }
59
62
 
60
63
  async pre(request: PluginPreRequest): Promise<void> {
@@ -71,10 +74,12 @@ export default class BotonicPluginFlowBuilder implements Plugin {
71
74
  return content
72
75
  }
73
76
 
74
- async getHandoffContent(): Promise<HandoffNode> {
77
+ async getHandoffContent(
78
+ handoffTargetId: string | undefined
79
+ ): Promise<HandoffNode> {
75
80
  const flow = await this.flow
76
81
  const content = flow.nodes.find(
77
- node => node.type === NodeType.HANDOFF
82
+ node => node.id === handoffTargetId
78
83
  ) as HandoffNode
79
84
  if (!content) throw Error(`Handoff node not found`)
80
85
  return content
@@ -91,6 +96,8 @@ export default class BotonicPluginFlowBuilder implements Plugin {
91
96
  return FlowImage.fromHubtypeCMS(hubtypeContent, locale)
92
97
  case NodeType.CAROUSEL:
93
98
  return FlowCarousel.fromHubtypeCMS(hubtypeContent, locale)
99
+ case NodeType.VIDEO:
100
+ return FlowVideo.fromHubtypeCMS(hubtypeContent, locale)
94
101
  default:
95
102
  return undefined
96
103
  }
@@ -98,20 +105,34 @@ export default class BotonicPluginFlowBuilder implements Plugin {
98
105
 
99
106
  async getStartId(): Promise<string> {
100
107
  const flow = await this.flow
101
- const startNode = flow.nodes.find(node => node.type === NodeType.START_UP)
102
- if (!startNode) {
103
- throw new Error('start-up id must be defined')
104
- }
105
- return (startNode as StartNode).target.id
108
+ const startNode = flow.nodes.find(
109
+ node => node.type === NodeType.START_UP
110
+ ) as StartNode | undefined
111
+ if (!startNode) throw new Error('start-up id must be defined')
112
+ return startNode.target.id
106
113
  }
107
114
 
115
+ async getFallbackId(): Promise<string> {
116
+ const flow = await this.flow
117
+ const fallbackNode = flow.nodes.find(
118
+ node => node.type === NodeType.FALLBACK
119
+ ) as FallbackNode | undefined
120
+ if (!fallbackNode) throw new Error('fallback node must be defined')
121
+ const fallbackFirstMessage = fallbackNode.content.first_message
122
+ const fallbackSecondMessage = fallbackNode.content.second_message
123
+ if (!fallbackSecondMessage) return fallbackFirstMessage.id
124
+ const fallbackIds = [fallbackFirstMessage.id, fallbackSecondMessage.id]
125
+ return fallbackIds[Math.floor(Math.random() * fallbackIds.length)]
126
+ }
108
127
  async getContents(
109
128
  id: string,
110
129
  locale: string,
111
130
  prevContents?: FlowContent[]
112
- ): Promise<FlowContent[]> {
131
+ ): Promise<{ contents: FlowContent[]; handoffNode: HandoffNode }> {
113
132
  const contents = prevContents || []
114
133
  const hubtypeContent: any = await this.getContent(id)
134
+ const isHandoff = hubtypeContent.type === NodeType.HANDOFF
135
+
115
136
  if (hubtypeContent.content.elements) {
116
137
  for (const i in hubtypeContent.content.elements) {
117
138
  const button = hubtypeContent.content.elements[i].button
@@ -151,10 +172,10 @@ export default class BotonicPluginFlowBuilder implements Plugin {
151
172
  }
152
173
  // execute function
153
174
  // return this.getContents(function result_mapping target, locale, contents)
154
- return contents
175
+ return { contents, handoffNode: isHandoff && hubtypeContent }
155
176
  }
156
177
 
157
- async getPayloadByInput(
178
+ async getPayloadByIntent(
158
179
  input: Input,
159
180
  locale: string
160
181
  ): Promise<string | undefined> {
@@ -163,14 +184,17 @@ export default class BotonicPluginFlowBuilder implements Plugin {
163
184
  const intents = flow.nodes.filter(
164
185
  node => node.type === NodeType.INTENT
165
186
  ) as IntentNode[]
166
- if (input.intent) {
167
- const matchedIntents = intents.filter(node =>
168
- //@ts-ignore
169
- this.hasIntent(node, input.intent, locale)
187
+ const inputIntent = input.intent
188
+ const inputConfidence = input.confidence
189
+ if (inputIntent) {
190
+ const matchedIntentNode = intents.find(
191
+ node =>
192
+ inputIntent &&
193
+ this.hasIntent(node, inputIntent, locale) &&
194
+ inputConfidence &&
195
+ this.hasMetConfidenceThreshold(node, inputConfidence)
170
196
  )
171
- if (matchedIntents.length > 0) {
172
- return matchedIntents[0].target?.id
173
- }
197
+ return matchedIntentNode?.target?.id
174
198
  }
175
199
  } catch (error) {
176
200
  console.error('Error getting payload by input: ', error)
@@ -180,10 +204,17 @@ export default class BotonicPluginFlowBuilder implements Plugin {
180
204
  }
181
205
 
182
206
  hasIntent(node: IntentNode, intent: string, locale: string): boolean {
183
- const result = node.content.intents.find(
207
+ return node.content.intents.some(
184
208
  i => i.locale === locale && i.values.includes(intent)
185
209
  )
186
- return Boolean(result)
210
+ }
211
+
212
+ hasMetConfidenceThreshold(
213
+ node: IntentNode,
214
+ predictedConfidence: number
215
+ ): boolean {
216
+ const nodeConfidence = node.content.confidence / 100
217
+ return predictedConfidence >= nodeConfidence
187
218
  }
188
219
 
189
220
  async getPayloadByKeyword(