wingbot 3.37.2 → 3.38.0-alpha.1

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.37.2",
3
+ "version": "3.38.0-alpha.1",
4
4
  "description": "Enterprise Messaging Bot Conversation Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/src/Ai.js CHANGED
@@ -49,7 +49,7 @@ let uq = 1;
49
49
  */
50
50
  class Ai {
51
51
 
52
- constructor (matcher = new AiMatching()) {
52
+ constructor () {
53
53
  this._keyworders = new Map();
54
54
 
55
55
  /**
@@ -111,7 +111,7 @@ class Ai {
111
111
  *
112
112
  * @type {AiMatching}
113
113
  */
114
- this.matcher = matcher;
114
+ this.matcher = new AiMatching(this);
115
115
  }
116
116
 
117
117
  /**
package/src/AiMatching.js CHANGED
@@ -91,6 +91,11 @@ const COMPARE = {
91
91
  * @prop {object} [state]
92
92
  */
93
93
 
94
+ /**
95
+ * @typedef {object} ConfidenceProvider
96
+ * @prop {number} confidence
97
+ */
98
+
94
99
  /**
95
100
  * @class {AiMatching}
96
101
  *
@@ -98,7 +103,11 @@ const COMPARE = {
98
103
  */
99
104
  class AiMatching {
100
105
 
101
- constructor () {
106
+ /**
107
+ *
108
+ * @param {ConfidenceProvider} ai
109
+ */
110
+ constructor (ai = { confidence: 0.8 }) {
102
111
  /**
103
112
  * When the entity is optional, the final score should be little bit lower
104
113
  * (0.001 by default)
@@ -138,6 +147,8 @@ class AiMatching {
138
147
  * (1 by default)
139
148
  */
140
149
  this.stateEntityScore = 1;
150
+
151
+ this._ai = ai;
141
152
  }
142
153
 
143
154
  get redundantHandicap () {
@@ -447,11 +458,29 @@ class AiMatching {
447
458
  ? score - noIntentHandicap
448
459
  : (regexpScore + score) / 2;
449
460
 
461
+ const matchedEntitiesTextLength = matched.reduce((tot, entity) => (
462
+ typeof entity.end === 'number' && typeof entity.start === 'number' && tot !== null
463
+ ? (tot + (entity.end - entity.start))
464
+ : null
465
+ ), 0);
466
+
467
+ let finalScore = (baseScore - handicap)
468
+ * (this.multiMatchGain ** countOfAdditionalItems);
469
+
470
+ const textLength = req.text().length;
471
+
472
+ if (matchedEntitiesTextLength && textLength) {
473
+ const remainingScore = Math.max(0, Math.min(1, finalScore) - (
474
+ this._ai.confidence + this.redundantEntityHandicap
475
+ ));
476
+
477
+ finalScore -= (matchedEntitiesTextLength / textLength) * remainingScore;
478
+ }
479
+
450
480
  return {
451
481
  intent: null,
452
482
  entities: matched,
453
- score: (baseScore - handicap)
454
- * (this.multiMatchGain ** countOfAdditionalItems)
483
+ score: finalScore
455
484
  };
456
485
  }
457
486
 
@@ -5,7 +5,31 @@
5
5
 
6
6
  let handlebars;
7
7
  try {
8
+ // @ts-ignore
8
9
  handlebars = module.require('handlebars');
10
+
11
+ handlebars.registerHelper('lang', function langHelper (content) {
12
+ if (typeof content !== 'object' || !content) {
13
+ return content;
14
+ }
15
+
16
+ const { lang } = this;
17
+
18
+ if (content[lang]) {
19
+ return content[lang];
20
+ }
21
+
22
+ if (Array.isArray(content)) {
23
+ const entry = content.find((c) => c
24
+ && (!lang || c.l === lang || c.lang === lang) && (c.t || c.text));
25
+
26
+ if (entry) {
27
+ return entry.text || entry.t;
28
+ }
29
+ }
30
+
31
+ return content[Object.keys(content)[0]];
32
+ });
9
33
  } catch (er) {
10
34
  handlebars = { compile: (text) => () => text };
11
35
  }
@@ -16,6 +16,7 @@ const button = require('./button');
16
16
  const subscribtions = require('./subscribtions');
17
17
  const setState = require('./setState');
18
18
  const expectedInput = require('./expectedInput');
19
+ const skip = require('./skip');
19
20
 
20
21
  module.exports = {
21
22
  path,
@@ -30,5 +31,6 @@ module.exports = {
30
31
  button,
31
32
  subscribtions,
32
33
  setState,
33
- expectedInput
34
+ expectedInput,
35
+ skip
34
36
  };
@@ -195,9 +195,6 @@ function message (params, context = {}) {
195
195
  * @param {Responder} res
196
196
  */
197
197
  return (req, res) => {
198
- if (condition && !condition(req, res)) {
199
- return ret;
200
- }
201
198
  const data = stateData(req, res, configuration);
202
199
 
203
200
  // filter supported messages
@@ -216,30 +213,51 @@ function message (params, context = {}) {
216
213
  const text = textTemplate(data)
217
214
  .trim();
218
215
 
216
+ res.setData({ $this: text });
217
+ if (condition && !condition(req, res)) {
218
+ res.setData({ $this: null });
219
+ return ret;
220
+ }
221
+ res.setData({ $this: null });
222
+
219
223
  let sendReplies;
220
224
  if (quickReplies) {
221
- const okQuickReplies = quickReplies
222
- .filter((reply) => reply.condition(req, res));
223
-
224
- sendReplies = okQuickReplies
225
+ sendReplies = quickReplies
225
226
  .filter((reply) => reply.title
226
227
  || reply.isLocation
227
228
  || reply.isEmail
228
229
  || reply.isPhone)
229
230
  .map((reply) => {
230
- const rep = (reply.isLocation || reply.isEmail || reply.isPhone)
231
- ? ({ ...reply })
232
- : ({ ...reply, title: reply.title(data) });
231
+ let $this = null;
232
+ let rep;
233
+
234
+ if (reply.isLocation || reply.isEmail || reply.isPhone) {
235
+ rep = { ...reply };
236
+ } else {
237
+ const title = reply.title(data);
238
+ $this = title;
239
+ rep = { ...reply, title };
240
+ }
241
+
242
+ if (typeof rep.condition !== 'function') {
243
+ return rep;
244
+ }
233
245
 
234
- if (typeof rep.condition === 'function') {
235
- delete rep.condition;
246
+ res.setData({ $this });
247
+ if (!rep.condition(req, res)) {
248
+ res.setData({ $this: null });
249
+ return null;
236
250
  }
251
+ res.setData({ $this: null });
252
+
253
+ delete rep.condition;
237
254
 
238
255
  return rep;
239
- });
256
+ })
257
+ .filter((rep) => rep !== null);
240
258
 
241
- okQuickReplies
242
- .filter((reply) => !reply.title && reply.match)
259
+ quickReplies
260
+ .filter((reply) => !reply.title && reply.match && reply.condition(req, res))
243
261
  .forEach(({
244
262
  match, action, data: replyData, setState, aiTitle
245
263
  }) => {
@@ -0,0 +1,57 @@
1
+ /*
2
+ * @author David Menger
3
+ */
4
+ 'use strict';
5
+
6
+ const Router = require('../Router');
7
+ const getCondition = require('../utils/getCondition');
8
+
9
+ /** @typedef {import('../BuildRouter').BotContext} BotContext */
10
+ /** @typedef {import('../Router').Resolver} Resolver */
11
+
12
+ /**
13
+ *
14
+ * @param {object} params
15
+ * @param {BotContext} context
16
+ * @returns {Resolver}
17
+ */
18
+ function skip (params, context = {}) {
19
+ const { isLastIndex } = context;
20
+ const { type } = params;
21
+
22
+ const ret = isLastIndex ? Router.END : Router.CONTINUE;
23
+
24
+ if (['skip', 'end', 'intent'].indexOf(type) === -1) {
25
+ throw new Error(`Unsupported skip type: ${type}`);
26
+ }
27
+
28
+ const condition = getCondition(params, context, 'Skip condition');
29
+
30
+ return (req, res, postBack) => {
31
+ if (condition && !condition(req, res)) {
32
+ return ret;
33
+ }
34
+
35
+ switch (type) {
36
+ case 'end':
37
+ return Router.END;
38
+ case 'skip':
39
+ return Router.BREAK;
40
+ case 'intent': {
41
+ let { lastAiActionsIndex = 0 } = req.actionData();
42
+ const actions = req.aiActions();
43
+ lastAiActionsIndex++;
44
+ if (!actions[lastAiActionsIndex] || !actions[lastAiActionsIndex].aboveConfidence) {
45
+ return Router.BREAK;
46
+ }
47
+ postBack(actions[lastAiActionsIndex].action, { lastAiActionsIndex });
48
+ return Router.END;
49
+ }
50
+
51
+ default:
52
+ return ret;
53
+ }
54
+ };
55
+ }
56
+
57
+ module.exports = skip;
@@ -88,8 +88,23 @@ 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 === '=='
92
- return variable.some((variableElement) => compare(variableElement, operator, value));
91
+ if (operator === ConditionOperators['==']
92
+ && (typeof value === 'number' || isStringNumber(value))) {
93
+
94
+ const numeric = toNumber(value);
95
+ return numeric === variable.length;
96
+ }
97
+
98
+ if (operator === ConditionOperators['not contains']) {
99
+ return variable
100
+ .every((variableElement) => !compare(variableElement, ConditionOperators['=='], value));
101
+ }
102
+
103
+ const useOperator = operator === ConditionOperators.contains
104
+ ? ConditionOperators['==']
105
+ : operator;
106
+
107
+ return variable.some((variableElement) => compare(variableElement, useOperator, value));
93
108
  }
94
109
 
95
110
  if (variable && typeof variable === 'object' && !isArrayLengthCompare) {
@@ -161,6 +161,8 @@ function getSetState (setState, req, res = null, useState = null, configuration
161
161
  valAsArray.shift();
162
162
  } else if (val._$pop) {
163
163
  valAsArray.pop();
164
+ } else if (val._$pushT) {
165
+ valAsArray.push(req.text());
164
166
  } else {
165
167
  const value = val._$add || val._$rem || val._$push || val._$set;
166
168
  const [, entity, rear] = `${value}`.match(ENTITY_HBS_REGEXP) || [];
@@ -17,11 +17,12 @@ module.exports = function stateData (req, res = null, configuration = null) {
17
17
  const c = configuration || req.configuration;
18
18
 
19
19
  return {
20
+ c,
21
+ configuration: c,
20
22
  ...req.state,
21
23
  ...(res ? res.newState : {}),
22
24
  ...req.actionData(),
23
25
  ...(res ? res.data : {}),
24
- c,
25
- configuration: c
26
+ $input: req.text()
26
27
  };
27
28
  };