wingbot 3.46.0-alpha.8 → 3.46.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/index.js CHANGED
@@ -48,6 +48,10 @@ const compileWithState = require('./src/utils/compileWithState');
48
48
  const onInteractionHandler = require('./src/analytics/onInteractionHandler');
49
49
  const GA4 = require('./src/analytics/GA4');
50
50
  const plugins = require('./plugins/plugins.json');
51
+ const extractText = require('./src/transcript/extractText');
52
+ const htmlBodyFromTranscript = require('./src/transcript/htmlBodyFromTranscript');
53
+ const textBodyFromTranscript = require('./src/transcript/textBodyFromTranscript');
54
+ const transcriptFromHistory = require('./src/transcript/transcriptFromHistory');
51
55
  const {
52
56
  bufferloader,
53
57
  MemoryStateStorage
@@ -135,5 +139,11 @@ module.exports = {
135
139
 
136
140
  // ANALYTICS
137
141
  onInteractionHandler,
138
- GA4
142
+ GA4,
143
+
144
+ // TRANSCRIPTS
145
+ extractText,
146
+ htmlBodyFromTranscript,
147
+ textBodyFromTranscript,
148
+ transcriptFromHistory
139
149
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wingbot",
3
- "version": "3.46.0-alpha.8",
3
+ "version": "3.46.0",
4
4
  "description": "Enterprise Messaging Bot Conversation Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/src/BotApp.js CHANGED
@@ -104,6 +104,14 @@ class BotApp {
104
104
  this._logger = options.log || console;
105
105
  this._textFilter = options.textFilter;
106
106
 
107
+ this._preHeatCalled = false;
108
+ this._preHeat = [
109
+ this._logger,
110
+ chatLogStorage,
111
+ processorOptions.stateStorage,
112
+ processorOptions.tokenStorage
113
+ ].filter((s) => s && typeof s.preHeat === 'function');
114
+
107
115
  let { apiUrl } = options;
108
116
 
109
117
  if (!apiUrl) apiUrl = DEFAULT_API_URL;
@@ -204,6 +212,10 @@ class BotApp {
204
212
  analyticsStorage.setDefaultLogger(log);
205
213
  }
206
214
 
215
+ if (typeof analyticsStorage.preHeat === 'function') {
216
+ this._preHeat.push(analyticsStorage);
217
+ }
218
+
207
219
  // @ts-ignore
208
220
  const { snapshot = null, botId = null } = this._bot;
209
221
 
@@ -440,6 +452,15 @@ class BotApp {
440
452
  return this._errorResponse('Missing authentication header', 401);
441
453
  }
442
454
 
455
+ let preHeatPromise = null;
456
+ if (!this._preHeatCalled) {
457
+ preHeatPromise = Promise.all(
458
+ this._preHeat.map((p) => Promise.resolve(p.preHeat())
459
+ .catch((e) => this._logger.error('BotApp: preHeat failed', e)))
460
+ );
461
+ this._preHeatCalled = true;
462
+ }
463
+
443
464
  let sha1;
444
465
  let appId;
445
466
  let secret;
@@ -449,6 +470,7 @@ class BotApp {
449
470
  // @ts-ignore
450
471
  ({ sha1, appId } = await this._verify(token, secret));
451
472
  } catch (e) {
473
+ await Promise.resolve(preHeatPromise);
452
474
  return this._errorResponse(`Failed to verify token: ${e.message}`, 403);
453
475
  }
454
476
 
@@ -457,22 +479,28 @@ class BotApp {
457
479
  .digest('hex');
458
480
 
459
481
  if (sha1 !== bodySha1) {
482
+ await Promise.resolve(preHeatPromise);
460
483
  return this._errorResponse(`SHA1 does not match. Got in token: '${sha1}'`, 403);
461
484
  }
462
485
 
463
- const body = JSON.parse(rawBody);
464
-
465
- const entry = await this._processEntries(appId, secret, body.entry, rawHeaders);
486
+ try {
487
+ const body = JSON.parse(rawBody);
466
488
 
467
- return {
468
- statusCode: 200,
469
- body: JSON.stringify({
470
- entry
471
- }),
472
- headers: {
473
- 'Content-Type': 'application/json'
474
- }
475
- };
489
+ const entry = await this._processEntries(appId, secret, body.entry, rawHeaders);
490
+ await Promise.resolve(preHeatPromise);
491
+ return {
492
+ statusCode: 200,
493
+ body: JSON.stringify({
494
+ entry
495
+ }),
496
+ headers: {
497
+ 'Content-Type': 'application/json'
498
+ }
499
+ };
500
+ } catch (e) {
501
+ await Promise.resolve(preHeatPromise);
502
+ throw e;
503
+ }
476
504
  }
477
505
 
478
506
  async _processEntries (appId, secret, entry = [], headers = {}) {
package/src/Processor.js CHANGED
@@ -62,6 +62,7 @@ const { mergeState, isUserInteraction } = require('./utils/stateVariables');
62
62
  * @prop {TrackingEvent[]} events
63
63
  * @prop {ResponseFlag|null} flag
64
64
  * @prop {boolean} nonInteractive
65
+ * @prop {string[]} responseTexts
65
66
  */
66
67
 
67
68
  /**
@@ -434,6 +435,7 @@ class Processor extends EventEmitter {
434
435
  const { events = [] } = messageSender.tracking;
435
436
 
436
437
  const event = {
438
+ responseTexts: messageSender.responseTexts,
437
439
  req,
438
440
  actions,
439
441
  lastAction,
@@ -6,6 +6,7 @@
6
6
  const ai = require('./Ai');
7
7
  const { FEATURE_PHRASES, FEATURE_TRACKING } = require('./features');
8
8
  const { ResponseFlag } = require('./analytics/consts');
9
+ const extractText = require('./transcript/extractText');
9
10
 
10
11
  /** @typedef {import('./Request')} Request */
11
12
  /** @typedef {import('./Responder')} Responder */
@@ -122,8 +123,12 @@ class ReturnSender {
122
123
  events: []
123
124
  };
124
125
 
126
+ this._responseTexts = [];
127
+
125
128
  this._intentsAndEntities = [];
126
129
 
130
+ this._confidentInput = false;
131
+
127
132
  /**
128
133
  * @type {Function}
129
134
  * @private
@@ -147,6 +152,18 @@ class ReturnSender {
147
152
  return this._simulatesOptIn;
148
153
  }
149
154
 
155
+ /**
156
+ * @returns {string[]}
157
+ */
158
+ get responseTexts () {
159
+ const filter = this._confidentInput
160
+ ? this.confidentInputFilter
161
+ : this.textFilter;
162
+
163
+ return this._responseTexts
164
+ .map((t) => filter(t));
165
+ }
166
+
150
167
  _gotAnotherEvent () {
151
168
  if (this._gotAnotherEventDefer) {
152
169
  this._gotAnotherEventDefer();
@@ -379,6 +396,10 @@ class ReturnSender {
379
396
  return;
380
397
  }
381
398
 
399
+ const text = extractText(payload);
400
+ if (text) {
401
+ this._responseTexts.push(text);
402
+ }
382
403
  this._queue.push(payload);
383
404
  this._gotAnotherEvent();
384
405
 
@@ -509,7 +530,7 @@ class ReturnSender {
509
530
  async finished (req = null, res = null, err = null, reportError = console.error) {
510
531
  this._finish(req);
511
532
  const meta = this._createMeta(req, res);
512
- const confidentInput = req && req.isConfidentInput();
533
+ this._confidentInput = !!req && req.isConfidentInput();
513
534
  let error = err;
514
535
  try {
515
536
  await this._promise;
@@ -526,7 +547,7 @@ class ReturnSender {
526
547
  const processedEvent = req
527
548
  ? req.event
528
549
  : this._incommingMessage;
529
- let incomming = this._filterMessage(processedEvent, confidentInput, req);
550
+ let incomming = this._filterMessage(processedEvent, this._confidentInput, req);
530
551
 
531
552
  if (processedEvent !== this._incommingMessage) {
532
553
  incomming = {
@@ -32,7 +32,6 @@ const {
32
32
  * @prop {string} [label]
33
33
  * @prop {number} [value]
34
34
  * @prop {string} [lang]
35
- * @prop {number} [sessionStart]
36
35
  */
37
36
 
38
37
  /**
@@ -53,7 +52,6 @@ const {
53
52
  * @prop {boolean} withUser
54
53
  * @prop {string} [userId]
55
54
  * @prop {number} [feedback]
56
- * @prop {number} sessionDuration
57
55
  * @prop {string} [winnerAction]
58
56
  * @prop {string} [winnerIntent]
59
57
  * @prop {string[]|string} [winnerEntities]
@@ -64,8 +62,6 @@ const {
64
62
  * @prop {string[]|string} [entities]
65
63
  * @prop {string[]|string} allActions
66
64
  * @prop {boolean} nonInteractive
67
- * @prop {string} [snapshot]
68
- * @prop {string} [botId]
69
65
  *
70
66
  * @typedef {Event & ConversationEventExtension} ConversationEvent
71
67
  */
@@ -75,13 +71,10 @@ const {
75
71
  * @prop {string} [lastAction]
76
72
  * @prop {string} [prevAction]
77
73
  * @prop {string} [skill]
78
- * @prop {number} sessionDuration
79
74
  * @prop {string[]|string} allActions
80
75
  * @prop {boolean} nonInteractive
81
76
  * @prop {boolean} isGoto
82
77
  * @prop {boolean} withUser
83
- * @prop {string} [snapshot]
84
- * @prop {string} [botId]
85
78
  *
86
79
  * @typedef {Event & PageViewEventExtension} PageViewEvent
87
80
  */
@@ -101,6 +94,8 @@ const {
101
94
  * @prop {number|null} [feedback]
102
95
  * @prop {string} [timeZone]
103
96
  * @prop {number} [sessionStart]
97
+ * @prop {number} [sessionDuration]
98
+ * @prop {string[]} [responseTexts]
104
99
  */
105
100
 
106
101
  /**
@@ -143,6 +138,7 @@ const {
143
138
  * @prop {boolean} [useDescriptiveCategories]
144
139
  * @prop {boolean} [useExtendedScalars]
145
140
  * @prop {boolean} [parallelSessionInsert]
141
+ * @prop {Function} [preHeat]
146
142
  */
147
143
 
148
144
  /**
@@ -242,7 +238,8 @@ function onInteractionHandler (
242
238
  skill,
243
239
  events,
244
240
  flag,
245
- nonInteractive
241
+ nonInteractive,
242
+ responseTexts
246
243
  }) {
247
244
  if (!enabled) {
248
245
  return;
@@ -297,7 +294,9 @@ function onInteractionHandler (
297
294
  didHandover,
298
295
  feedback,
299
296
  timeZone,
300
- sessionStart
297
+ sessionStart,
298
+ responseTexts,
299
+ sessionDuration: sessionTs - sessionStart
301
300
  };
302
301
 
303
302
  let sessionPromise;
@@ -380,12 +379,8 @@ function onInteractionHandler (
380
379
  isPostback,
381
380
  didHandover,
382
381
  withUser,
383
- sessionStart,
384
- sessionDuration: sessionTs - sessionStart,
385
382
  feedback,
386
383
  skill: useSkill,
387
- snapshot,
388
- botId,
389
384
  winnerAction,
390
385
  winnerIntent,
391
386
  winnerEntities: asArray(winnerEntities.map((e) => e.entity)),
@@ -414,7 +409,6 @@ function onInteractionHandler (
414
409
  prevAction: lastAction,
415
410
  skill: useSkill,
416
411
  isGoto: false,
417
- sessionDuration: timestamp - sessionStart,
418
412
  withUser,
419
413
  ...langsExtension,
420
414
  ...(hasExtendedEvents ? {} : actionMeta)
@@ -435,7 +429,7 @@ function onInteractionHandler (
435
429
  prevAction,
436
430
  skill: useSkill,
437
431
  isGoto: true,
438
- sessionDuration: timestamp - sessionStart,
432
+ withUser,
439
433
  ...langsExtension
440
434
  };
441
435
 
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @author David Menger
3
+ */
4
+ 'use strict';
5
+
6
+ /**
7
+ * @class MemoryChatLogStorage
8
+ */
9
+ class MemoryChatLogStorage {
10
+
11
+ constructor () {
12
+ this._logs = new Map();
13
+ }
14
+
15
+ /**
16
+ * Interate history
17
+ * all limits are inclusive
18
+ *
19
+ * @param {string} senderId
20
+ * @param {string} pageId
21
+ * @param {number} [limit]
22
+ * @param {number} [endAt] - iterate backwards to history
23
+ * @param {number} [startAt] - iterate forward to last interaction
24
+ * @returns {Promise<object[]>}
25
+ */
26
+ async getInteractions (senderId, pageId, limit = 10, endAt = null, startAt = null) { // eslint-disable-line max-len,no-unused-vars
27
+ const events = this._getEvents(senderId);
28
+ return events.slice(-limit);
29
+ }
30
+
31
+ /**
32
+ *
33
+ * @param {string} senderId
34
+ * @returns {object[]}
35
+ */
36
+ _getEvents (senderId) {
37
+ let events = this._logs.get(senderId);
38
+ if (!events) {
39
+ events = [];
40
+ this._logs.set(senderId, events);
41
+ }
42
+ return events;
43
+ }
44
+
45
+ /**
46
+ * Log single event
47
+ *
48
+ * @param {string} senderId
49
+ * @param {object[]} responses - list of sent responses
50
+ * @param {object} request - event request
51
+ * @param {object} [metadata] - request metadata
52
+ * @returns {void}
53
+ */
54
+ log (senderId, responses = [], request = {}, metadata = {}) {
55
+ const events = this._getEvents(senderId);
56
+ events.push({
57
+ senderId,
58
+ request,
59
+ responses,
60
+ ...metadata
61
+ });
62
+ }
63
+
64
+ error (error, senderId, sent, incomming, meta) {
65
+ return this.log(senderId, sent, incomming, { ...meta, error });
66
+ }
67
+
68
+ }
69
+
70
+ module.exports = MemoryChatLogStorage;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @author David Menger
3
+ */
4
+ 'use strict';
5
+
6
+ /**
7
+ * Extracts text from conversational event
8
+ *
9
+ * @param {object} payload
10
+ * @returns {string|null}
11
+ */
12
+ function extractText (payload) {
13
+
14
+ // text message
15
+ if (payload.message && payload.message.text) {
16
+ return payload.message.text;
17
+ }
18
+
19
+ // button message
20
+ if (payload.message && payload.message.attachment
21
+ && payload.message.attachment.type === 'template'
22
+ && payload.message.attachment.payload
23
+ && payload.message.attachment.payload.text) {
24
+
25
+ return payload.message.attachment.payload.text;
26
+ }
27
+
28
+ if (!payload.postback) {
29
+ return null;
30
+ }
31
+
32
+ // postback with title
33
+ if (payload.postback.title) {
34
+ return payload.postback.title;
35
+ }
36
+
37
+ if (payload.postback.payload && payload.postback.payload.action) {
38
+ return payload.postback.payload.action;
39
+ }
40
+
41
+ if (typeof payload.postback.payload !== 'string') {
42
+ return null;
43
+ }
44
+
45
+ if (payload.postback.payload[0] === '{') {
46
+ const pl = JSON.parse(payload.postback.payload);
47
+
48
+ return pl.action;
49
+ }
50
+
51
+ return payload.postback.payload;
52
+ }
53
+
54
+ module.exports = extractText;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @author David Menger
3
+ */
4
+ 'use strict';
5
+
6
+ /** @typedef {import('./transcriptFromHistory').Transcript} Transcript */
7
+
8
+ /**
9
+ * @param {Transcript[]} transcript
10
+ * @param {string} [userSide]
11
+ * @param {string} [botSide]
12
+ * @returns {string}
13
+ */
14
+ function htmlBodyFromTranscript (transcript, userSide = 'User', botSide = 'Bot') {
15
+
16
+ return transcript
17
+ .map((msg) => `<b>${msg.fromBot ? botSide : userSide}:</b> ${msg.text}`)
18
+ .join('<br />');
19
+ }
20
+
21
+ module.exports = htmlBodyFromTranscript;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @author David Menger
3
+ */
4
+ 'use strict';
5
+
6
+ /** @typedef {import('./transcriptFromHistory').Transcript} Transcript */
7
+
8
+ /**
9
+ * @param {Transcript[]} transcript
10
+ * @param {string} [userSide]
11
+ * @param {string} [botSide]
12
+ * @returns {string}
13
+ */
14
+ function textBodyFromTranscript (transcript, userSide = 'User', botSide = 'Bot') {
15
+
16
+ return transcript
17
+ .map((msg, i) => `${msg.fromBot ? ' <' : `${i > 0 ? '\n' : ''}# >`} ${msg.fromBot ? botSide : userSide}: ${msg.text}`)
18
+ .join('\n');
19
+ }
20
+
21
+ module.exports = textBodyFromTranscript;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @author David Menger
3
+ */
4
+ 'use strict';
5
+
6
+ const extractText = require('./extractText');
7
+
8
+ /**
9
+ * @callback GetInteractions
10
+ * @param {string} senderId
11
+ * @param {string} pageId
12
+ * @param {number} limit
13
+ * @returns {Promise<object[]>}
14
+ */
15
+
16
+ /**
17
+ * @typedef {object} IChatStorage
18
+ * @prop {GetInteractions} getInteractions
19
+ */
20
+
21
+ /**
22
+ * @typedef {object} Transcript
23
+ * @prop {string} text
24
+ * @prop {boolean} fromBot
25
+ */
26
+
27
+ /**
28
+ *
29
+ * @param {IChatStorage} chatLogStorage
30
+ * @param {string} senderId
31
+ * @param {string} pageId
32
+ * @param {number} limit
33
+ * @returns {Promise<Transcript[]>}
34
+ */
35
+ async function transcriptFromHistory (chatLogStorage, senderId, pageId, limit = 20) {
36
+ const data = await chatLogStorage.getInteractions(senderId, pageId, limit);
37
+
38
+ return data
39
+ .map((turn) => {
40
+ const { request, responses = [] } = turn;
41
+
42
+ return [
43
+ { fromBot: false, text: extractText(request) },
44
+ ...responses
45
+ .map((response) => ({ fromBot: true, text: extractText(response) }))
46
+ ];
47
+ })
48
+ .reduce((ret, arr) => [...ret, ...arr], [])
49
+ .filter((ret) => !!ret.text);
50
+ }
51
+
52
+ module.exports = transcriptFromHistory;