wingbot 3.34.0 → 3.36.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wingbot",
3
- "version": "3.34.0",
3
+ "version": "3.36.0",
4
4
  "description": "Enterprise Messaging Bot Conversation Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -46,7 +46,7 @@
46
46
  "eslint-plugin-react": "^7.29.4",
47
47
  "graphql-markdown": "^6.0.0",
48
48
  "jsdoc-to-markdown": "^7.1.1",
49
- "mocha": "^9.2.2",
49
+ "mocha": "^10.0.0",
50
50
  "nyc": "^15.1.0",
51
51
  "rimraf": "^3.0.2",
52
52
  "sinon": "^13.0.1",
package/src/Ai.js CHANGED
@@ -40,6 +40,8 @@ let uq = 1;
40
40
 
41
41
  /** @typedef {import('./Request').IntentAction} IntentAction */
42
42
  /** @typedef {import('./Request')} Request */
43
+ /** @typedef {import('./Responder')} Responder */
44
+ /** @typedef {import('./wingbot/CachedModel').Result} Result */
43
45
  /** @typedef {import('./wingbot/CustomEntityDetectionModel').Phrases} Phrases */
44
46
 
45
47
  /**
@@ -348,13 +350,13 @@ class Ai {
348
350
  const rules = this.matcher.preprocessRule(intent);
349
351
  const matcher = this._createIntentMatcher(rules, usedEntities);
350
352
 
351
- return async (req) => {
353
+ return async (req, res) => {
352
354
  if (!req.isTextOrIntent()) {
353
355
  return false;
354
356
  }
355
357
 
356
358
  if (!req.intents) {
357
- await this._loadIntents(req);
359
+ await this._loadIntents(req, res);
358
360
  }
359
361
 
360
362
  const winningIntent = matcher(req);
@@ -523,9 +525,10 @@ class Ai {
523
525
  /**
524
526
  *
525
527
  * @param {Request} req
528
+ * @param {Responder} [res]
526
529
  * @returns {Promise}
527
530
  */
528
- async preloadAi (req) {
531
+ async preloadAi (req, res = null) {
529
532
  if (req.supportsFeature(req.FEATURE_PHRASES)) {
530
533
  const model = this._getModelForRequest(req, false);
531
534
 
@@ -534,7 +537,7 @@ class Ai {
534
537
  .catch(() => {});
535
538
  }
536
539
  }
537
- return this._preloadIntent(req);
540
+ return this._preloadIntent(req, res);
538
541
  }
539
542
 
540
543
  /**
@@ -552,12 +555,43 @@ class Ai {
552
555
  return CustomEntityDetectionModel.getEmptyPhrasesObject();
553
556
  }
554
557
 
555
- async _preloadIntent (req) {
558
+ /**
559
+ *
560
+ * @param {Request} req
561
+ * @param {Responder} res
562
+ * @param {Result} result
563
+ * @returns {void}
564
+ */
565
+ _setResultToReqRes (req, res, result) {
566
+ const { text = null, intents, entities = [] } = result;
567
+ Object.assign(req, { intents, entities, _anonymizedText: text });
568
+
569
+ if (!res) {
570
+ return;
571
+ }
572
+
573
+ const entitiesObj = (entities || []).reduce((o, { entity, value }) => {
574
+ const list = o[entity] || [];
575
+ list.push(value);
576
+ return Object.assign(o, { [entity]: list });
577
+ }, {});
578
+
579
+ res.setData({
580
+ '@': entitiesObj
581
+ });
582
+ }
583
+
584
+ /**
585
+ *
586
+ * @param {Request} req
587
+ * @param {Responder} [res]
588
+ * @returns {Promise}
589
+ */
590
+ async _preloadIntent (req, res = null) {
556
591
  const mockIntent = this._getMockIntent(req);
557
592
 
558
593
  if (mockIntent) {
559
- req.intents = mockIntent.intents;
560
- req.entities = mockIntent.entities;
594
+ this._setResultToReqRes(req, res, mockIntent);
561
595
  return;
562
596
  }
563
597
 
@@ -571,15 +605,15 @@ class Ai {
571
605
  req.intents = [];
572
606
  return;
573
607
  }
574
- await this._loadIntents(req, model);
608
+ await this._loadIntents(req, res, model);
575
609
  } else {
576
610
  req.intents = [];
577
611
  }
578
612
  }
