wingbot 3.42.1 → 3.43.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.42.1",
3
+ "version": "3.43.1",
4
4
  "description": "Enterprise Messaging Bot Conversation Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -11,10 +11,12 @@ const { vars } = require('../../src/utils/stateVariables');
11
11
  /**
12
12
  * @param {object} params
13
13
  * @param {string} [params.skip]
14
+ * @param {string} [params.fill]
14
15
  * @returns {SlotsResolver}
15
16
  */
16
17
  function slotsContinue ({
17
- skip
18
+ skip,
19
+ fill
18
20
  }) {
19
21
 
20
22
  /**
@@ -39,6 +41,10 @@ function slotsContinue ({
39
41
  .split(',')
40
42
  .map((e) => e.trim());
41
43
 
44
+ const fillEntities = compileWithState(req, res, fill)
45
+ .split(',')
46
+ .map((e) => e.trim());
47
+
42
48
  const clear = {};
43
49
 
44
50
  slotState = slotState.map((s) => {
@@ -52,7 +58,7 @@ function slotsContinue ({
52
58
  return { ...s, s: StepState.INITIALIZED };
53
59
  }
54
60
 
55
- return s.e === step.entity
61
+ return s.e === step.entity || fillEntities.includes(s.e)
56
62
  ? { ...s, s: StepState.FILLED }
57
63
  : s;
58
64
  });
@@ -415,7 +415,7 @@
415
415
  {
416
416
  "type": "select",
417
417
  "name": "type",
418
- "label": "Question",
418
+ "label": "Type",
419
419
  "options": [
420
420
  { "label": "Required", "value": "req" },
421
421
  { "label": "Multi value", "value": "mul" },
@@ -454,6 +454,18 @@
454
454
  "validations": [
455
455
  { "type": "regexp", "value": "^\\s*@[a-zA-Z0-9-]+\\s*(,\\s*@[a-zA-Z0-9-]+\\s*)*$", "message": "the entity for the slot filling should be valid" }
456
456
  ]
457
+ },
458
+ {
459
+ "name": "fill",
460
+ "label": "Skip entity (mark as filled)",
461
+ "type": "text",
462
+ "validations": [
463
+ {
464
+ "type": "regexp",
465
+ "value": "^(\\s*@[a-zA-Z0-9-]+\\s*,?)*$",
466
+ "message": "the entity to skip should be valid"
467
+ }
468
+ ]
457
469
  }
458
470
  ],
459
471
  "items": [
package/src/Ai.js CHANGED
@@ -7,12 +7,15 @@ const { WingbotModel } = require('./wingbot');
7
7
  const AiMatching = require('./AiMatching');
8
8
  const { vars } = require('./utils/stateVariables');
9
9
  const { deepEqual } = require('./utils/deepMapTools');
10
+ const systemEntities = require('./systemEntities');
10
11
  const CustomEntityDetectionModel = require('./wingbot/CustomEntityDetectionModel');
11
12
 
12
13
  const DEFAULT_PREFIX = 'default';
13
14
 
14
15
  let uq = 1;
15
16
 
17
+ /** @typedef {import('./AiMatching').Compare} Compare */
18
+
16
19
  /**
17
20
  * @typedef {object} EntityExpression
18
21
  * @prop {string} entity - the requested entity
@@ -43,6 +46,10 @@ let uq = 1;
43
46
  /** @typedef {import('./Responder')} Responder */
44
47
  /** @typedef {import('./wingbot/CachedModel').Result} Result */
45
48
  /** @typedef {import('./wingbot/CustomEntityDetectionModel').Phrases} Phrases */
49
+ /** @typedef {import('./wingbot/CustomEntityDetectionModel').EntityDetector} EntityDetector */
50
+ /** @typedef {import('./wingbot/CustomEntityDetectionModel').DetectorOptions} DetectorOptions */
51
+
52
+ /** @typedef {[string,EntityDetector|RegExp,DetectorOptions]} DetectorArgs */
46
53
 
47
54
  /**
48
55
  * @class Ai
@@ -50,8 +57,20 @@ let uq = 1;
50
57
  class Ai {
51
58
 
52
59
  constructor () {
60
+ /**
61
+ * @private
62
+ * @type {Map<string,CustomEntityDetectionModel>}
63
+ */
53
64
  this._keyworders = new Map();
54
65
 
66
+ /**
67
+ * @private
68
+ * @type {Map<string,DetectorArgs>}
69
+ */
70
+ this._detectors = new Map(
71
+ systemEntities.map((a) => [a[0], a])
72
+ );
73
+
55
74
  /**
56
75
  * Upper threshold - for match method and for navigate method
57
76
  *
@@ -157,29 +176,86 @@ class Ai {
157
176
  /**
158
177
  * Registers Wingbot AI model
159
178
  *
160
- * @template T
179
+ * @template {CustomEntityDetectionModel} T
161
180
  * @param {string|WingbotModel|T} model - wingbot model name or AI plugin
162
181
  * @param {string} prefix - model prefix
163
182
  *
164
- * @returns {WingbotModel|T}
183
+ * @returns {T}
165
184
  * @memberOf Ai
166
185
  */
167
186
  register (model, prefix = 'default') {
187
+ /** @type {T} */
168
188
  let modelObj;
169
189
 
170
190
  if (typeof model === 'string') {
191
+ // @ts-ignore
171
192
  modelObj = new WingbotModel({
172
193
  model
173
194
  }, this.logger);
174
195
  } else {
196
+ // @ts-ignore
175
197
  modelObj = model;
176
198
  }
177
199
 
178
200
  this._keyworders.set(prefix, modelObj);
179
201
 
202
+ for (const entityArgs of this._detectors.values()) {
203
+ modelObj.setEntityDetector(...entityArgs);
204
+ }
205
+
180
206
  return modelObj;
181
207
  }
182
208
 
209
+ /**
210
+ *
211
+ * @param {string} name
212
+ * @param {EntityDetector|RegExp} detector
213
+ * @param {object} [options]
214
+ * @param {boolean} [options.anonymize] - if true, value will not be sent to NLP
215
+ * @param {Function|string} [options.extractValue] - entity extractor
216
+ * @param {boolean} [options.matchWholeWords] - match whole words at regular expression
217
+ * @param {boolean} [options.replaceDiacritics] - keep diacritics when matching regexp
218
+ * @param {string[]} [options.dependencies] - array of dependent entities
219
+ * @param {boolean} [options.clearOverlaps] - let longer entities from NLP to replace entity
220
+ * @returns {this}
221
+ */
222
+ registerEntityDetector (name, detector, options = {}) {
223
+ const useOptions = { clearOverlaps: true, ...options };
224
+
225
+ this._detectors.set(name, [name, detector, useOptions]);
226
+
227
+ for (const model of this._keyworders.values()) {
228
+ model.setEntityDetector(name, detector, useOptions);
229
+ }
230
+
231
+ return this;
232
+ }
233
+
234
+ /**
235
+ * Sets options to entity detector.
236
+ * Useful for disabling anonymization of local system entities.
237
+ *
238
+ * @param {string} name
239
+ * @param {object} options
240
+ * @param {boolean} [options.anonymize]
241
+ * @returns {this}
242
+ * @example
243
+ *
244
+ * ai.register('wingbot-model-name')
245
+ * .setDetectorOptions('phone', { anonymize: false })
246
+ * .setDetectorOptions('email', { anonymize: false })
247
+ */
248
+ configureEntityDetector (name, options) {
249
+ if (!this._detectors.has(name)) {
250
+ throw new Error(`Can't set entity detector options. Entity "${name}" does not exist.`);
251
+ }
252
+ Object.assign(this._detectors.get(name)[2], options);
253
+ for (const model of this._keyworders.values()) {
254
+ model.setDetectorOptions(name, options);
255
+ }
256
+ return this;
257
+ }
258
+
183
259
  /**
184
260
  * Remove registered model
185
261
  *
@@ -194,7 +270,7 @@ class Ai {
194
270
  *
195
271
  * @param {string} prefix - model prefix
196
272
  *
197
- * @returns {WingbotModel}
273
+ * @returns {CustomEntityDetectionModel}
198
274
  * @memberOf Ai
199
275
  */
200
276
  getModel (prefix = 'default') {
@@ -40,6 +40,13 @@ function getVoiceControlFromParams (params, lang = null) {
40
40
  }
41
41
  });
