@botonic/plugin-flow-builder 0.36.1 → 0.37.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.
Files changed (86) hide show
  1. package/lib/cjs/action/payload.d.ts +1 -1
  2. package/lib/cjs/action/payload.js +46 -3
  3. package/lib/cjs/action/payload.js.map +1 -1
  4. package/lib/cjs/api.d.ts +3 -1
  5. package/lib/cjs/api.js +15 -0
  6. package/lib/cjs/api.js.map +1 -1
  7. package/lib/cjs/constants.d.ts +1 -0
  8. package/lib/cjs/constants.js +4 -1
  9. package/lib/cjs/constants.js.map +1 -1
  10. package/lib/cjs/content-fields/flow-button.d.ts +2 -0
  11. package/lib/cjs/content-fields/flow-button.js +8 -0
  12. package/lib/cjs/content-fields/flow-button.js.map +1 -1
  13. package/lib/cjs/content-fields/flow-rating.d.ts +15 -0
  14. package/lib/cjs/content-fields/flow-rating.js +56 -0
  15. package/lib/cjs/content-fields/flow-rating.js.map +1 -0
  16. package/lib/cjs/content-fields/hubtype-fields/index.d.ts +1 -0
  17. package/lib/cjs/content-fields/hubtype-fields/index.js +1 -0
  18. package/lib/cjs/content-fields/hubtype-fields/index.js.map +1 -1
  19. package/lib/cjs/content-fields/hubtype-fields/node-types.d.ts +2 -1
  20. package/lib/cjs/content-fields/hubtype-fields/node-types.js +1 -0
  21. package/lib/cjs/content-fields/hubtype-fields/node-types.js.map +1 -1
  22. package/lib/cjs/content-fields/hubtype-fields/nodes.d.ts +2 -1
  23. package/lib/cjs/content-fields/hubtype-fields/rating.d.ts +23 -0
  24. package/lib/cjs/content-fields/hubtype-fields/rating.js +9 -0
  25. package/lib/cjs/content-fields/hubtype-fields/rating.js.map +1 -0
  26. package/lib/cjs/content-fields/index.d.ts +3 -2
  27. package/lib/cjs/content-fields/index.js +3 -1
  28. package/lib/cjs/content-fields/index.js.map +1 -1
  29. package/lib/cjs/index.d.ts +1 -0
  30. package/lib/cjs/index.js +4 -0
  31. package/lib/cjs/index.js.map +1 -1
  32. package/lib/cjs/tracking.d.ts +2 -1
  33. package/lib/cjs/tracking.js +1 -0
  34. package/lib/cjs/tracking.js.map +1 -1
  35. package/lib/cjs/types.d.ts +1 -0
  36. package/lib/cjs/types.js.map +1 -1
  37. package/lib/esm/action/payload.d.ts +1 -1
  38. package/lib/esm/action/payload.js +46 -3
  39. package/lib/esm/action/payload.js.map +1 -1
  40. package/lib/esm/api.d.ts +3 -1
  41. package/lib/esm/api.js +15 -0
  42. package/lib/esm/api.js.map +1 -1
  43. package/lib/esm/constants.d.ts +1 -0
  44. package/lib/esm/constants.js +3 -0
  45. package/lib/esm/constants.js.map +1 -1
  46. package/lib/esm/content-fields/flow-button.d.ts +2 -0
  47. package/lib/esm/content-fields/flow-button.js +8 -0
  48. package/lib/esm/content-fields/flow-button.js.map +1 -1
  49. package/lib/esm/content-fields/flow-rating.d.ts +15 -0
  50. package/lib/esm/content-fields/flow-rating.js +52 -0
  51. package/lib/esm/content-fields/flow-rating.js.map +1 -0
  52. package/lib/esm/content-fields/hubtype-fields/index.d.ts +1 -0
  53. package/lib/esm/content-fields/hubtype-fields/index.js +1 -0
  54. package/lib/esm/content-fields/hubtype-fields/index.js.map +1 -1
  55. package/lib/esm/content-fields/hubtype-fields/node-types.d.ts +2 -1
  56. package/lib/esm/content-fields/hubtype-fields/node-types.js +1 -0
  57. package/lib/esm/content-fields/hubtype-fields/node-types.js.map +1 -1
  58. package/lib/esm/content-fields/hubtype-fields/nodes.d.ts +2 -1
  59. package/lib/esm/content-fields/hubtype-fields/rating.d.ts +23 -0
  60. package/lib/esm/content-fields/hubtype-fields/rating.js +6 -0
  61. package/lib/esm/content-fields/hubtype-fields/rating.js.map +1 -0
  62. package/lib/esm/content-fields/index.d.ts +3 -2
  63. package/lib/esm/content-fields/index.js +2 -1
  64. package/lib/esm/content-fields/index.js.map +1 -1
  65. package/lib/esm/index.d.ts +1 -0
  66. package/lib/esm/index.js +5 -1
  67. package/lib/esm/index.js.map +1 -1
  68. package/lib/esm/tracking.d.ts +2 -1
  69. package/lib/esm/tracking.js +1 -0
  70. package/lib/esm/tracking.js.map +1 -1
  71. package/lib/esm/types.d.ts +1 -0
  72. package/lib/esm/types.js.map +1 -1
  73. package/package.json +2 -2
  74. package/src/action/payload.ts +56 -6
  75. package/src/api.ts +28 -0
  76. package/src/constants.ts +4 -0
  77. package/src/content-fields/flow-button.tsx +9 -0
  78. package/src/content-fields/flow-rating.tsx +92 -0
  79. package/src/content-fields/hubtype-fields/index.ts +1 -0
  80. package/src/content-fields/hubtype-fields/node-types.ts +1 -0
  81. package/src/content-fields/hubtype-fields/nodes.ts +2 -0
  82. package/src/content-fields/hubtype-fields/rating.ts +26 -0
  83. package/src/content-fields/index.ts +4 -0
  84. package/src/index.ts +7 -0
  85. package/src/tracking.ts +1 -0
  86. package/src/types.ts +1 -0
