wingbot 3.24.1 → 3.24.5

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.24.1",
3
+ "version": "3.24.5",
4
4
  "description": "Enterprise Messaging Bot Conversation Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {
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 || aiActions[0].aboveConfidence === false) {
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
- const intentIsNotTheSame = !first.intent || !second.intent
593
- || !first.intent.intent
594
- || first.intent.intent !== second.intent.intent;
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
- && intentIsNotTheSame) {
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 = req.entities.length * this.redundantEntityHandicap;
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
- req.entities,
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]
@@ -320,6 +322,7 @@ class Request {
320
322
  }
321
323
 
322
324
  const text = this.text();
325
+ const knownTexts = new Set();
323
326
 
324
327
  return (aiActions || this._aiActions)
325
328
  .filter((a) => a.title)
@@ -336,16 +339,22 @@ class Request {
336
339
 
337
340
  const entities = intent.entities || [];
338
341
 
339
- const templateData = {
342
+ let templateData = {
340
343
  ...stateData(this),
341
344
  ...getSetState(setState || {}, this),
342
345
  intent: intent.intent,
343
- entities,
344
- ...entities.reduceRight((o, e) => ({
345
- ...o,
346
- [`@${e.entity}`]: e.value
347
- }), {})
346
+ entities
348
347
  };
348
+ Object.keys(templateData)
349
+ .forEach((key) => {
350
+ if (key.match(/^@/)) {
351
+ delete templateData[key];
352
+ }
353
+ });
354
+ templateData = entities.reduceRight((o, e) => ({
355
+ ...o,
356
+ [`@${e.entity}`]: e.value
357
+ }), templateData);
349
358
 
350
359
  const textTemplate = typeof title === 'function'
351
360
  ? title
@@ -355,6 +364,10 @@ class Request {
355
364
  title: textTemplate(templateData),
356
365
  action: overrideAction || action,
357
366
  templateData,
367
+ ...entities.reduceRight((o, e) => ({
368
+ ...o,
369
+ [`@${e.entity}`]: e.value
370
+ }), {}),
358
371
  data: {
359
372
  ...data,
360
373
  _senderMeta: {
@@ -369,6 +382,13 @@ class Request {
369
382
  if (match) Object.assign(res, { match });
370
383
 
371
384
  return res;
385
+ })
386
+ .filter((qr) => {
387
+ if (knownTexts.has(qr.title)) {
388
+ return false;
389
+ }
390
+ knownTexts.add(qr.title);
391
+ return true;
372
392
  });
373
393
  }
374
394
 
@@ -1064,7 +1084,7 @@ class Request {
1064
1084
  }
1065
1085
 
1066
1086
  if (!res && this.state._expectedKeywords) {
1067
- res = this._actionByExpectedKeywords();
1087
+ res = this._actionByExpectedKeywords(this.state._expected);
1068
1088
  }
1069
1089
 
1070
1090
  if (!res && this.state._expected) {
@@ -1225,17 +1245,23 @@ class Request {
1225
1245
  return aiActions[0];
1226
1246
  }
1227
1247
 
1228
- _actionByExpectedKeywords () {
1229
- let res = null;
1248
+ _actionByExpectedKeywords (expected) {
1249
+ if (!this.state._expectedKeywords) {
1250
+ return null;
1251
+ }
1230
1252
 
1231
- if (this.state._expectedKeywords) {
1232
- const [payload] = this._resolveQuickReplyActions();
1233
- if (payload && payload.aboveConfidence) {
1234
- res = parseActionPayload(payload);
1235
- }
1253
+ const actions = this._resolveQuickReplyActions();
1254
+
1255
+ if (expected && Ai.ai.shouldDisambiguate(actions, true)) {
1256
+ return parseActionPayload(expected);
1236
1257
  }
1237
1258
 
1238
- return res;
1259
+ const [payload] = actions;
1260
+ if (!payload || !payload.aboveConfidence) {
1261
+ return null;
1262
+ }
1263
+
1264
+ return parseActionPayload(payload);
1239
1265
  }
1240
1266
 
1241
1267
  _resolveQuickReplyActions () {
package/src/Responder.js CHANGED
@@ -596,7 +596,7 @@ class Responder {
596
596
  }
597
597
 
598
598
  if (aiTitle) {
599
- Object.assign(push, { title: aiTitle });
599
+ Object.assign(push, { title: aiTitle, hasAiTitle: true });
600
600
  }
601
601
 
602
602
  this._addExpectedIntents([push]);
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
 
@@ -168,8 +168,13 @@ function getSetState (setState = {}, req, res = null, useState = null) {
168
168
  if (entity && (!rear || req.entities.some((e) => e.entity === entity))) {
169
169
  values = req.entities.filter((e) => e.entity === entity)
170
170
  .map((e) => e.value);
171
+ if (values.length === 0 && useState && useState[`@${entity}`]) {
172
+ values = [useState[`@${entity}`]];
173
+ }
171
174
  } else if (variable) {
172
175
  values = toArray(getValue(variable, state));
176
+ } else if (value === '' || value === null || value === undefined) {
177
+ values = [];
173
178
  } else {
174
179
  const useValue = typeof value === 'string'
175
180
  ? handlebars.compile(value)(stateData(req, res))
@@ -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 {object[]} expectedKeywords
275
+ * @param {ExpectedKeyword[]} expectedKeywords
236
276
  * @param {Request} req
237
277
  * @param {Ai} ai
238
- * @returns {object[]}
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(({ score: a }, { score: z }) => z - a);
351
+ found.sort(({ sort: a }, { sort: z }) => z - a);
293
352
 
294
353
  return found;
295
354
  }