wingbot 3.65.12 → 3.66.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.65.12",
3
+ "version": "3.66.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,6 +49,8 @@ let uq = 1;
49
49
  /** @typedef {import('./wingbot/CustomEntityDetectionModel').Phrases} Phrases */
50
50
  /** @typedef {import('./wingbot/CustomEntityDetectionModel').EntityDetector} EntityDetector */
51
51
  /** @typedef {import('./wingbot/CustomEntityDetectionModel').DetectorOptions} DetectorOptions */
52
+ // eslint-disable-next-line max-len
53
+ /** @typedef {import('./wingbot/CustomEntityDetectionModel').WordEntityDetector} WordEntityDetector */
52
54
 
53
55
  /** @typedef {[string,EntityDetector|RegExp,DetectorOptions]} DetectorArgs */
54
56
 
@@ -72,6 +74,12 @@ class Ai {
72
74
  systemEntities.map((a) => [a[0], a])
73
75
  );
74
76
 
77
+ /**
78
+ * @private
79
+ * @type {WordEntityDetector}
80
+ */
81
+ this._wordEntityDetector = null;
82
+
75
83
  /**
76
84
  * Upper threshold - for match method and for navigate method
77
85
  *
@@ -200,6 +208,7 @@ class Ai {
200
208
 
201
209
  this._keyworders.set(prefix, modelObj);
202
210
 
211
+ modelObj.wordEntityDetector = this._wordEntityDetector;
203
212
  for (const entityArgs of this._detectors.values()) {
204
213
  modelObj.setEntityDetector(...entityArgs);
205
214
  }
@@ -232,6 +241,19 @@ class Ai {
232
241
  return this;
233
242
  }
234
243
 
244
+ /**
245
+ *
246
+ * @param {WordEntityDetector} wordEntityDetector
247
+ */
248
+ setWordEntityDetector (wordEntityDetector) {
249
+ this._wordEntityDetector = wordEntityDetector;
250
+
251
+ for (const model of this._keyworders.values()) {
252
+ model.wordEntityDetector = wordEntityDetector;
253
+ }
254
+ return this;
255
+ }
256
+
235
257
  /**
236
258
  * Sets options to entity detector.
237
259
  * Useful for disabling anonymization of local system entities.
package/src/BotApp.js CHANGED
@@ -16,7 +16,7 @@ const DEFAULT_API_URL = 'https://orchestrator-api.wingbot.ai';
16
16
 
17
17
  /** @typedef {import('./ReducerWrapper')} ReducerWrapper */
18
18
  /** @typedef {import('./Router')} Router */
19
- /** @typedef {import('./Processor').ProcessorOptions} ProcessorOptions */
19
+ /** @typedef {import('./Processor').ProcessorOptions<Router>} ProcessorOptions */
20
20
  /** @typedef {import('./ReturnSender').ChatLogStorage} ChatLogStorage */
21
21
  /** @typedef {import('./Request')} Request */
22
22
  /** @typedef {import('./Responder')} Responder */