@@ -1,13 +1,20 @@
1
+ import { storeCaseRating } from '@botonic/core'
2
+ import { v7 as uuid } from 'uuid'
3
+
4
+ import { AGENT_RATING_PAYLOAD, SEPARATOR } from '../constants'
1
5
  import { FlowContent } from '../content-fields'
2
6
  import { HtNodeWithContent } from '../content-fields/hubtype-fields'
7
+ import { EventAction, trackEvent } from '../tracking'
3
8
  import { FlowBuilderContext } from './index'
4
9
 
5
- export async function getContentsByPayload({
6
- cmsApi,
7
- flowBuilderPlugin,
8
- request,
9
- contentID,
10
- }: FlowBuilderContext): Promise<FlowContent[]> {
10
+ export async function getContentsByPayload(
11
+ context: FlowBuilderContext
12
+ ): Promise<FlowContent[]> {
13
+ const { cmsApi, flowBuilderPlugin, request, contentID } = context
14
+ if (request.input.payload?.startsWith(AGENT_RATING_PAYLOAD)) {
15
+ return await resolveRatingPayload(context)
16
+ }
17
+
11
18
  const id = contentID
12
19
  ? cmsApi.getNodeByContentID(contentID)?.id
13
20
  : request.input.payload
@@ -19,3 +26,46 @@ export async function getContentsByPayload({
19
26
 
20
27
  return []
21
28
  }
29
+
30
+ async function resolveRatingPayload(
31
+ context: FlowBuilderContext
32
+ ): Promise<FlowContent[]> {
33
+ const { cmsApi, flowBuilderPlugin, request } = context
34
+
35
+ if (!request.input.payload) {
36
+ return []
37
+ }
38
+
39
+ const id = request.input.payload
40
+ const buttonId = id?.split(SEPARATOR)[1]
41
+ const ratingNode = cmsApi.getRatingNodeByButtonId(buttonId)
42
+ const ratingButton = cmsApi.getRatingButtonById(ratingNode, buttonId)
43
+ const { target, text, value } = ratingButton
44
+ const possibleOptions = ratingNode.content.buttons.map(button => button.text)
45
+ const possibleValues = ratingNode.content.buttons.map(button => button.value)
46
+
47
+ if (request.session._hubtype_case_id) {
48
+ const event = {
49
+ action: EventAction.FeedbackCase,
50
+ feedbackTargetId: request.session._hubtype_case_id,
51
+ feedbackGroupId: uuid().toString(),
52
+ possibleOptions,
53
+ possibleValues,
54
+ option: text,
55
+ value,
56
+ }
57
+
58
+ await storeCaseRating(request.session, value)
59
+ await trackEvent(request, EventAction.FeedbackCase, event)
60
+ }
61
+
62
+ const targetNode = target
63
+ ? cmsApi.getNodeById<HtNodeWithContent>(target.id)
64
+ : undefined
65
+
66
+ if (targetNode) {
67
+ return await flowBuilderPlugin.getContentsByNode(targetNode)
68
+ }
69
+
70
+ return []
71
+ }
package/src/api.ts CHANGED
@@ -19,6 +19,8 @@ import {
19
19
  HtNodeWithContentType,
20
20
  HtNodeWithoutContentType,
21
21
  HtPayloadNode,
22
+ HtRatingButton,
23
+ HtRatingNode,
22
24
  } from './content-fields/hubtype-fields'
23
25
  import { HtSmartIntentNode } from './content-fields/hubtype-fields/smart-intent'
24
26
  import { FlowBuilderApiOptions, ProcessEnvNodeEnvs } from './types'
@@ -80,6 +82,32 @@ export class FlowBuilderApi {
80
82
  return node as T
81
83
  }
82
84
 
85
+ getRatingNodeByButtonId(id: string): HtRatingNode {
86
+ const ratingNodes = this.flow.nodes.filter(
87
+ node => node.type === HtNodeWithContentType.RATING
88
+ ) as HtRatingNode[]
89
+ const ratingNode = ratingNodes.find(node =>
90
+ node.content.buttons.some(button => button.id === id)
91
+ ) as HtRatingNode | undefined
92
+
93
+ if (!ratingNode) {
94
+ throw Error(`Rating node with button id: '${id}' not found`)
95
+ }
96
+
97
+ return ratingNode
98
+ }
99
+
100
+ getRatingButtonById(ratingNode: HtRatingNode, id: string): HtRatingButton {
101
+ const ratingButton = ratingNode.content.buttons.find(
102
+ button => button.id === id
103
+ ) as HtRatingButton | undefined
104
+ if (!ratingButton) {
105
+ throw Error(`Rating button with id: '${id}' not found`)
106
+ }
107
+
108
+ return ratingButton
109
+ }
110
+
83
111
  getNodeByContentID(contentID: string): HtNodeComponent {
84
112
  const content = this.flow.nodes.find(node =>
85
113
  'code' in node ? node.code === contentID : false
package/src/constants.ts CHANGED
@@ -8,6 +8,10 @@ export const REG_EXP_PATTERN = /^\/(.*)\/([gimyus]*)$/
8
8
  export const UUID_REGEXP =
9
9
  /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
10
10
 
11
+ /* ********** PAYLOADS ********** */
12
+ export const AGENT_RATING_PAYLOAD = 'agent-rating'
13
+
14
+ /* ********** FLOW NAMES ********** */
11
15
  export const MAIN_FLOW_NAME = 'Main'
12
16
  export const KNOWLEDGE_BASE_FLOW_NAME = 'Knowledge base'
13
17
  export const AI_AGENTS_FLOW_NAME = 'AI Agents'
@@ -5,6 +5,7 @@ import { FlowBuilderApi } from '../api'
5
5
  import { SOURCE_INFO_SEPARATOR } from '../constants'
6
6
  import { ContentFieldsBase } from './content-fields-base'
7
7
  import { HtButton, HtButtonStyle, HtUrlNode } from './hubtype-fields'
8
+ import { HtRatingButton } from './hubtype-fields/rating'
8
9
 
9
10
  export class FlowButton extends ContentFieldsBase {
10
11
  public text = ''
@@ -47,6 +48,14 @@ export class FlowButton extends ContentFieldsBase {
47
48
  return newButton
48
49
  }
49
50
 
51
+ static fromRating(button: HtRatingButton): FlowButton {
52
+ const newButton = new FlowButton(button.id)
53
+ newButton.text = button.text
54
+ newButton.payload = button.payload
55
+ newButton.target = button.target?.id
56
+ return newButton
57
+ }
58
+
50
59
  static getUrlId(cmsButton: HtButton, locale: string): string | undefined {
51
60
  return cmsButton.url.find(url => url.locale === locale)?.id
52
61
  }
@@ -0,0 +1,92 @@
1
+ import { isDev, isWebchat, isWhatsapp } from '@botonic/core'
2
+ import {
3
+ ActionRequest,
4
+ CustomRatingMessage,
5
+ Text,
6
+ WhatsappButtonList,
7
+ } from '@botonic/react'
8
+
9
+ import { getFlowBuilderPlugin } from '../helpers'
10
+ import { ContentFieldsBase } from './content-fields-base'
11
+ import { FlowButton } from './flow-button'
12
+ import { HtRatingNode, RatingType } from './hubtype-fields'
13
+
14
+ export class FlowRating extends ContentFieldsBase {
15
+ public code = ''
16
+ public text = ''
17
+ public sendButtonText = ''
18
+ public ratingType = RatingType.Stars
19
+ public buttons: FlowButton[] = []
20
+ public openListButtonText = ''
21
+
22
+ static fromHubtypeCMS(cmsText: HtRatingNode, locale: string): FlowRating {
23
+ const newRating = new FlowRating(cmsText.id)
24
+ newRating.code = cmsText.code
25
+ newRating.text = this.getTextByLocale(locale, cmsText.content.text)
26
+ newRating.sendButtonText = this.getTextByLocale(
27
+ locale,
28
+ cmsText.content.send_button_text
29
+ )
30
+ newRating.ratingType = cmsText.content.rating_type
31
+ newRating.buttons = cmsText.content.buttons.map(button =>
32
+ FlowButton.fromRating(button)
33
+ )
34
+ newRating.openListButtonText = this.getTextByLocale(
35
+ locale,
36
+ cmsText.content.open_list_button_text
37
+ )
38
+
39
+ return newRating
40
+ }
41
+
42
+ toBotonic(id: string, request: ActionRequest): JSX.Element {
43
+ const flowBuilderPlugin = getFlowBuilderPlugin(request.plugins)
44
+ const customRatingMessageEnabled =
45
+ flowBuilderPlugin.customRatingMessageEnabled
46
+
47
+ if (isWhatsapp(request.session)) {
48
+ return (
49
+ <WhatsappButtonList
50
+ body={this.text}
51
+ button={this.openListButtonText}
52
+ sections={[
53
+ {
54
+ rows: this.buttons.map(button => ({
55
+ id: button.payload as string,
56
+ title: button.text,
57
+ })),
58
+ },
59
+ ]}
60
+ />
61
+ )
62
+ }
63
+
64
+ if (
65
+ (isWebchat(request.session) || isDev(request.session)) &&
66
+ customRatingMessageEnabled
67
+ ) {
68
+ const payloads = this.buttons
69
+ .map(button => button.payload)
70
+ .slice()
71
+ .reverse()
72
+
73
+ return (
74
+ <CustomRatingMessage
75
+ payloads={payloads}
76
+ messageText={this.text}
77
+ buttonText={this.sendButtonText}
78
+ ratingType={this.ratingType}
79
+ />
80
+ )
81
+ }
82
+
83
+ return (
84
+ <Text key={id}>
85
+ {this.text}
86
+ {this.buttons.map((button, buttonIndex) =>
87
+ button.renderButton(buttonIndex)
88
+ )}
89
+ </Text>
90
+ )
91
+ }
92
+ }
@@ -12,6 +12,7 @@ export * from './knowledge-base'
12
12
  export * from './node-types'
13
13
  export * from './nodes'
14
14
  export * from './payload'
15
+ export * from './rating'
15
16
  export * from './smart-intent'
16
17
  export * from './text'
17
18
  export * from './url'
@@ -13,6 +13,7 @@ export enum HtNodeWithContentType {
13
13
  KNOWLEDGE_BASE = 'knowledge-base',
14
14
  BOT_ACTION = 'bot-action',
15
15
  AI_AGENT = 'ai-agent',
16
+ RATING = 'rating',
16
17
  }
17
18
 
18
19
  export enum HtNodeWithoutContentType {
@@ -9,6 +9,7 @@ import { HtImageNode } from './image'
9
9
  import { HtKeywordNode } from './keyword'
10
10
  import { HtKnowledgeBaseNode } from './knowledge-base'
11
11
  import { HtPayloadNode } from './payload'
12
+ import { HtRatingNode } from './rating'
12
13
  import { HtSmartIntentNode } from './smart-intent'
13
14
  import { HtTextNode } from './text'
14
15
  import { HtUrlNode } from './url'
@@ -31,6 +32,7 @@ export type HtNodeWithContent =
31
32
  | HtKnowledgeBaseNode
32
33
  | HtBotActionNode
33
34
  | HtAiAgentNode
35
+ | HtRatingNode
34
36
 
35
37
  export type HtNodeWithoutContent = HtUrlNode | HtPayloadNode | HtGoToFlow
36
38
 
@@ -0,0 +1,26 @@
1
+ import { HtBaseNode, HtNodeLink, HtTextLocale } from './common'
2
+ import { HtNodeWithContentType } from './node-types'
3
+
4
+ export enum RatingType {
5
+ Stars = 'stars',
6
+ Smileys = 'smileys',
7
+ }
8
+
9
+ export interface HtRatingButton {
10
+ id: string
11
+ text: string
12
+ payload: string
13
+ value: number
14
+ target?: HtNodeLink
15
+ }
16
+
17
+ export interface HtRatingNode extends HtBaseNode {
18
+ type: HtNodeWithContentType.RATING
19
+ content: {
20
+ text: HtTextLocale[]
21
+ buttons: HtRatingButton[]
22
+ rating_type: RatingType
23
+ send_button_text: HtTextLocale[]
24
+ open_list_button_text: HtTextLocale[]
25
+ }
26
+ }
@@ -7,10 +7,12 @@ import {
7
7
  DISABLED_MEMORY_LENGTH,
8
8
  FlowKnowledgeBase,
9
9
  } from './flow-knowledge-base'
10
+ import { FlowRating } from './flow-rating'
10
11
  import { FlowText } from './flow-text'
11
12
  import { FlowVideo } from './flow-video'
12
13
  import { FlowWhatsappCtaUrlButtonNode } from './flow-whatsapp-cta-url-button'
13
14
  import { FlowWhatsappButtonList } from './whatsapp-button-list/flow-whatsapp-button-list'
15
+
14
16
  export { ContentFieldsBase } from './content-fields-base'
15
17
  export { FlowButton } from './flow-button'
16
18
  export { FlowElement } from './flow-element'
@@ -21,6 +23,7 @@ export {
21
23
  FlowHandoff,
22
24
  FlowImage,
23
25
  FlowKnowledgeBase,
26
+ FlowRating,
24
27
  FlowText,
25
28
  FlowVideo,
26
29
  FlowWhatsappButtonList,
@@ -38,5 +41,6 @@ export type FlowContent =
38
41
  | FlowKnowledgeBase
39
42
  | FlowBotAction
40
43
  | FlowAiAgent
44
+ | FlowRating
41
45
 
42
46
  export { DISABLED_MEMORY_LENGTH }
package/src/index.ts CHANGED
@@ -21,6 +21,7 @@ import {
21
21
  FlowHandoff,
22
22
  FlowImage,
23
23
  FlowKnowledgeBase,
24
+ FlowRating,
24
25
  FlowText,
25
26
  FlowVideo,
26
27
  FlowWhatsappButtonList,
@@ -69,6 +70,7 @@ export default class BotonicPluginFlowBuilder implements Plugin {
69
70
  // TODO: Rethink how we construct FlowBuilderApi to be simpler
70
71
  public jsonVersion: FlowBuilderJSONVersion
71
72
  public apiUrl: string
73
+ public customRatingMessageEnabled: boolean
72
74
 
73
75
  constructor(options: BotonicPluginFlowBuilderOptions<ResolvedPlugins, any>) {
74
76
  this.apiUrl = options.apiUrl || FLOW_BUILDER_API_URL_PROD
@@ -90,6 +92,8 @@ export default class BotonicPluginFlowBuilder implements Plugin {
90
92
  allowKnowledgeBases: options.inShadowing?.allowKnowledgeBases || false,
91
93
  }
92
94
  this.contentFilters = options.contentFilters || []
95
+ this.customRatingMessageEnabled =
96
+ options.customRatingMessageEnabled || false
93
97
  }
94
98
 
95
99
  resolveFlowUrl(request: PluginPreRequest): string {
@@ -240,6 +244,9 @@ export default class BotonicPluginFlowBuilder implements Plugin {
240
244
  case HtNodeWithContentType.AI_AGENT:
241
245
  return FlowAiAgent.fromHubtypeCMS(hubtypeContent)
242
246
 
247
+ case HtNodeWithContentType.RATING:
248
+ return FlowRating.fromHubtypeCMS(hubtypeContent, locale)
249
+
243
250
  case HtNodeWithContentType.BOT_ACTION:
244
251
  return FlowBotAction.fromHubtypeCMS(hubtypeContent, locale, this.cmsApi)
245
252
 
package/src/tracking.ts CHANGED
@@ -14,6 +14,7 @@ export enum EventAction {
14
14
  IntentSmart = 'nlu_intent_smart',
15
15
  Knowledgebase = 'knowledgebase',
16
16
  Fallback = 'fallback',
17
+ FeedbackCase = 'feedback_case',
17
18
  }
18
19
 
19
20
  export enum KnowledgebaseFailReason {
package/src/types.ts CHANGED
@@ -24,6 +24,7 @@ export interface BotonicPluginFlowBuilderOptions<
24
24
  smartIntentsConfig?: { numSmartIntentsToUse: number }
25
25
  inShadowing?: Partial<InShadowingConfig>
26
26
  contentFilters?: ContentFilter<TPlugins, TExtraData>[]
27
+ customRatingMessageEnabled?: boolean
27
28
  }
28
29
 
29
30
  export type TrackEventFunction<