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 +2 -2
- package/src/Processor.js +2 -0
- package/src/ReturnSender.js +23 -2
- package/src/analytics/consts.js +12 -4
- package/src/analytics/onInteractionHandler.js +148 -58
- package/src/tools/MemoryChatLogStorage.js +70 -0
- package/src/transcript/extractText.js +54 -0
- package/src/transcript/htmlBodyFromTranscript.js +21 -0
- package/src/transcript/textBodyFromTranscript.js +21 -0
- package/src/transcript/transcriptFromHistory.js +52 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wingbot",
|
|
3
|
-
"version": "3.46.0-alpha.
|
|
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
|
|
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,
|
package/src/ReturnSender.js
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
550
|
+
let incomming = this._filterMessage(processedEvent, this._confidentInput, req);
|
|
530
551
|
|
|
531
552
|
if (processedEvent !== this._incommingMessage) {
|
|
532
553
|
incomming = {
|
package/src/analytics/consts.js
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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 {
|
|
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 ?
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
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
|
|
296
|
-
sessionStart,
|
|
297
|
-
sessionDuration: sessionTs - sessionStart,
|
|
380
|
+
withUser,
|
|
298
381
|
feedback,
|
|
299
|
-
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
|
-
|
|
323
|
-
|
|
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.
|
|
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
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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;
|