@@ -70,7 +70,7 @@ class BotApp {
70
70
 
71
71
  /**
72
72
  *
73
- * @param {ReducerWrapper|Router} bot
73
+ * @param {Router} bot
74
74
  * @param {Options} options
75
75
  */
76
76
  constructor (bot, options) {
@@ -119,6 +119,7 @@ const DUMMY_ROUTE = { id: 0, path: null, resolvers: [] };
119
119
  * @prop {LinkTranslator} [linksTranslator] - function, that translates links globally
120
120
  * @prop {ConfigStorage} [configStorage] - function, that translates links globally
121
121
  * @prop {boolean} [allowForbiddenSnippetWords] - disable security rule
122
+ * @prop {Middleware} [defaultPlugin] - to be able to test configurations without plugins
122
123
  * @prop {RouteConfig[]} [routeConfigs] - list of disabled routes
123
124
  * @prop {C} [configuration] - context data
124
125
  * @prop {LinksMap} [linksMap]
package/src/Plugins.js CHANGED
@@ -53,14 +53,16 @@ class Plugins {
53
53
  this._plugins = new Map();
54
54
  }
55
55
 
56
- getPluginFactory (name, paramsData = {}, configuration = {}) {
56
+ getPluginFactory (name, paramsData = {}, configuration = {}, defaultPlugin = null) {
57
57
  let plugin;
58
58
  if (plugins.has(name)) {
59
59
  plugin = plugins.get(name);
60
- } else if (!this._plugins.has(name)) {
61
- throw new Error(`Unknown Plugin: ${name}. Ensure its registration.`);
62
- } else {
60
+ } else if (this._plugins.has(name)) {
63
61
  plugin = this._plugins.get(name);
62
+ } else if (defaultPlugin) {
63
+ plugin = defaultPlugin;
64
+ } else {
65
+ throw new Error(`Unknown Plugin: ${name}. Ensure its registration.`);
64
66
  }
65
67
  if (plugin && plugin.pluginFactory) {
66
68
  return plugin.pluginFactory(paramsData, configuration);
@@ -79,6 +81,7 @@ class Plugins {
79
81
  * @param {boolean} [context.isLastIndex]
80
82
  * @param {Router} [context.router]
81
83
  * @param {object} [context.configuration]
84
+ * @param {Middleware} [context.defaultPlugin]
82
85
  * @example
83
86
  *
84
87
  * const { Router } = require('wingbot');
@@ -122,7 +125,13 @@ class Plugins {
122
125
  .map(([k, e]) => ({ [k]: e }))
123
126
  .reduce(Object.assign, {});
124
127
 
125
- const customFn = this.getPluginFactory(name, cleanParams, context.configuration);
128
+ const customFn = this.getPluginFactory(
129
+ name,
130
+ cleanParams,
131
+ context.configuration,
132
+ context.defaultPlugin
133
+ );
134
+
126
135
  if (typeof customFn === 'object') {
127
136
  // this is an attached router
128
137
 
package/src/Processor.js CHANGED
@@ -90,6 +90,7 @@ const { prepareState, mergeState, isUserInteraction } = require('./utils/stateVa
90
90
 
91
91
  /**
92
92
  *
93
+ * @template {ReducerWrapper|Router|BuildRouter} R
93
94
  * @typedef {object} ProcessorOptions
94
95
  * @prop {string} [appUrl] - url basepath for relative links
95
96
  * @prop {IStateStorage} [stateStorage] - chatbot state storage
@@ -109,6 +110,7 @@ const { prepareState, mergeState, isUserInteraction } = require('./utils/stateVa
109
110
  * @prop {string} [apiUrl] - Url for calling orchestrator API
110
111
  * @prop {Function} [fetch] - Fetch function for calling orchestrator API
111
112
  * @prop {number} [sessionDuration] - Session duration for analytic purposes
113
+ * @prop {Preloader<R>} [preloader]
112
114
  */
113
115
 
114
116
  /**
@@ -133,6 +135,14 @@ const { prepareState, mergeState, isUserInteraction } = require('./utils/stateVa
133
135
  * @prop {Function} getOrCreateAndLock
134
136
  */
135
137
 
138
+ /**
139
+ * @template {ReducerWrapper|Router|BuildRouter} T
140
+ * @callback Preloader
141
+ * @param {T} router
142
+ * @param {Ai} ai
143
+ * @returns {Promise<void>}
144
+ */
145
+
136
146
  function NAME_FROM_STATE (state) {
137
147
  if (state.user && state.user.firstName) {
138
148
  return `${state.user.firstName} ${state.user.lastName}`;
@@ -161,6 +171,7 @@ function toBase (number) {
161
171
  /**
162
172
  * Messaging event processor
163
173
  *
174
+ * @template {ReducerWrapper|Router|BuildRouter} T
164
175
  * @class
165
176
  * @fires Processor#interaction
166
177
  */
@@ -169,8 +180,8 @@ class Processor extends EventEmitter {
169
180
  /**
170
181
  * Creates an instance of Processor
171
182
  *
172
- * @param {ReducerWrapper|Router|BuildRouter} reducer
173
- * @param {ProcessorOptions} [options] - processor options
183
+ * @param {T} reducer
184
+ * @param {ProcessorOptions<T>} [options] - processor options
174
185
  *
175
186
  * @memberOf Processor
176
187
  */
@@ -197,6 +208,8 @@ class Processor extends EventEmitter {
197
208
 
198
209
  Object.assign(this.options, options);
199
210
 
211
+ this._preloaders = options.preloader ? [options.preloader] : [];
212
+
200
213
  this.reducer = reducer;
201
214
 
202
215
  /**
@@ -319,15 +332,17 @@ class Processor extends EventEmitter {
319
332
  }
320
333
 
321
334
  async _preload () {
322
- // @ts-ignore
323
- if (this.reducer && typeof this.reducer.preload === 'function') {
335
+ return Promise.all([
324
336
  // @ts-ignore
325
- return this.reducer.preload()
326
- .catch((e) => this.options.log.error('preload error', e))
327
- // mute log errors
328
- .catch(() => {});
329
- }
330
- return Promise.resolve();
337
+ this.reducer && typeof this.reducer.preload === 'function'
338
+ // @ts-ignore
339
+ ? this.reducer.preload()
340
+ : Promise.resolve(),
341
+ ...this._preloaders
342
+ .map((preloader) => preloader(this.reducer, Ai.ai))
343
+ ]).catch((e) => this.options.log.error('preload error', e))
344
+ // mute log errors
345
+ .catch(() => {});
331
346
  }
332
347
 
333
348
  async processMessage (
package/src/Router.js CHANGED
@@ -128,6 +128,15 @@ class Router extends ReducerWrapper {
128
128
  return this._configuration;
129
129
  }
130
130
 
131
+ /**
132
+ *
133
+ * @param {Partial<C>} c
134
+ * @returns {C}
135
+ */
136
+ updateConfiguration (c) {
137
+ return Object.assign(this._configuration, c);
138
+ }
139
+
131
140
  _normalizePath (path) {
132
141
  let normalizedPath;
133
142
 
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @author David Menger
3
+ */
4
+ 'use strict';
5
+
6
+ function* iterateThroughWords (string, maxWordCount) {
7
+ const split = string.split(/\s/);
8
+
9
+ let start = 0;
10
+ for (let i = 0, w; i < split.length; i++) {
11
+ w = split[i];
12
+
13
+ if (!w) {
14
+ start++;
15
+ continue;
16
+ }
17
+
18
+ if (w.match(/@[A-Z0-9-]+/)) {
19
+ continue;
20
+ }
21
+
22
+ yield [w, start];
23
+
24
+ let multiW = w;
25
+ let subword;
26
+ for (let j = i + 1, k = 1; j < split.length && k < maxWordCount; j++) {
27
+ subword = split[j];
28
+ if (subword.match(/@[A-Z0-9-]+/)) {
29
+ break;
30
+ }
31
+ if (subword) {
32
+ k++;
33
+ multiW += ` ${subword}`;
34
+
35
+ yield [multiW, start];
36
+ } else {
37
+ multiW += ' ';
38
+ }
39
+ }
40
+
41
+ start += 1 + w.length;
42
+ }
43
+ }
44
+
45
+ module.exports = {
46
+ iterateThroughWords
47
+ };
@@ -65,7 +65,7 @@ class CachedModel extends CustomEntityDetectionModel {
65
65
  return this._cacheMap.get(text);
66
66
  }
67
67
 
68
- const promise = this._queryModel(local.text)
68
+ const promise = this._queryModel(local.text, local.entities)
69
69
  .then((res) => {
70
70
  // clean the cache
71
71
  while (this._cache.length > this._cacheSize) {
@@ -156,9 +156,10 @@ class CachedModel extends CustomEntityDetectionModel {
156
156
  /**
157
157
  *
158
158
  * @param {string} text
159
+ * @param {Entity[]} entities
159
160
  * @returns {Promise<Intent[]|Result>}
160
161
  */
161
- async _queryModel (text) { // eslint-disable-line no-unused-vars
162
+ async _queryModel (text, entities) { // eslint-disable-line no-unused-vars
162
163
  throw new Error('Not implemented!');
163
164
  }
164
165
 
@@ -4,6 +4,7 @@
4
4
  'use strict';
5
5
 
6
6
  const { replaceDiacritics } = require('../utils');
7
+ const { iterateThroughWords } = require('../utils/ai');
7
8
 
8
9
  /**
9
10
  * @typedef {object} DetectedEntity
@@ -61,6 +62,13 @@ const { replaceDiacritics } = require('../utils');
61
62
  * @prop {boolean} [clearOverlaps] - let longer entities from NLP to replace entity
62
63
  */
63
64
 
65
+ /**
66
+ * @callback WordEntityDetector
67
+ * @param {string} text
68
+ * @param {DetectedEntity[]} entities
69
+ * @param {number} startIndex
70
+ */
71
+
64
72
  /**
65
73
  * @typedef {object} Phrases
66
74
  * @prop {Map<string,string[]>} phrases
@@ -94,6 +102,16 @@ class CustomEntityDetectionModel {
94
102
  * @type {number}
95
103
  */
96
104
  this.phrasesCacheTime = 0;
105
+
106
+ /**
107
+ * @type {number}
108
+ */
109
+ this.maxWordCount = 0;
110
+
111
+ /**
112
+ * @type {WordEntityDetector}
113
+ */
114
+ this.wordEntityDetector = null;
97
115
  }
98
116
 
99
117
  /**
@@ -378,6 +396,13 @@ class CustomEntityDetectionModel {
378
396
  * @returns {Promise<DetectedEntity[]>}
379
397
  */
380
398
  async resolveEntities (text, singleEntity = null, expected = [], prevEnts = [], subWord = []) {
399
+ let entities = prevEnts.slice();
400
+ if (this.wordEntityDetector) {
401
+ for (const [s, startIndex] of iterateThroughWords(text, this.maxWordCount)) {
402
+ this.wordEntityDetector(s, entities, startIndex);
403
+ }
404
+ }
405
+
381
406
  // mark unknown dependencies as resolved
382
407
  const resolved = new Set(
383
408
  this.getDependentEntities(false)
@@ -385,7 +410,7 @@ class CustomEntityDetectionModel {
385
410
  );
386
411
 
387
412
  let missing = Array.from(this._entityDetectors.keys());
388
- const entities = prevEnts.map((e) => {
413
+ entities = entities.map((e) => {
389
414
  if (typeof e.text === 'string') {
390
415
  return e;
391
416
  }
@@ -1,6 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  const { default: fetch } = require('node-fetch');
4
+ const { brotliCompress } = require('zlib');
5
+ const { promisify } = require('util');
4
6
  const assert = require('assert');
5
7
  const CachedModel = require('./CachedModel');
6
8
 
@@ -59,6 +61,7 @@ class WingbotModel extends CachedModel {
59
61
  this._serviceUrl = options.serviceUrl || SERVICE_URL;
60
62
  this._trainingUrl = options.trainingUrl || TRAINING_URL;
61
63
  this._model = options.model;
64
+ this._brotli = promisify(brotliCompress);
62
65
  }
63
66
 
64
67
  async _getPhrases () {
@@ -84,9 +87,10 @@ class WingbotModel extends CachedModel {
84
87
  /**
85
88
  *
86
89
  * @param {string} text
90
+ * @param {Entity[]} entities
87
91
  * @returns {Promise<Result>}
88
92
  */
89
- async _queryModel (text) {
93
+ async _queryModel (text, entities = null) {
90
94
  if ((text || '').trim().length === 0) {
91
95
  return [];
92
96
  }
@@ -96,6 +100,11 @@ class WingbotModel extends CachedModel {
96
100
  `matches=${encodeURIComponent(this._matches)}`
97
101
  ];
98
102
 
103
+ if (entities) {
104
+ const buf = await this._brotli(Buffer.from(JSON.stringify({ entities })));
105
+ qs.push(`meta=${encodeURIComponent(buf.toString('base64url'))}`);
106
+ }
107
+
99
108
  try {
100
109
  const res = await this._fetch(
101
110
  `${this._serviceUrl}/${this._model}?${qs.join('&')}`,