42
42
 
43
+ // remove empty values ("")
44
+ Object.keys(voiceControl).forEach((key) => {
45
+ if (voiceControl[key] === '') {
46
+ delete voiceControl[key];
47
+ }
48
+ });
49
+
43
50
  // if voiceControl is empty, return null
44
51
  return Object.keys(voiceControl).length > 0 ? voiceControl : null;
45
52
  }
@@ -3,6 +3,11 @@
3
3
  */
4
4
  'use strict';
5
5
 
6
+ /** @typedef {import('../wingbot/CustomEntityDetectionModel').EntityDetector} EntityDetector */
7
+ /** @typedef {import('../wingbot/CustomEntityDetectionModel').DetectorOptions} DetectorOptions */
8
+
9
+ /** @type {[string,EntityDetector|RegExp,DetectorOptions]} */
6
10
  module.exports = ['email', /(?<=(\s|^|:))[a-zA-Z0-9!#$%&'*+\-=?^_`{|}~"][^@:\s]*@[^.@\s]+\.[^@\s,]+/, {
7
- anonymize: true
11
+ anonymize: true,
12
+ clearOverlaps: true
8
13
  }];
@@ -3,11 +3,16 @@
3
3
  */
4
4
  'use strict';
5
5
 
6
+ /** @typedef {import('../wingbot/CustomEntityDetectionModel').EntityDetector} EntityDetector */
7
+ /** @typedef {import('../wingbot/CustomEntityDetectionModel').DetectorOptions} DetectorOptions */
8
+
9
+ /** @type {[string,EntityDetector|RegExp,DetectorOptions]} */
6
10
  module.exports = [
7
11
  'phone',
8
12
  /((00|\+)[\s-]?[0-9]{1,4}[\s-]?)?([0-9]{3,4}[\s-]?([0-9]{2,3}[\s-]?[0-9]{2}[\s-]?[0-9]{2,3}|[0-9]{3,4}[\s-]?[0-9]{3,4}))(?=(\s|$|[,!.?\-:]))/,
9
13
  {
10
14
  anonymize: true,
15
+ clearOverlaps: true,
11
16
  extractValue: (match) => {
12
17
  let [, internat,, number] = match;
13
18
 
@@ -35,7 +35,7 @@ class CachedModel extends CustomEntityDetectionModel {
35
35
  * @param {object} options
36
36
  * @param {number} [options.cacheSize]
37
37
  * @param {number} [options.cachePhrasesTime]
38
- * @param {{ warn: Function, error: Function }} [log]
38
+ * @param {{ warn: Function, error: Function, log: Function }} [log]
39
39
  */
40
40
  constructor (options, log = console) {
41
41
  super(options, log);
@@ -88,11 +88,19 @@ class CachedModel extends CustomEntityDetectionModel {
88
88
  const res = await promise;
89
89
  let { intents = [], entities = [] } = Array.isArray(res) ? { intents: res } : res;
90
90
 
91
- entities = [...entities, ...local.entities];
92
- intents = intents.map((i) => ({
93
- ...i,
94
- entities: [...(i.entities || []), ...local.entities]
95
- }));
91
+ const expectedEntities = req ? req.expectedEntities() : [];
92
+
93
+ const before = local.entities
94
+ .filter((e) => this._entityDetectors.has(e.entity)
95
+ && this._entityDetectors.get(e.entity).clearOverlaps);
96
+
97
+ [intents, entities] = this._attachEntities(intents, entities, before, expectedEntities);
98
+
99
+ const after = local.entities
100
+ .filter((e) => !this._entityDetectors.has(e.entity)
101
+ || !this._entityDetectors.get(e.entity).clearOverlaps);
102
+
103
+ [intents, entities] = this._attachEntities(intents, entities, after);
96
104
 
97
105
  return {
98
106
  text: local.text,
@@ -101,6 +109,26 @@ class CachedModel extends CustomEntityDetectionModel {
101
109
  };
102
110
  }
103
111
 
112
+ _attachEntities (intents, entities, attachEntities, expectedEntities = null) {
113
+ const retEntities = [...entities, ...attachEntities];
114
+ const retIntents = intents
115
+ .map((i) => {
116
+ const ents = [...(i.entities || []), ...attachEntities];
117
+
118
+ return {
119
+ ...i,
120
+ entities: expectedEntities
121
+ ? this.nonOverlapping(ents, expectedEntities)
122
+ : ents
123
+ };
124
+ });
125
+
126
+ return [
127
+ retIntents,
128
+ expectedEntities ? this.nonOverlapping(retEntities, expectedEntities) : retEntities
129
+ ];
130
+ }
131
+
104
132
  async getPhrases () {
105
133
  if (this._phrasesCachedAt > (Date.now() - this.phrasesCacheTime)) {
106
134
  return this._phrasesCache;
@@ -51,6 +51,16 @@ const { replaceDiacritics } = require('../utils');
51
51
  * @prop {Intent[]} intents
52
52
  */
53
53
 
54
+ /**
55
+ * @typedef {object} DetectorOptions
56
+ * @prop {boolean} [anonymize] - if true, value will not be sent to NLP
57
+ * @prop {Function|string} [extractValue] - entity extractor
58
+ * @prop {boolean} [matchWholeWords] - match whole words at regular expression
59
+ * @prop {boolean} [replaceDiacritics] - keep diacritics when matching regexp
60
+ * @prop {string[]} [dependencies] - array of dependent entities
61
+ * @prop {boolean} [clearOverlaps] - let longer entities from NLP to replace entity
62
+ */
63
+
54
64
  /**
55
65
  * @typedef {object} Phrases
56
66
  * @prop {Map<string,string[]>} phrases
@@ -70,7 +80,7 @@ class CustomEntityDetectionModel {
70
80
 
71
81
  /**
72
82
  * @param {object} options
73
- * @param {{ warn: Function, error: Function }} [log]
83
+ * @param {{ warn: Function, error: Function, log: Function }} [log]
74
84
  */
75
85
  constructor (options, log = console) {
76
86
  this._options = options;
@@ -582,12 +592,7 @@ class CustomEntityDetectionModel {
582
592
  *
583
593
  * @param {string} name
584
594
  * @param {EntityDetector|RegExp} detector
585
- * @param {object} [options]
586
- * @param {boolean} [options.anonymize] - if true, value will not be sent to NLP
587
- * @param {Function|string} [options.extractValue] - entity extractor
588
- * @param {boolean} [options.matchWholeWords] - match whole words at regular expression
589
- * @param {boolean} [options.replaceDiacritics] - keep diacritics when matching regexp
590
- * @param {string[]} [options.dependencies] - array of dependent entities
595
+ * @param {DetectorOptions} [options]
591
596
  * @returns {this}
592
597
  */
593
598
  setEntityDetector (name, detector, options = {}) {
@@ -610,7 +615,8 @@ class CustomEntityDetectionModel {
610
615
  entityDetector,
611
616
  detector,
612
617
  dependencies,
613
- anonymize: !!options.anonymize
618
+ anonymize: !!options.anonymize,
619
+ clearOverlaps: !!options.clearOverlaps
614
620
  });
615
621
 
616
622
  return this;
@@ -623,6 +629,7 @@ class CustomEntityDetectionModel {
623
629
  * @param {string} name
624
630
  * @param {object} options
625
631
  * @param {boolean} [options.anonymize]
632
+ * @param {boolean} [options.clearOverlaps]
626
633
  * @returns {this}
627
634
  * @example
628
635
  *
@@ -632,7 +639,7 @@ class CustomEntityDetectionModel {
632
639
  */
633
640
  setDetectorOptions (name, options) {
634
641
  if (!this._entityDetectors.has(name)) {
635
- throw new Error('Can\'t set entity detector options. Entity "name" does not exist.');
642
+ throw new Error(`Can't set entity detector options. Entity "${name}" does not exist.`);
636
643
  }
637
644
  Object.assign(this._entityDetectors.get(name), options);
638
645
  return this;
@@ -3,7 +3,6 @@
3
3
  const { default: fetch } = require('node-fetch');
4
4
  const assert = require('assert');
5
5
  const CachedModel = require('./CachedModel');
6
- const systemEntities = require('../systemEntities');
7
6
 
8
7
  const DEFAULT_MATCHES = 3;
9
8
  const SERVICE_URL = 'https://model.wingbot.ai';
@@ -42,7 +41,7 @@ class WingbotModel extends CachedModel {
42
41
  * @param {number} [options.cacheSize]
43
42
  * @param {number} [options.matches]
44
43
  * @param {Function} [options.fetch]
45
- * @param {{ warn: Function, log: Function }} [log]
44
+ * @param {{ warn: Function, log: Function, error: Function }} [log]
46
45
  */
47
46
  constructor (options, log = console) {
48
47
  super(options, log);
@@ -60,11 +59,6 @@ class WingbotModel extends CachedModel {
60
59
  this._serviceUrl = options.serviceUrl || SERVICE_URL;
61
60
  this._trainingUrl = options.trainingUrl || TRAINING_URL;
62
61
  this._model = options.model;
63
-
64
- // apply default entities
65
- systemEntities
66
- // @ts-ignore
67
- .forEach(([name, d, opts = {}]) => this.setEntityDetector(name, d, opts));
68
62
  }
69
63
 
70
64
  async _getPhrases () {