579
613
 
580
- async _loadIntents (req, model = null) {
581
- const { text = null, intents, entities = [] } = await this._queryModel(req, model);
582
- Object.assign(req, { intents, entities, _anonymizedText: text });
614
+ async _loadIntents (req, res = null, model = null) {
615
+ const result = await this._queryModel(req, model);
616
+ this._setResultToReqRes(req, res, result);
583
617
  }
584
618
 
585
619
  async _queryModel (req, useModel = null) {
package/src/BotApp.js CHANGED
@@ -46,6 +46,15 @@ const DEFAULT_API_URL = 'https://orchestrator-api.wingbot.ai';
46
46
  * @prop {object} headers
47
47
  */
48
48
 
49
+ function defaultMsg (senderId, pageId) {
50
+ return {
51
+ sender: { id: senderId },
52
+ recipient: { id: pageId },
53
+ mid: null,
54
+ timestamp: Date.now()
55
+ };
56
+ }
57
+
49
58
  /**
50
59
  * Adapter for Wingbot flight director
51
60
  *
@@ -239,32 +248,55 @@ class BotApp {
239
248
  };
240
249
  }
241
250
 
251
+ const sender = await this.createSender(senderId, pageId, message, secret, appId);
252
+ const res = await this._processor.processMessage(message, pageId, sender, { appId });
253
+ await this._processSenderResponses(sender, senderId, pageId, headers);
254
+
255
+ return {
256
+ status: res.status,
257
+ response_to_mid: message.mid,
258
+ messaging: []
259
+ };
260
+ }
261
+
262
+ /**
263
+ * Creates a Sender to be able, for example, to upload files
264
+ *
265
+ * @param {string} senderId
266
+ * @param {string} pageId
267
+ * @param {object} [message]
268
+ * @param {string|Promise<string>} [secret]
269
+ * @param {string} appId
270
+ * @returns {Promise<BotAppSender>}
271
+ */
272
+ async createSender (
273
+ senderId,
274
+ pageId,
275
+ message = defaultMsg(senderId, pageId),
276
+ secret = this._secret,
277
+ appId = this._appId
278
+ ) {
279
+ const useSecret = await Promise.resolve(secret);
280
+
281
+ const setResponseToMid = message.response_to_mid || message.mid;
282
+
242
283
  const options = {
243
284
  ...this._returnSenderOptions,
244
285
  apiUrl: this._apiUrl,
245
286
  pageId,
246
287
  appId,
247
- secret,
288
+ secret: useSecret,
248
289
  mid: setResponseToMid,
249
290
  fetch: this._fetch,
250
291
  tls: this._tls
251
292
  };
252
293
 
253
- const sender = new BotAppSender(options, senderId, message, this._senderLogger);
254
- const res = await this._processor.processMessage(message, pageId, sender, { appId });
255
- await this._processSenderResponses(sender, senderId, pageId, headers);
256
-
257
- return {
258
- status: res.status,
259
- response_to_mid: message.mid,
260
- messaging: []
261
- };
294
+ return new BotAppSender(options, senderId, message, this._senderLogger);
262
295
  }
263
296
 
264
297
  /**
265
298
  * Compatibility method for Notification engine
266
299
  *
267
- * @deprecated
268
300
  * @param {object} message - wingbot chat event
269
301
  * @param {string} senderId - chat event sender identifier
270
302
  * @param {string} pageId - channel/page identifier
package/src/Processor.js CHANGED
@@ -541,7 +541,7 @@ class Processor extends EventEmitter {
541
541
  }
542
542
  }
543
543
 
544
- await Ai.ai.preloadAi(req);
544
+ await Ai.ai.preloadAi(req, res);
545
545
 
546
546
  // @deprecated backward compatibility
547
547
  const aByAi = req.actionByAi();
@@ -14,6 +14,7 @@ const TYPE_SHARE = 'element_share';
14
14
  const TYPE_URL = 'web_url';
15
15
  const TYPE_URL_WITH_EXT = 'web_url_extension';
16
16
  const TYPE_POSTBACK = 'postback';
17
+ const TYPE_ATTACHMENT = 'attachment';
17
18
 
18
19
  const WEBVIEW_FULL = 'full';
19
20
  const WEBVIEW_TALL = 'tall';
@@ -227,10 +228,11 @@ function processButtons (
227
228
  editableCondition,
228
229
  setState
229
230
  }) => {
231
+
230
232
  if (hasCondition) {
231
233
  const condition = getCondition({
232
234
  hasCondition, conditionFn, hasEditableCondition, editableCondition
233
- }, context, 'Quick reply condition');
235
+ }, context, 'Button condition');
234
236
 
235
237
  if (!condition(req, res)) {
236
238
  return;
@@ -244,7 +246,8 @@ function processButtons (
244
246
  url,
245
247
  webviewHeight = WEBVIEW_TALL,
246
248
  targetRouteId,
247
- action
249
+ action,
250
+ payload
248
251
  } = btnAction;
249
252
 
250
253
  const isExtUrl = type === TYPE_URL_WITH_EXT;
@@ -273,6 +276,9 @@ function processButtons (
273
276
  case TYPE_SHARE:
274
277
  elem.shareButton(btnTitleText);
275
278
  break;
279
+ case TYPE_ATTACHMENT:
280
+ elem.attachmentButton(btnTitleText, payload);
281
+ break;
276
282
  default:
277
283
  }
278
284
  });
@@ -6,6 +6,38 @@
6
6
  const BaseTemplate = require('./BaseTemplate');
7
7
  const { makeAbsolute } = require('../utils');
8
8
 
9
+ /**
10
+ * @typedef MarkdownPayload
11
+ * @prop {"text/markdown"} contentType
12
+ * @prop {string} content
13
+ */
14
+
15
+ /**
16
+ * Designer payload type
17
+ *
18
+ * @typedef {MarkdownPayload} Payload
19
+ */
20
+
21
+ /**
22
+ * @typedef EncodedPayload
23
+ * @prop {string} content
24
+ * @prop {"text/markdown"} content_type
25
+ */
26
+
27
+ /**
28
+ * Encodes different types of payloads from designer snapshot to payload for chat
29
+ * Content is Base64 encoded.
30
+ *
31
+ * @param {Payload} payload
32
+ * @returns {EncodedPayload}
33
+ */
34
+ function encodePayload ({ content, contentType }) {
35
+ return {
36
+ content: Buffer.from(content).toString('base64'),
37
+ content_type: contentType
38
+ };
39
+ }
40
+
9
41
  /**
10
42
  * Helps with creating of button template
11
43
  * Instance of button template is returned by {Responder}
@@ -92,6 +124,31 @@ class ButtonTemplate extends BaseTemplate {
92
124
  return this;
93
125
  }
94
126
 
127
+ /**
128
+ * Adds button, which opens a popup with content on click.
129
+ *
130
+ * @param {string} title
131
+ * @param {Payload} payload
132
+ * @returns {this}
133
+ *
134
+ * @example
135
+ * res.button('text')
136
+ * .attachmentButton('button title', {
137
+ * content: '# Heading 1',
138
+ * contentType: 'text/markdown'
139
+ * })
140
+ * .send();
141
+ *
142
+ */
143
+ attachmentButton (title, payload) {
144
+ this.buttons.push({
145
+ type: 'attachment',
146
+ title: this._t(title),
147
+ payload: encodePayload(payload)
148
+ });
149
+ return this;
150
+ }
151
+
95
152
  /**
96
153
  *
97
154
  * @returns {this}
@@ -3,8 +3,19 @@
3
3
  */
4
4
  'use strict';
5
5
 
6
+ /** @typedef {import('../Request')} Request */
7
+ /** @typedef {import('../Responder')} Responder */
8
+
9
+ /**
10
+ *
11
+ * @param {Request} req
12
+ * @param {Responder} res
13
+ * @param {object} configuration
14
+ * @returns {object}
15
+ */
6
16
  module.exports = function stateData (req, res = null, configuration = null) {
7
17
  const c = configuration || req.configuration;
18
+
8
19
  return {
9
20
  ...req.state,
10
21
  ...(res ? res.newState : {}),