wingbot 3.24.0-alpha.1 → 3.24.3
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 +1 -1
- package/src/Ai.js +82 -8
- package/src/AiMatching.js +5 -4
- package/src/Request.js +33 -15
- package/src/Responder.js +1 -1
- package/src/Tester.js +6 -0
- package/src/resolvers/postback.js +2 -2
- package/src/tools/routeToEvents.js +31 -1
- package/src/utils/customCondition.js +1 -0
- package/src/utils/getUpdate.js +4 -0
- package/src/utils/quickReplies.js +63 -4
package/package.json
CHANGED
package/src/Ai.js
CHANGED
|
@@ -363,7 +363,6 @@ class Ai {
|
|
|
363
363
|
return null;
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
-
const aboveConfidence = winningIntent.score >= this.confidence;
|
|
367
366
|
const usedEntities = this.matcher.parseEntitiesFromIntentRule(intent, true);
|
|
368
367
|
const setState = this._getSetStateForEntities(
|
|
369
368
|
usedEntities,
|
|
@@ -372,10 +371,56 @@ class Ai {
|
|
|
372
371
|
req.state
|
|
373
372
|
);
|
|
374
373
|
|
|
374
|
+
const alterScoreMax = (1 - ((1 - this.confidence) / 2));
|
|
375
|
+
const alterEntities = winningIntent.entities
|
|
376
|
+
.filter((e) => alterScoreMax >= e.score && e.alternatives && e.alternatives.length > 0);
|
|
377
|
+
|
|
378
|
+
let alternatives = [];
|
|
379
|
+
if (alterEntities.length === 1) {
|
|
380
|
+
const [alterEntity] = alterEntities;
|
|
381
|
+
alternatives = alterEntity.alternatives
|
|
382
|
+
.map((alternative) => {
|
|
383
|
+
if (alternative.value === alterEntity.value
|
|
384
|
+
&& alternative.entity === alterEntity.entity) {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
const reqEntities = req.entities
|
|
388
|
+
.map((reqEntity) => {
|
|
389
|
+
if (reqEntity.entity === alterEntity.entity
|
|
390
|
+
&& reqEntity.value === alterEntity.value) {
|
|
391
|
+
|
|
392
|
+
return alternative;
|
|
393
|
+
}
|
|
394
|
+
return reqEntity;
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
const winner = this.matcher.match(req, rules, stateless, reqEntities);
|
|
398
|
+
|
|
399
|
+
if (!winner || winner.score < this.threshold) {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const alterSetState = this._getSetStateForEntities(
|
|
404
|
+
this.matcher.parseEntitiesFromIntentRule(intent, true),
|
|
405
|
+
winner.entities,
|
|
406
|
+
reqEntities,
|
|
407
|
+
req.state
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
return {
|
|
411
|
+
...winner,
|
|
412
|
+
setState: alterSetState,
|
|
413
|
+
aboveConfidence: winner.score >= this.confidence
|
|
414
|
+
};
|
|
415
|
+
})
|
|
416
|
+
.filter((alt) => alt !== null);
|
|
417
|
+
}
|
|
418
|
+
|
|
375
419
|
return {
|
|
376
420
|
...winningIntent,
|
|
377
421
|
setState,
|
|
378
|
-
aboveConfidence
|
|
422
|
+
aboveConfidence: winningIntent.score >= this.confidence,
|
|
423
|
+
alternatives
|
|
379
424
|
};
|
|
380
425
|
}
|
|
381
426
|
|
|
@@ -570,13 +615,29 @@ class Ai {
|
|
|
570
615
|
/**
|
|
571
616
|
*
|
|
572
617
|
* @param {IntentAction[]} aiActions
|
|
618
|
+
* @param {boolean} [forQuickReplies]
|
|
573
619
|
* @returns {boolean}
|
|
574
620
|
*/
|
|
575
|
-
shouldDisambiguate (aiActions) {
|
|
576
|
-
if (aiActions.length === 0
|
|
621
|
+
shouldDisambiguate (aiActions, forQuickReplies = false) {
|
|
622
|
+
if (aiActions.length === 0
|
|
623
|
+
|| aiActions[0].aboveConfidence === false
|
|
624
|
+
|| (forQuickReplies && !aiActions[0].hasAiTitle)) {
|
|
625
|
+
|
|
577
626
|
return false;
|
|
578
627
|
}
|
|
579
628
|
|
|
629
|
+
// if (aiActions[0].intent) {
|
|
630
|
+
// const { entities = [] } = aiActions[0].intent;
|
|
631
|
+
// const [{ score = 0, alternatives = [] } = {}] = entities;
|
|
632
|
+
|
|
633
|
+
// if (entities.length === 1
|
|
634
|
+
// && score < (1 - ((1 - this.confidence) / 2))
|
|
635
|
+
// && alternatives.length) {
|
|
636
|
+
|
|
637
|
+
// return true;
|
|
638
|
+
// }
|
|
639
|
+
// }
|
|
640
|
+
|
|
580
641
|
// there will be no winner, if there are two different intents
|
|
581
642
|
if (aiActions.length > 1 && aiActions[1].aboveConfidence !== false) {
|
|
582
643
|
|
|
@@ -588,14 +649,27 @@ class Ai {
|
|
|
588
649
|
const margin = 1 - (secondScore / firstScore);
|
|
589
650
|
const bothHaveTitle = first.title && second.title;
|
|
590
651
|
const similarScore = margin < (1 - Ai.ai.confidence);
|
|
652
|
+
const hasAititles = !forQuickReplies || second.hasAiTitle;
|
|
591
653
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
654
|
+
let intentsDiffers = true;
|
|
655
|
+
if (first.intent && second.intent && first.intent.intent === second.intent.intent) {
|
|
656
|
+
let { entities: firstEntities = [] } = first.intent;
|
|
657
|
+
let { entities: secondEntities = [] } = second.intent;
|
|
658
|
+
|
|
659
|
+
if (firstEntities.length > secondEntities.length) {
|
|
660
|
+
[firstEntities, secondEntities] = [secondEntities, firstEntities];
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const entitiesDiffers = secondEntities.some((f) => !firstEntities
|
|
664
|
+
.some((s) => s.entity === f.entity && s.value === f.value));
|
|
665
|
+
|
|
666
|
+
intentsDiffers = entitiesDiffers;
|
|
667
|
+
}
|
|
595
668
|
|
|
596
669
|
if (bothHaveTitle
|
|
597
670
|
&& similarScore
|
|
598
|
-
&&
|
|
671
|
+
&& hasAititles
|
|
672
|
+
&& intentsDiffers) {
|
|
599
673
|
|
|
600
674
|
return true;
|
|
601
675
|
}
|
package/src/AiMatching.js
CHANGED
|
@@ -356,10 +356,11 @@ class AiMatching {
|
|
|
356
356
|
*
|
|
357
357
|
* @param {AIRequest} req
|
|
358
358
|
* @param {PreprocessorOutput} rule
|
|
359
|
-
* @param {boolean} stateless
|
|
359
|
+
* @param {boolean} [stateless]
|
|
360
|
+
* @param {Entity[]} [reqEntities]
|
|
360
361
|
* @returns {Intent|null}
|
|
361
362
|
*/
|
|
362
|
-
match (req, rule, stateless = false) {
|
|
363
|
+
match (req, rule, stateless = false, reqEntities = req.entities) {
|
|
363
364
|
const { regexps, intents, entities } = rule;
|
|
364
365
|
|
|
365
366
|
const noIntentHandicap = req.intents.length === 0 ? 0 : this.redundantIntentHandicap;
|
|
@@ -371,7 +372,7 @@ class AiMatching {
|
|
|
371
372
|
if (regexpScore === 0) {
|
|
372
373
|
return null;
|
|
373
374
|
}
|
|
374
|
-
const handicap =
|
|
375
|
+
const handicap = reqEntities.length * this.redundantEntityHandicap;
|
|
375
376
|
return {
|
|
376
377
|
intent: null,
|
|
377
378
|
entities: [],
|
|
@@ -381,7 +382,7 @@ class AiMatching {
|
|
|
381
382
|
const { score, handicap, matched } = this
|
|
382
383
|
._entityMatching(
|
|
383
384
|
entities,
|
|
384
|
-
|
|
385
|
+
reqEntities,
|
|
385
386
|
stateless || intents.length === 0
|
|
386
387
|
? {}
|
|
387
388
|
: req.state
|
package/src/Request.js
CHANGED
|
@@ -42,6 +42,7 @@ function makeTimestamp () {
|
|
|
42
42
|
* @prop {string} entity
|
|
43
43
|
* @prop {string} value
|
|
44
44
|
* @prop {number} score
|
|
45
|
+
* @prop {Entity[]} [alternatives]
|
|
45
46
|
*/
|
|
46
47
|
|
|
47
48
|
/**
|
|
@@ -70,6 +71,7 @@ function makeTimestamp () {
|
|
|
70
71
|
* @prop {object} [setState]
|
|
71
72
|
* @prop {boolean} [winner]
|
|
72
73
|
* @prop {string|Function} [title]
|
|
74
|
+
* @prop {boolean} [hasAiTitle]
|
|
73
75
|
* @prop {object} meta
|
|
74
76
|
* @prop {string} [meta.targetAppId]
|
|
75
77
|
* @prop {string|null} [meta.targetAction]
|
|
@@ -336,16 +338,22 @@ class Request {
|
|
|
336
338
|
|
|
337
339
|
const entities = intent.entities || [];
|
|
338
340
|
|
|
339
|
-
|
|
341
|
+
let templateData = {
|
|
340
342
|
...stateData(this),
|
|
341
343
|
...getSetState(setState || {}, this),
|
|
342
344
|
intent: intent.intent,
|
|
343
|
-
entities
|
|
344
|
-
...entities.reduceRight((o, e) => ({
|
|
345
|
-
...o,
|
|
346
|
-
[`@${e.entity}`]: e.value
|
|
347
|
-
}), {})
|
|
345
|
+
entities
|
|
348
346
|
};
|
|
347
|
+
Object.keys(templateData)
|
|
348
|
+
.forEach((key) => {
|
|
349
|
+
if (key.match(/^@/)) {
|
|
350
|
+
delete templateData[key];
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
templateData = entities.reduceRight((o, e) => ({
|
|
354
|
+
...o,
|
|
355
|
+
[`@${e.entity}`]: e.value
|
|
356
|
+
}), templateData);
|
|
349
357
|
|
|
350
358
|
const textTemplate = typeof title === 'function'
|
|
351
359
|
? title
|
|
@@ -355,6 +363,10 @@ class Request {
|
|
|
355
363
|
title: textTemplate(templateData),
|
|
356
364
|
action: overrideAction || action,
|
|
357
365
|
templateData,
|
|
366
|
+
...entities.reduceRight((o, e) => ({
|
|
367
|
+
...o,
|
|
368
|
+
[`@${e.entity}`]: e.value
|
|
369
|
+
}), {}),
|
|
358
370
|
data: {
|
|
359
371
|
...data,
|
|
360
372
|
_senderMeta: {
|
|
@@ -1064,7 +1076,7 @@ class Request {
|
|
|
1064
1076
|
}
|
|
1065
1077
|
|
|
1066
1078
|
if (!res && this.state._expectedKeywords) {
|
|
1067
|
-
res = this._actionByExpectedKeywords();
|
|
1079
|
+
res = this._actionByExpectedKeywords(this.state._expected);
|
|
1068
1080
|
}
|
|
1069
1081
|
|
|
1070
1082
|
if (!res && this.state._expected) {
|
|
@@ -1225,17 +1237,23 @@ class Request {
|
|
|
1225
1237
|
return aiActions[0];
|
|
1226
1238
|
}
|
|
1227
1239
|
|
|
1228
|
-
_actionByExpectedKeywords () {
|
|
1229
|
-
|
|
1240
|
+
_actionByExpectedKeywords (expected) {
|
|
1241
|
+
if (!this.state._expectedKeywords) {
|
|
1242
|
+
return null;
|
|
1243
|
+
}
|
|
1230
1244
|
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
}
|
|
1245
|
+
const actions = this._resolveQuickReplyActions();
|
|
1246
|
+
|
|
1247
|
+
if (expected && Ai.ai.shouldDisambiguate(actions, true)) {
|
|
1248
|
+
return parseActionPayload(expected);
|
|
1236
1249
|
}
|
|
1237
1250
|
|
|
1238
|
-
|
|
1251
|
+
const [payload] = actions;
|
|
1252
|
+
if (!payload || !payload.aboveConfidence) {
|
|
1253
|
+
return null;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
return parseActionPayload(payload);
|
|
1239
1257
|
}
|
|
1240
1258
|
|
|
1241
1259
|
_resolveQuickReplyActions () {
|
package/src/Responder.js
CHANGED
package/src/Tester.js
CHANGED
|
@@ -158,6 +158,12 @@ class Tester {
|
|
|
158
158
|
* @returns {Promise<any>}
|
|
159
159
|
*/
|
|
160
160
|
async processMessage (message, senderId = this.senderId, pageId = this.pageId, data = {}) {
|
|
161
|
+
if (!message.sender && !message.optin) {
|
|
162
|
+
Object.assign(message, {
|
|
163
|
+
sender: { id: senderId }
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
161
167
|
const messageSender = new ReturnSender({}, senderId, message, this.senderLogger);
|
|
162
168
|
messageSender.simulatesOptIn = true;
|
|
163
169
|
|
|
@@ -49,7 +49,7 @@ function postback (params, { linksMap, isLastIndex, allowForbiddenSnippetWords }
|
|
|
49
49
|
const request = {
|
|
50
50
|
sender: { id: req.senderId },
|
|
51
51
|
postback: {
|
|
52
|
-
action: res.toAbsoluteAction(action),
|
|
52
|
+
action: routeId ? res.toAbsoluteAction(action) : action,
|
|
53
53
|
data: data || {}
|
|
54
54
|
}
|
|
55
55
|
};
|
|
@@ -60,7 +60,7 @@ function postback (params, { linksMap, isLastIndex, allowForbiddenSnippetWords }
|
|
|
60
60
|
|
|
61
61
|
postBack(request);
|
|
62
62
|
|
|
63
|
-
return Router.END;
|
|
63
|
+
return routeId ? Router.END : ret;
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
66
|
|
|
@@ -56,13 +56,43 @@ async function routeToEvents (pageId, senderId, state, resolvers) {
|
|
|
56
56
|
]
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
let postback = null;
|
|
60
|
+
const postbackFn = (action, dat) => {
|
|
61
|
+
let payload;
|
|
62
|
+
let data;
|
|
63
|
+
if (typeof action === 'string') {
|
|
64
|
+
payload = action;
|
|
65
|
+
data = dat || {};
|
|
66
|
+
} else if (typeof action === 'object') {
|
|
67
|
+
payload = action.postback.action;
|
|
68
|
+
data = action.postback.data;
|
|
69
|
+
} else {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (data && Object.keys(data).length) {
|
|
74
|
+
payload = JSON.stringify({ action, data });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
postback = {
|
|
78
|
+
sender: { id: req.senderId },
|
|
79
|
+
postback: {
|
|
80
|
+
payload
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
await router.reduce(req, res, postbackFn);
|
|
60
86
|
await returnSender.finished(req, res);
|
|
61
87
|
|
|
62
88
|
delete res.newState._lastAction;
|
|
63
89
|
delete res.newState._lastVisitedPath;
|
|
64
90
|
delete res.newState.lastAction;
|
|
65
91
|
|
|
92
|
+
if (postback) {
|
|
93
|
+
returnSender.responses.push(postback);
|
|
94
|
+
}
|
|
95
|
+
|
|
66
96
|
return {
|
|
67
97
|
events: returnSender.responses,
|
|
68
98
|
setState: res.newState,
|
|
@@ -88,6 +88,7 @@ const ARRAY_LENGTH_OPERATORS = [
|
|
|
88
88
|
const compare = (variable, operator, value = undefined) => {
|
|
89
89
|
const isArrayLengthCompare = ARRAY_LENGTH_OPERATORS.includes(operator);
|
|
90
90
|
if (Array.isArray(variable) && !isArrayLengthCompare) {
|
|
91
|
+
// @todo if value === number && operator === '=='
|
|
91
92
|
return variable.some((variableElement) => compare(variableElement, operator, value));
|
|
92
93
|
}
|
|
93
94
|
|
package/src/utils/getUpdate.js
CHANGED
|
@@ -170,9 +170,13 @@ function getSetState (setState = {}, req, res = null, useState = null) {
|
|
|
170
170
|
.map((e) => e.value);
|
|
171
171
|
} else if (variable) {
|
|
172
172
|
values = toArray(getValue(variable, state));
|
|
173
|
+
} else if (value === '' || value === null || value === undefined) {
|
|
174
|
+
values = [];
|
|
173
175
|
} else {
|
|
174
176
|
const useValue = typeof value === 'string'
|
|
175
177
|
? handlebars.compile(value)(stateData(req, res))
|
|
178
|
+
.split(/(?<!\\),/g)
|
|
179
|
+
.map((v) => v.trim())
|
|
176
180
|
: value;
|
|
177
181
|
values = toArray(useValue);
|
|
178
182
|
}
|
|
@@ -11,6 +11,26 @@ const { checkSetState } = require('./stateVariables');
|
|
|
11
11
|
/** @typedef {import('../Request')} Request */
|
|
12
12
|
/** @typedef {import('../Ai')} Ai */
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {object} ExpectedKeyword
|
|
16
|
+
* @prop {string} action
|
|
17
|
+
* @prop {string} title
|
|
18
|
+
* @prop {null|string|string[]} match
|
|
19
|
+
* @prop {object} data
|
|
20
|
+
* @prop {boolean} [hasAiTitle]
|
|
21
|
+
* @prop {object} [setState]
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
*
|
|
26
|
+
* @param {string} action
|
|
27
|
+
* @param {string} title
|
|
28
|
+
* @param {RegExp|string|string[]} [matcher]
|
|
29
|
+
* @param {object} [payloadData]
|
|
30
|
+
* @param {object} [setState]
|
|
31
|
+
* @param {string} [aiTitle]
|
|
32
|
+
* @returns {ExpectedKeyword}
|
|
33
|
+
*/
|
|
14
34
|
function makeExpectedKeyword (
|
|
15
35
|
action, title, matcher = null, payloadData = {}, setState = null, aiTitle = null
|
|
16
36
|
) {
|
|
@@ -37,7 +57,7 @@ function makeExpectedKeyword (
|
|
|
37
57
|
};
|
|
38
58
|
|
|
39
59
|
if (setState) Object.assign(ret, { setState });
|
|
40
|
-
if (aiTitle) Object.assign(ret, { title: aiTitle });
|
|
60
|
+
if (aiTitle) Object.assign(ret, { title: aiTitle, hasAiTitle: true });
|
|
41
61
|
|
|
42
62
|
return ret;
|
|
43
63
|
}
|
|
@@ -229,13 +249,33 @@ function makeQuickReplies (replies, path = '', translate = (w) => w, quickReplyC
|
|
|
229
249
|
};
|
|
230
250
|
}
|
|
231
251
|
|
|
252
|
+
/** @typedef {import('../Request').Intent} Intent */
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* @typedef {object} QuickReplyAction
|
|
256
|
+
* @prop {boolean} aboveConfidence
|
|
257
|
+
*
|
|
258
|
+
* @prop {string} action
|
|
259
|
+
* @prop {string} title
|
|
260
|
+
* @prop {null|string|string[]} match
|
|
261
|
+
* @prop {object} data
|
|
262
|
+
* @prop {number} score
|
|
263
|
+
* @prop {number} sort
|
|
264
|
+
*
|
|
265
|
+
* @prop {string} [title]
|
|
266
|
+
* @prop {object} [setState]
|
|
267
|
+
*
|
|
268
|
+
* @prop {string[]} [_aiKeys]
|
|
269
|
+
* @prop {Intent} [intent]
|
|
270
|
+
*/
|
|
271
|
+
|
|
232
272
|
/**
|
|
233
273
|
*
|
|
234
274
|
* @ignore
|
|
235
|
-
* @param {
|
|
275
|
+
* @param {ExpectedKeyword[]} expectedKeywords
|
|
236
276
|
* @param {Request} req
|
|
237
277
|
* @param {Ai} ai
|
|
238
|
-
* @returns {
|
|
278
|
+
* @returns {QuickReplyAction[]}
|
|
239
279
|
*/
|
|
240
280
|
function quickReplyAction (expectedKeywords, req, ai) {
|
|
241
281
|
const text = req.text();
|
|
@@ -249,6 +289,8 @@ function quickReplyAction (expectedKeywords, req, ai) {
|
|
|
249
289
|
return [
|
|
250
290
|
{
|
|
251
291
|
...lowerCaseMatch[0],
|
|
292
|
+
score: 1,
|
|
293
|
+
sort: 1,
|
|
252
294
|
aboveConfidence: true
|
|
253
295
|
}
|
|
254
296
|
];
|
|
@@ -261,6 +303,8 @@ function quickReplyAction (expectedKeywords, req, ai) {
|
|
|
261
303
|
return exactMatch
|
|
262
304
|
.map((e) => ({
|
|
263
305
|
...e,
|
|
306
|
+
score: 1,
|
|
307
|
+
sort: 1,
|
|
264
308
|
aboveConfidence: true
|
|
265
309
|
}));
|
|
266
310
|
}
|
|
@@ -280,16 +324,31 @@ function quickReplyAction (expectedKeywords, req, ai) {
|
|
|
280
324
|
...keyword,
|
|
281
325
|
intent,
|
|
282
326
|
score,
|
|
327
|
+
sort: score,
|
|
283
328
|
_aiKeys,
|
|
284
329
|
aboveConfidence,
|
|
285
330
|
setState: keyword.setState
|
|
286
331
|
? { ...keyword.setState, ...setState }
|
|
287
332
|
: { ...setState }
|
|
288
333
|
});
|
|
334
|
+
|
|
335
|
+
for (const alternative of intent.alternatives) {
|
|
336
|
+
found.push({
|
|
337
|
+
...keyword,
|
|
338
|
+
intent: alternative,
|
|
339
|
+
score: alternative.score,
|
|
340
|
+
sort: alternative.score - 0.0001,
|
|
341
|
+
_aiKeys: Object.keys(alternative.setState),
|
|
342
|
+
aboveConfidence: alternative.aboveConfidence,
|
|
343
|
+
setState: keyword.setState
|
|
344
|
+
? { ...keyword.setState, ...alternative.setState }
|
|
345
|
+
: { ...alternative.setState }
|
|
346
|
+
});
|
|
347
|
+
}
|
|
289
348
|
}
|
|
290
349
|
});
|
|
291
350
|
|
|
292
|
-
found.sort(({
|
|
351
|
+
found.sort(({ sort: a }, { sort: z }) => z - a);
|
|
293
352
|
|
|
294
353
|
return found;
|
|
295
354
|
}
|