wingbot 3.65.12 → 3.66.0
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 +22 -0
- package/src/BotApp.js +2 -2
- package/src/BuildRouter.js +1 -0
- package/src/Plugins.js +14 -5
- package/src/Processor.js +25 -10
- package/src/Router.js +9 -0
- package/src/utils/ai.js +47 -0
- package/src/wingbot/CachedModel.js +3 -2
- package/src/wingbot/CustomEntityDetectionModel.js +26 -1
- package/src/wingbot/WingbotModel.js +10 -1
package/package.json
CHANGED
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 {
|
|
73
|
+
* @param {Router} bot
|
|
74
74
|
* @param {Options} options
|
|
75
75
|
*/
|
|
76
76
|
constructor (bot, options) {
|
package/src/BuildRouter.js
CHANGED
|
@@ -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 (
|
|
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(
|
|
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 {
|
|
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
|
-
|
|
323
|
-
if (this.reducer && typeof this.reducer.preload === 'function') {
|
|
335
|
+
return Promise.all([
|
|
324
336
|
// @ts-ignore
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
.
|
|
329
|
-
|
|
330
|
-
|
|
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
|
|
package/src/utils/ai.js
ADDED
|
@@ -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
|
-
|
|
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('&')}`,
|