wingbot 3.46.0-alpha.1 → 3.46.0-alpha.11

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.46.0-alpha.1",
3
+ "version": "3.46.0-alpha.11",
4
4
  "description": "Enterprise Messaging Bot Conversation Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -9,7 +9,7 @@
9
9
  "doc": "npm run doc:gql && node ./bin/makeApiDoc.js && cpy ./CHANGELOG.md ./doc && gitbook install ./doc && gitbook build ./doc && rimraf -rf ./docs && rimraf --rf ./doc/CHANGELOG.md && move-cli ./doc/_book ./docs",
10
10
  "test": "npm run test:lint && npm run test:coverage && npm run test:coverage:threshold",
11
11
  "test:coverage": "nyc --reporter=html mocha ./test && nyc report",
12
- "test:coverage:threshold": "nyc check-coverage --lines 90 --functions 90 --branches 80",
12
+ "test:coverage:threshold": "nyc check-coverage --lines 90 --functions 89 --branches 80",
13
13
  "test:backend": "mocha ./test",
14
14
  "test:lint": "eslint --ext .js src test *.js plugins"
15
15
  },
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 = {
@@ -42,6 +42,10 @@ const TrackingType = { // max length 12
42
42
  * @enum {string} TrackingCategory
43
43
  */
44
44
  const TrackingCategory = { // max length 3
45
+ // PAGE_VIEW: 'page_view'
46
+ PAGE_VIEW_FIRST: 'pf',
47
+ PAGE_VIEW_SUBSEQUENT: 'pp',
48
+
45
49
  // CONVERSATION_EVENT: 'conversation'
46
50
  STICKER: 'sti',
47
51
  IMAGE: 'img',
@@ -58,18 +62,22 @@ const TrackingCategory = { // max length 3
58
62
 
59
63
  // TRAINING: 'train'
60
64
  INTENT_DETECTION: 'int',
61
- HANDOVER_OCCURRED: 'hum',
62
65
  DISAMBIGUATION_SELECTED: 'dis',
63
66
  DISAMBIGUATION_OFFERED: 'dio',
64
67
 
65
68
  // REPORT: 'report'
66
- REPORT_FEEDBACK: 'fdb'
69
+ REPORT_FEEDBACK: 'fdb',
70
+ HANDOVER_OCCURRED: 'hum'
67
71
  };
68
72
 
69
73
  /**
70
74
  * @type {Object<TrackingCategory,string>}
71
75
  */
72
76
  const CATEGORY_LABELS = {
77
+ // PAGE_VIEW: 'page_view'
78
+ [TrackingCategory.PAGE_VIEW_FIRST]: 'Bot: Interaction',
79
+ [TrackingCategory.PAGE_VIEW_SUBSEQUENT]: 'Bot: Sub-interaction',
80
+
73
81
  // CONVERSATION_EVENT: 'conversation'
74
82
  [TrackingCategory.STICKER]: 'User: Sticker',
75
83
  [TrackingCategory.IMAGE]: 'User: Image',
@@ -86,12 +94,12 @@ const CATEGORY_LABELS = {
86
94
 
87
95
  // TRAINING: 'train'
88
96
  [TrackingCategory.INTENT_DETECTION]: 'Intent: Detection',
89
- [TrackingCategory.HANDOVER_OCCURRED]: 'Bot: Handover out',
90
97
  [TrackingCategory.DISAMBIGUATION_SELECTED]: 'Disambiguation: selected',
91
98
  [TrackingCategory.DISAMBIGUATION_OFFERED]: 'Disambiguation: offered',
92
99
 
93
100
  // REPORT: 'report'
94
- [TrackingCategory.REPORT_FEEDBACK]: 'User: Feedback'
101
+ [TrackingCategory.REPORT_FEEDBACK]: 'User: Feedback',
102
+ [TrackingCategory.HANDOVER_OCCURRED]: 'Bot: Handover out'
95
103
  };
96
104
 
97
105
  module.exports = {
@@ -31,6 +31,56 @@ const {
31
31
  * @prop {string} [action]
32
32
  * @prop {string} [label]
33
33
  * @prop {number} [value]
34
+ * @prop {string} [lang]
35
+ */
36
+
37
+ /**
38
+ * @typedef {object} ConversationEventExtension
39
+ * @prop {string} [lastAction]
40
+ * @prop {string} [skill]
41
+ * @prop {string} [text]
42
+ * @prop {string} [expected]
43
+ * @prop {boolean} expectedTaken
44
+ * @prop {boolean} isContextUpdate
45
+ * @prop {boolean} isAttachment
46
+ * @prop {boolean} isNotification
47
+ * @prop {boolean} isQuickReply
48
+ * @prop {boolean} isPassThread
49
+ * @prop {boolean} isPostback
50
+ * @prop {boolean} isText
51
+ * @prop {boolean} didHandover
52
+ * @prop {boolean} withUser
53
+ * @prop {string} [userId]
54
+ * @prop {number} [feedback]
55
+ * @prop {string} [winnerAction]
56
+ * @prop {string} [winnerIntent]
57
+ * @prop {string[]|string} [winnerEntities]
58
+ * @prop {number} [winnerScore]
59
+ * @prop {boolean} [winnerTaken]
60
+ * @prop {string} [intent]
61
+ * @prop {number} [intentScore]
62
+ * @prop {string[]|string} [entities]
63
+ * @prop {string[]|string} allActions
64
+ * @prop {boolean} nonInteractive
65
+ *
66
+ * @typedef {Event & ConversationEventExtension} ConversationEvent
67
+ */
68
+
69
+ /**
70
+ * @typedef {object} PageViewEventExtension
71
+ * @prop {string} [lastAction]
72
+ * @prop {string} [prevAction]
73
+ * @prop {string} [skill]
74
+ * @prop {string[]|string} allActions
75
+ * @prop {boolean} nonInteractive
76
+ * @prop {boolean} isGoto
77
+ * @prop {boolean} withUser
78
+ *
79
+ * @typedef {Event & PageViewEventExtension} PageViewEvent
80
+ */
81
+
82
+ /**
83
+ * @typedef {ConversationEvent | Event | PageViewEvent} TrackingEvent
34
84
  */
35
85
 
36
86
  /**
@@ -40,6 +90,12 @@ const {
40
90
  * @prop {string} [action]
41
91
  * @prop {string} [snapshot]
42
92
  * @prop {string} [botId]
93
+ * @prop {boolean} [didHandover]
94
+ * @prop {number|null} [feedback]
95
+ * @prop {string} [timeZone]
96
+ * @prop {number} [sessionStart]
97
+ * @prop {number} [sessionDuration]
98
+ * @prop {string[]} [responseTexts]
43
99
  */
44
100
 
45
101
  /**
@@ -50,7 +106,6 @@ const {
50
106
  * @param {SessionMetadata} [metadata]
51
107
  * @param {number} [ts]
52
108
  * @param {boolean} [nonInteractive]
53
- * @param {string} [timeZone]
54
109
  * @returns {Promise}
55
110
  */
56
111
 
@@ -59,12 +114,12 @@ const {
59
114
  * @param {string} pageId
60
115
  * @param {string} senderId
61
116
  * @param {string} sessionId
62
- * @param {Event[]} events
117
+ * @param {TrackingEvent[]} events
63
118
  * @param {GAUser} [user]
64
119
  * @param {number} [ts]
65
120
  * @param {boolean} [nonInteractive]
66
121
  * @param {boolean} [sessionStarted]
67
- * @param {string} [timeZone]
122
+ * @param {SessionMetadata} [metadata]
68
123
  * @returns {Promise}
69
124
  */
70
125
 
@@ -82,6 +137,7 @@ const {
82
137
  * @prop {boolean} [supportsArrays]
83
138
  * @prop {boolean} [useDescriptiveCategories]
84
139
  * @prop {boolean} [useExtendedScalars]
140
+ * @prop {boolean} [parallelSessionInsert]
85
141
  */
86
142
 
87
143
  /**
@@ -98,7 +154,7 @@ const {
98
154
 
99
155
  /**
100
156
  * @typedef {object} TrackingEvents
101
- * @prop {Event[]} events
157
+ * @prop {TrackingEvent[]} events
102
158
  */
103
159
 
104
160
  /**
@@ -160,13 +216,14 @@ function onInteractionHandler (
160
216
  supportsArrays = false,
161
217
  useExtendedScalars = false,
162
218
  hasExtendedEvents = false,
163
- useDescriptiveCategories = true
219
+ useDescriptiveCategories = true,
220
+ parallelSessionInsert = false
164
221
  } = analyticsStorage;
165
222
 
166
223
  const asArray = (data = []) => (supportsArrays ? data : data.join(','));
167
224
  const asCategory = (cat) => (useDescriptiveCategories && CATEGORY_LABELS[cat]) || cat;
168
225
  const noneAction = useExtendedScalars ? null : '(none)';
169
- const noneValue = useExtendedScalars ? -1 : 0;
226
+ const noneValue = useExtendedScalars ? null : 0;
170
227
 
171
228
  /**
172
229
  * @param {InteractionEvent} params
@@ -180,7 +237,8 @@ function onInteractionHandler (
180
237
  skill,
181
238
  events,
182
239
  flag,
183
- nonInteractive
240
+ nonInteractive,
241
+ responseTexts
184
242
  }) {
185
243
  if (!enabled) {
186
244
  return;
@@ -201,26 +259,60 @@ function onInteractionHandler (
201
259
  lang
202
260
  } = req.state;
203
261
 
262
+ const trackEvents = [];
263
+
204
264
  const [action = noneAction, ...otherActions] = actions;
205
265
 
206
- if (createSession) {
207
- const metadata = {
208
- sessionCount,
209
- lang,
210
- action,
211
- snapshot,
212
- botId
213
- };
266
+ const feedbackEvent = events.find((e) => e.type === TrackingType.REPORT
267
+ && e.category === TrackingCategory.REPORT_FEEDBACK);
268
+ const feedback = feedbackEvent
269
+ ? feedbackEvent.value
270
+ : noneValue;
271
+ let didHandover = flag === ResponseFlag.HANDOVER;
272
+ const hasHandoverEvent = events
273
+ .some((e) => e.category === TrackingCategory.HANDOVER_OCCURRED);
274
+
275
+ if (didHandover && !hasHandoverEvent) {
276
+ trackEvents.push({
277
+ type: TrackingType.REPORT,
278
+ category: asCategory(TrackingCategory.HANDOVER_OCCURRED),
279
+ action: null,
280
+ label: null,
281
+ value: noneValue
282
+ });
283
+ } else if (hasHandoverEvent) {
284
+ didHandover = true;
285
+ }
286
+
287
+ const metadata = {
288
+ sessionCount,
289
+ lang,
290
+ action,
291
+ snapshot,
292
+ botId,
293
+ didHandover,
294
+ feedback,
295
+ timeZone,
296
+ sessionStart,
297
+ responseTexts,
298
+ sessionDuration: sessionTs - sessionStart
299
+ };
214
300
 
215
- await analyticsStorage.createUserSession(
301
+ let sessionPromise;
302
+ if (createSession) {
303
+ sessionPromise = analyticsStorage.createUserSession(
216
304
  pageId,
217
305
  senderId,
218
306
  sessionId,
219
307
  metadata,
220
308
  timestamp,
221
- nonInteractive,
222
- timeZone
309
+ nonInteractive
223
310
  );
311
+
312
+ if (!parallelSessionInsert) {
313
+ await sessionPromise;
314
+ sessionPromise = null;
315
+ }
224
316
  }
225
317
 
226
318
  const [{
@@ -233,6 +325,7 @@ function onInteractionHandler (
233
325
  : anonymize(
234
326
  replaceDiacritics(req.text()).replace(/\s+/g, ' ').toLowerCase().trim()
235
327
  );
328
+ const useSkill = (skill && webalize(skill)) || noneAction;
236
329
 
237
330
  let winnerAction = '';
238
331
  let winnerScore = 0;
@@ -253,14 +346,6 @@ function onInteractionHandler (
253
346
  }
254
347
 
255
348
  const expected = req.expected() ? req.expected().action : '';
256
-
257
- const feedbackEvent = events.find((e) => e.type === TrackingType.REPORT
258
- && e.category === TrackingCategory.REPORT_FEEDBACK);
259
- const feedback = feedbackEvent
260
- ? feedbackEvent.value
261
- : noneValue;
262
- const didHandover = flag === ResponseFlag.HANDOVER;
263
-
264
349
  const user = userExtractor(req.state);
265
350
 
266
351
  const isContextUpdate = req.isSetContext();
@@ -274,12 +359,12 @@ function onInteractionHandler (
274
359
  const allActions = asArray(actions);
275
360
  const requestAction = req.action();
276
361
 
277
- const trackEvents = [];
278
-
279
362
  const langsExtension = hasExtendedEvents
280
363
  ? { lang }
281
364
  : { cd1: lang };
282
365
 
366
+ const withUser = user !== null && !!user.id;
367
+
283
368
  const actionMeta = {
284
369
  requestAction: req.action() || noneAction,
285
370
  expected,
@@ -292,13 +377,9 @@ function onInteractionHandler (
292
377
  isText,
293
378
  isPostback,
294
379
  didHandover,
295
- withUser: user !== null && !!user.id,
296
- sessionStart,
297
- sessionDuration: sessionTs - sessionStart,
380
+ withUser,
298
381
  feedback,
299
- skill: webalize(skill),
300
- snapshot,
301
- botId,
382
+ skill: useSkill,
302
383
  winnerAction,
303
384
  winnerIntent,
304
385
  winnerEntities: asArray(winnerEntities.map((e) => e.entity)),
@@ -311,16 +392,24 @@ function onInteractionHandler (
311
392
  allActions
312
393
  };
313
394
 
395
+ const notHandled = actions.some((a) => a.match(/\*$/)) && !req.isQuickReply();
396
+ const value = notHandled ? 1 : 0;
397
+
314
398
  trackEvents.push({
315
399
  type: TrackingType.PAGE_VIEW,
400
+ category: asCategory(TrackingCategory.PAGE_VIEW_FIRST),
316
401
  action,
402
+ label: (isText || isQuickReply ? text : null),
403
+ value,
317
404
  allActions,
318
405
  nonInteractive,
319
406
  lastAction,
407
+ // @ts-ignore
320
408
  prevAction: lastAction,
321
- skill,
322
- lang,
323
- cd1: req.state.lang,
409
+ skill: useSkill,
410
+ isGoto: false,
411
+ withUser,
412
+ ...langsExtension,
324
413
  ...(hasExtendedEvents ? {} : actionMeta)
325
414
  });
326
415
 
@@ -330,12 +419,14 @@ function onInteractionHandler (
330
419
  ...otherActions.map((a) => {
331
420
  const r = {
332
421
  type: TrackingType.PAGE_VIEW,
422
+ category: asCategory(TrackingCategory.PAGE_VIEW_SUBSEQUENT),
333
423
  action: a,
424
+ value: 0,
334
425
  allActions,
335
426
  nonInteractive: false,
336
427
  lastAction,
337
428
  prevAction,
338
- skill,
429
+ skill: useSkill,
339
430
  isGoto: true,
340
431
  ...langsExtension
341
432
  };
@@ -347,14 +438,14 @@ function onInteractionHandler (
347
438
 
348
439
  trackEvents.push(
349
440
  ...events.map(({
350
- type, category, action: eventAction, label, value
441
+ type, category, action: eventAction, label, value: eVal
351
442
  }) => ({
352
443
  lastAction,
353
444
  type,
354
- category,
445
+ category: asCategory(category),
355
446
  action: eventAction,
356
447
  label,
357
- value,
448
+ value: eVal,
358
449
  ...langsExtension
359
450
  }))
360
451
  );
@@ -375,11 +466,8 @@ function onInteractionHandler (
375
466
  });
376
467
  }
377
468
 
378
- const notHandled = actions.some((a) => a.match(/\*$/)) && !req.isQuickReply();
379
-
380
469
  let actionCategory;
381
470
  let label = noneAction;
382
- const value = notHandled ? 1 : 0;
383
471
 
384
472
  if (isPassThread) {
385
473
  actionCategory = TrackingCategory.HANDOVER_TO_BOT;
@@ -408,7 +496,7 @@ function onInteractionHandler (
408
496
  actionCategory = TrackingCategory.REFERRAL;
409
497
  } else if (isPostback) {
410
498
  actionCategory = TrackingCategory.POSTBACK_BUTTON;
411
- label = req.data.postback.title || '(unknown)';
499
+ label = req.event.postback.title || (useExtendedScalars ? null : '(unknown)');
412
500
  } else {
413
501
  actionCategory = TrackingCategory.OTHER;
414
502
  }
@@ -416,7 +504,6 @@ function onInteractionHandler (
416
504
  trackEvents.push({
417
505
  ...(analyticsStorage.hasExtendedEvents ? actionMeta : {}),
418
506
  type: TrackingType.CONVERSATION_EVENT,
419
- // @ts-ignore
420
507
  lastAction,
421
508
  category: asCategory(actionCategory),
422
509
  action,
@@ -426,18 +513,21 @@ function onInteractionHandler (
426
513
  });
427
514
  }
428
515
 
429
- await analyticsStorage.storeEvents(
430
- pageId,
431
- senderId,
432
- sessionId,
433
- // @ts-ignore
434
- trackEvents,
435
- user,
436
- timestamp,
437
- nonInteractive,
438
- createSession,
439
- timeZone
440
- );
516
+ await Promise.all([
517
+ analyticsStorage.storeEvents(
518
+ pageId,
519
+ senderId,
520
+ sessionId,
521
+ // @ts-ignore
522
+ trackEvents,
523
+ user,
524
+ timestamp,
525
+ nonInteractive,
526
+ createSession,
527
+ metadata
528
+ ),
529
+ sessionPromise
530
+ ]);
441
531
  } catch (e) {
442
532
  if (throwException) {
443
533
  throw e;
@@ -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;