wingbot 3.66.4 → 3.67.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/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  */
4
4
  'use strict';
5
5
 
6
- /** @typedef {import('./src/Processor').ProcessorOptions} ProcessorOptions */
6
+ /** @typedef {import('./src/Processor').ProcessorOptions<Router|BuildRouter>} ProcessorOptions */
7
7
 
8
8
  const Processor = require('./src/Processor');
9
9
  const Router = require('./src/Router');
@@ -19,6 +19,7 @@ const CustomEntityDetectionModel = require('./src/wingbot/CustomEntityDetectionM
19
19
  const ConversationTester = require('./src/ConversationTester');
20
20
  const { asserts } = require('./src/testTools');
21
21
  const BuildRouter = require('./src/BuildRouter');
22
+ const ChatGpt = require('./src/ChatGpt');
22
23
  const MockAiModel = require('./src/MockAiModel');
23
24
  const ReturnSender = require('./src/ReturnSender');
24
25
  const CallbackAuditLog = require('./src/CallbackAuditLog');
@@ -126,6 +127,7 @@ module.exports = {
126
127
  ButtonTemplate,
127
128
  GenericTemplate,
128
129
  BaseTemplate,
130
+ ChatGpt,
129
131
 
130
132
  // tests
131
133
  ConversationTester,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wingbot",
3
- "version": "3.66.4",
3
+ "version": "3.67.0",
4
4
  "description": "Enterprise Messaging Bot Conversation Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -5,6 +5,7 @@
5
5
 
6
6
  const fetch = require('node-fetch').default;
7
7
  const Responder = require('../../src/Responder');
8
+ const ChatGpt = require('../../src/ChatGpt');
8
9
  const compileWithState = require('../../src/utils/compileWithState');
9
10
 
10
11
  const MSG_REPLACE = '#MSG-REPLACE#';
@@ -13,15 +14,11 @@ const CHAR_LIM = 4096;
13
14
 
14
15
  function chatgptPlugin (params, configuration = {}) {
15
16
  const {
16
- openAiEndpoint = null,
17
+ openAiEndpoint = undefined,
17
18
  openAiApiKey = null
18
19
  } = configuration;
19
20
 
20
21
  async function chatgpt (req, res) {
21
- const content = req.text();
22
-
23
- const charLim = params.charLim || CHAR_LIM;
24
-
25
22
  const token = compileWithState(req, res, params.token).trim();
26
23
 
27
24
  // gpt-3.5-turbo-0301 gpt-3.5-turbo
@@ -30,17 +27,13 @@ function chatgptPlugin (params, configuration = {}) {
30
27
  const system = compileWithState(req, res, params.system).trim();
31
28
 
32
29
  // 0 - 2
33
- const temperature = parseFloat(compileWithState(req, res, params.temperature).trim().replace(',', '.') || '1') || 1;
30
+ const temperature = parseFloat(compileWithState(req, res, params.temperature).trim().replace(',', '.') || '1') || undefined;
34
31
  // presence_penalty between -2.0 and 2.0
35
- const presence = parseFloat(compileWithState(req, res, params.presence).trim().replace(',', '.') || '0') || 0;
36
-
37
- const maxTokens = parseFloat(compileWithState(req, res, params.maxTokens).trim() || '512') || 512;
32
+ const presence = parseFloat(compileWithState(req, res, params.presence).trim().replace(',', '.') || '0') || undefined;
38
33
 
39
- const limit = parseInt(compileWithState(req, res, params.limit).trim() || '10', 10) || 10;
34
+ const requestTokens = parseFloat(compileWithState(req, res, params.maxTokens).trim() || '512') || undefined;
40
35
 
41
- const user = `${req.pageId}|${req.senderId}`;
42
-
43
- const systemAfter = compileWithState(req, res, params.systemAfter).trim();
36
+ const transcriptLength = parseInt(compileWithState(req, res, params.limit).trim() || '10', 10) || 10;
44
37
 
45
38
  const replacedAnnotation = `${params.annotation || ''}`.replace(/\{\{message\}\}/g, MSG_REPLACE);
46
39
  const annotation = compileWithState(req, res, replacedAnnotation).trim();
@@ -53,157 +46,27 @@ function chatgptPlugin (params, configuration = {}) {
53
46
  || continueConfig.find((c) => ['default', '']
54
47
  .includes(`${c.lang || ''}`.trim().toLowerCase()));
55
48
 
56
- let body;
49
+ const gpt = new ChatGpt({
50
+ fetch: params.fetch,
51
+ model,
52
+ presencePenalty: presence,
53
+ requestTokens,
54
+ temperature,
55
+ transcriptLength,
56
+ openAiEndpoint,
57
+ ...(openAiEndpoint
58
+ ? { apiKey: token || openAiApiKey }
59
+ : { authorization: token || openAiApiKey })
60
+ });
57
61
 
58
62
  try {
59
- res.setFlag('gpt');
60
- res.typingOn();
61
-
62
- body = {
63
- model,
64
- frequency_penalty: 0,
65
- presence_penalty: presence,
66
- max_tokens: maxTokens,
67
- temperature,
68
- user
69
- };
70
-
71
- const onlyFlag = Math.sign(limit) === -1 ? 'gpt' : null;
72
-
73
- const ts = await res.getTranscript(Math.abs(limit), onlyFlag);
74
-
75
- let total = (system ? system.length : 0)
76
- + (systemAfter ? systemAfter.length : 0)
77
- + maxTokens
78
- + content.length;
79
-
80
- for (let i = ts.length - 1; i >= 0; i--) {
81
- total += ts[i].text.length;
82
- if (total > charLim) {
83
- ts.splice(i, 1);
84
- }
85
- }
86
-
87
- const messages = [
88
- ...(system ? [{ role: 'system', content: system }] : []),
89
- ...ts.map((t) => ({ role: t.fromBot ? 'assistant' : 'user', content: t.text })),
90
- { role: 'user', content },
91
- ...(systemAfter ? [{ role: 'system', content: systemAfter }] : [])
92
- ];
93
-
94
- Object.assign(body, { messages });
95
-
96
- const useFetch = params.fetch || fetch;
97
-
98
- const apiUrl = openAiEndpoint
99
- ? `${openAiEndpoint}/chat/completions?api-version=2023-03-15-preview`
100
- : 'https://api.openai.com/v1/chat/completions';
101
-
102
- const response = await useFetch(apiUrl, {
103
- method: 'POST',
104
- headers: {
105
- 'Content-Type': 'application/json',
106
- ...(openAiEndpoint
107
- ? { 'api-key': token || openAiApiKey }
108
- : { Authorization: `Bearer ${token || openAiApiKey}` })
109
- },
110
- body: JSON.stringify(body)
63
+ await gpt.respond(req, res, {
64
+ system,
65
+ persona,
66
+ continueReply,
67
+ annotation
111
68
  });
112
-
113
- const data = await response.json();
114
-
115
- if (response.status !== 200
116
- || !Array.isArray(data.choices)) {
117
- const { status, statusText } = response;
118
- // eslint-disable-next-line no-console
119
- console.log('chat gpt error', {
120
- status, statusText, data, apiUrl
121
- });
122
- throw new Error(`Chat GPT ${status}`);
123
- }
124
-
125
- // eslint-disable-next-line no-console
126
- console.log('chat gpt', JSON.stringify(data));
127
-
128
- const sent = data.choices
129
- .filter((ch) => ch.message && ch.message.role === 'assistant' && ch.message.content)
130
- .map((ch) => {
131
-
132
- let sliced = data.usage && data.usage.completion_tokens >= maxTokens;
133
-
134
- let filtered = ch.message.content
135
- .replace(/\n\n/g, '\n')
136
- .split(/\n+(?!-)/g)
137
- .filter((t) => !!t.trim());
138
-
139
- if (filtered.length > 2) {
140
- filtered = filtered.slice(0, filtered.length - 1);
141
- sliced = true;
142
- }
143
-
144
- if (persona) {
145
- res.setPersona({ name: persona });
146
- }
147
-
148
- filtered
149
- .forEach((t, fi) => {
150
- let trim = t.trim();
151
-
152
- if (annotation) {
153
- // replace the annotation first
154
-
155
- const replacements = annotation.split(MSG_REPLACE);
156
- const last = replacements.length > 1
157
- ? replacements.length - 1
158
- : replacements.length;
159
- replacements.forEach((r, i) => {
160
- const foundI = trim.indexOf(r.trim(), i === last
161
- ? trim.length - r.trim().length
162
- : 0);
163
-
164
- if (foundI === -1
165
- || (i === 0 && foundI > 0)
166
- || (i === last && (trim.length - foundI - r.length) > 0)) {
167
- return;
168
- }
169
-
170
- trim = trim.replace(r.trim(), '').trim();
171
- });
172
- }
173
-
174
- if (annotation && annotation.includes(MSG_REPLACE)) {
175
- trim = annotation.replace(MSG_REPLACE, trim);
176
- } else if (annotation) {
177
- trim = `${annotation} ${trim}`;
178
- }
179
-
180
- res.text(trim, sliced && fi === (filtered.length - 1) && continueReply
181
- ? [
182
- {
183
- title: continueReply.title,
184
- action: res.currentAction()
185
- }
186
- ]
187
- : null);
188
- });
189
-
190
- if (persona) {
191
- res.setPersona({ name: null });
192
- }
193
-
194
- return ch;
195
- });
196
-
197
- if (sent.length === 0) {
198
- const { status, statusText } = response;
199
- // eslint-disable-next-line no-console
200
- console.log('chat gpt nothing to send', { status, statusText, data });
201
- throw new Error('Chat GPT empty');
202
- }
203
-
204
69
  } catch (e) {
205
- // eslint-disable-next-line no-console
206
- console.error('chat gpt fail', e, body);
207
70
  await res.run('fallback');
208
71
  }
209
72
 
package/src/ChatGpt.js ADDED
@@ -0,0 +1,434 @@
1
+ /**
2
+ * @author David Menger
3
+ */
4
+ 'use strict';
5
+
6
+ const nodeFetch = require('node-fetch').default;
7
+ const { PHONE_REGEX, EMAIL_REGEX } = require('./systemEntities/regexps');
8
+
9
+ /** @typedef {import('node-fetch').default} Fetch */
10
+ /** @typedef {import('./Request')} Request */
11
+ /** @typedef {import('./Responder')} Responder */
12
+ /** @typedef {import('./Responder').QuickReply} QuickReply */
13
+
14
+ /**
15
+ * @typedef {object} Transcript
16
+ * @prop {string} text
17
+ * @prop {boolean} fromBot
18
+ */
19
+
20
+ /**
21
+ * @typedef {object} GeneralOptions
22
+ * @prop {Fetch} [fetch]
23
+ * @prop {string} [defaultUser]
24
+ * @prop {string} [openAiEndpoint]
25
+ * @prop {string} [apiVersion]
26
+ * @prop {string} [apiKey] // for microsoft services
27
+ * @prop {string} [authorization] // for chat gpt api
28
+ */
29
+
30
+ /** @typedef {'gpt-3.5-turbo'|'gpt-4'|'gpt-4-32k'|'gpt-3.5-turbo-16k'|string} ChatGPTModel */
31
+
32
+ /**
33
+ * @typedef {object} RequestOptions
34
+ * @prop {ChatGPTModel} [model]
35
+ * @prop {number} [presencePenalty=0.0]
36
+ * @prop {number} [requestTokens=256]
37
+ * @prop {number} [tokensLimit=4096]
38
+ * @prop {number} [temperature=1.0]
39
+ * @prop {number} [transcriptLength=-5]
40
+ */
41
+
42
+ /**
43
+ * @typedef {GeneralOptions & RequestOptions} ChatGptOptions
44
+ */
45
+
46
+ /**
47
+ * @typedef {object} Message
48
+ * @prop {'system'|'user'|'assistant'|string} role
49
+ * @prop {string} content
50
+ */
51
+
52
+ /**
53
+ * @typedef {object} ChatGPTChoice
54
+ * @prop {'stop'|'length'|'function_call'|'content_filter'|null} finish_reason
55
+ * @prop {number} index
56
+ * @prop {Message} message
57
+ */
58
+
59
+ /**
60
+ * @typedef {object} ChatGPTUsage
61
+ * @prop {number} completion_tokens
62
+ * @prop {number} prompt_tokens
63
+ * @prop {number} total_tokens
64
+ */
65
+
66
+ /**
67
+ * @typedef {object} ChatGPTResponse
68
+ * @prop {ChatGPTChoice[]} choices
69
+ * @prop {number} created
70
+ * @prop {ChatGPTModel} model
71
+ * @prop {'text_completion'} object
72
+ * @prop {ChatGPTUsage} usage
73
+ */
74
+
75
+ /**
76
+ * @typedef {object} Logger
77
+ * @prop {Function} log
78
+ * @prop {Function} error
79
+ */
80
+
81
+ /**
82
+ * @typedef {object} SlicedAnnotation
83
+ * @prop {boolean} [sliced]
84
+ *
85
+ * @typedef {string[] & SlicedAnnotation} StringArrayWithSliced
86
+ */
87
+
88
+ /**
89
+ * @typedef {object} ContinueReply
90
+ * @prop {string} title
91
+ * @prop {string} [action]
92
+ */
93
+
94
+ /**
95
+ * @typedef {object} Persona
96
+ * @prop {string} [profile_pic_url]
97
+ * @prop {string} [name]
98
+ */
99
+
100
+ /**
101
+ * @typedef {object} ReplyConfiguration
102
+ * @prop {string} [system]
103
+ * @prop {string} [annotation]
104
+ * @prop {ContinueReply|boolean} [continueReply]
105
+ * @prop {string|Persona} [persona]
106
+ * @prop {boolean} [anonymize=true]
107
+ */
108
+
109
+ /**
110
+ * @class ChatGpt
111
+ */
112
+ class ChatGpt {
113
+
114
+ /**
115
+ *
116
+ * @param {ChatGptOptions} options
117
+ * @param {Logger} log
118
+ */
119
+ constructor (options, log = console) {
120
+ const {
121
+ fetch = nodeFetch,
122
+ defaultUser = null,
123
+ openAiEndpoint = 'https://api.openai.com/v1',
124
+ apiKey,
125
+ authorization,
126
+ ...rest
127
+ } = options;
128
+
129
+ this._apiKey = apiKey;
130
+ this._authorization = authorization;
131
+
132
+ this._fetch = fetch;
133
+
134
+ this._openAiEndpoint = openAiEndpoint;
135
+
136
+ this._defaultUser = defaultUser;
137
+
138
+ /** @type {Required<RequestOptions>} */
139
+ this._options = {
140
+ requestTokens: 256,
141
+ tokensLimit: 4096,
142
+ presencePenalty: 0.0, // -2.0-2.0
143
+ temperature: 1.0,
144
+ model: 'gpt-3.5-turbo',
145
+ transcriptLength: -5,
146
+ ...rest
147
+ };
148
+
149
+ this._log = log;
150
+
151
+ this.MSG_REPLACE = '#MSG-REPLACE#';
152
+
153
+ this.GPT_FLAG = 'gpt';
154
+
155
+ this._anonymizeRegexps = [
156
+ { replacement: '@PHONE', regex: new RegExp(PHONE_REGEX.source, 'g') },
157
+ { replacement: '@EMAIL', regex: new RegExp(EMAIL_REGEX.source, 'g') }
158
+ ];
159
+ }
160
+
161
+ /**
162
+ *
163
+ * @param {Responder} res
164
+ * @param {number} limit
165
+ * @param {boolean} anonymize
166
+ * @returns {Promise<Transcript[]>}
167
+ */
168
+ async getTranscript (
169
+ res,
170
+ limit = this._options.transcriptLength,
171
+ anonymize = true
172
+ ) {
173
+ const onlyFlag = Math.sign(limit) === -1 ? 'gpt' : null;
174
+ const transcript = await res.getTranscript(Math.abs(limit), onlyFlag);
175
+
176
+ if (!anonymize) {
177
+ return transcript;
178
+ }
179
+
180
+ return transcript.map((t) => ({
181
+ ...t,
182
+ text: this._anonymizeRegexps
183
+ .reduce((text, { replacement, regex }) => {
184
+ const replaced = text.replace(regex, replacement);
185
+ return replaced;
186
+ }, t.text)
187
+ }));
188
+ }
189
+
190
+ /**
191
+ *
192
+ * @param {string} content
193
+ * @param {string} [system]
194
+ * @param {Transcript[]} [transcript]
195
+ * @param {RequestOptions} [requestOptions]
196
+ * @param {string|Request} [user]
197
+ * @returns {Promise<ChatGPTChoice>}
198
+ */
199
+ async request (content, system = null, transcript = [], requestOptions = {}, user = null) {
200
+ const {
201
+ requestTokens,
202
+ tokensLimit,
203
+ model,
204
+ presencePenalty,
205
+ temperature
206
+ } = {
207
+ ...this._options,
208
+ ...requestOptions
209
+ };
210
+
211
+ const maxTokens = Math.min(requestTokens, tokensLimit);
212
+
213
+ let body;
214
+
215
+ try {
216
+ body = {
217
+ model,
218
+ frequency_penalty: 0,
219
+ presence_penalty: presencePenalty,
220
+ max_tokens: maxTokens,
221
+ temperature
222
+ };
223
+
224
+ if (typeof user === 'string') {
225
+ Object.assign(body, { user });
226
+ } else if (user) {
227
+ Object.assign(body, { user: `${user.pageId}|${user.senderId}` });
228
+ } else if (this._defaultUser) {
229
+ Object.assign(body, { user: this._defaultUser });
230
+ }
231
+
232
+ let total = (system ? system.length : 0)
233
+ + maxTokens
234
+ + content.length;
235
+
236
+ const ts = transcript.slice();
237
+
238
+ for (let i = ts.length - 1; i >= 0; i--) {
239
+ total += ts[i].text.length;
240
+ if (total > tokensLimit) {
241
+ ts.splice(i, 1);
242
+ }
243
+ }
244
+
245
+ /** @type {Message[]} */
246
+ const messages = [
247
+ ...(system ? [{ role: 'system', content: system }] : []),
248
+ ...ts.map((t) => ({ role: t.fromBot ? 'assistant' : 'user', content: t.text })),
249
+ { role: 'user', content }
250
+ ];
251
+
252
+ Object.assign(body, { messages });
253
+
254
+ const apiUrl = `${this._openAiEndpoint}/chat/completions${this._apiKey ? '?api-version=2023-03-15-preview' : ''}`;
255
+
256
+ this._log.log('#GPT request', body);
257
+
258
+ const response = await this._fetch(apiUrl, {
259
+ method: 'POST',
260
+ headers: {
261
+ 'Content-Type': 'application/json',
262
+ ...(this._apiKey
263
+ ? { 'api-key': this._apiKey }
264
+ : { Authorization: `Bearer ${this._authorization}` })
265
+ },
266
+ body: JSON.stringify(body)
267
+ });
268
+
269
+ /** @type {ChatGPTResponse} */
270
+ const data = await response.json();
271
+
272
+ if (response.status !== 200
273
+ || !Array.isArray(data.choices)) {
274
+ const { status, statusText } = response;
275
+
276
+ this._log.error('#GPT failed', {
277
+ status, statusText, data, body
278
+ });
279
+ throw new Error(`Chat GPT ${status}`);
280
+ }
281
+
282
+ const [choice] = data.choices;
283
+
284
+ this._log.log('#GPT response', { choice, data });
285
+
286
+ return choice;
287
+ } catch (e) {
288
+ this._log.error('#GPT failed', e, body);
289
+ throw e;
290
+ }
291
+ }
292
+
293
+ /**
294
+ *
295
+ * @param {ChatGPTChoice} choice
296
+ * @param {string} [annotation]
297
+ * @returns {StringArrayWithSliced}
298
+ */
299
+ toMessages (choice, annotation = null) {
300
+ let sliced = choice.finish_reason === 'length';
301
+
302
+ let filtered = choice.message.content
303
+ .replace(/\n\n/g, '\n')
304
+ .split(/\n+(?!-)/g)
305
+ .filter((t) => !!t.trim());
306
+
307
+ if (sliced && filtered.length > 1) {
308
+ filtered = filtered.slice(0, filtered.length - 1);
309
+ sliced = true;
310
+ }
311
+
312
+ filtered = filtered
313
+ .map((t) => {
314
+ let trim = t.trim();
315
+
316
+ if (annotation) {
317
+ // replace the annotation first
318
+
319
+ const replacements = annotation.split(this.MSG_REPLACE);
320
+
321
+ const last = replacements.length > 1
322
+ ? replacements.length - 1
323
+ : replacements.length;
324
+
325
+ replacements.forEach((r, i) => {
326
+ const foundI = trim.indexOf(r.trim(), i === last
327
+ ? trim.length - r.trim().length
328
+ : 0);
329
+
330
+ if (foundI === -1
331
+ || (i === 0 && foundI > 0)
332
+ || (i === last && (trim.length - foundI - r.length) > 0)) {
333
+ return;
334
+ }
335
+
336
+ trim = trim.replace(r.trim(), '').trim();
337
+ });
338
+ }
339
+
340
+ if (annotation && annotation.includes(this.MSG_REPLACE)) {
341
+ trim = annotation.replace(this.MSG_REPLACE, trim);
342
+ } else if (annotation) {
343
+ trim = `${annotation} ${trim}`;
344
+ }
345
+
346
+ return trim;
347
+ });
348
+
349
+ return Object.assign(filtered, { sliced });
350
+ }
351
+
352
+ /**
353
+ *
354
+ * @param {Responder} res
355
+ * @param {string[]} messages
356
+ * @param {QuickReply[]} [quickReplies]
357
+ */
358
+ sendMessages (res, messages, quickReplies = null) {
359
+ messages.forEach((text, i) => {
360
+ const addQuickReply = i === (messages.length - 1);
361
+ res.text(text, addQuickReply ? quickReplies : null);
362
+ });
363
+ }
364
+
365
+ /**
366
+ *
367
+ * @param {Request} req
368
+ * @param {Responder} res
369
+ * @param {ReplyConfiguration} [replyConfig]
370
+ * @param {RequestOptions} [options]
371
+ */
372
+ async respond (req, res, replyConfig = {}, options = {}) {
373
+ res.setFlag(this.GPT_FLAG);
374
+ res.typingOn();
375
+
376
+ const {
377
+ transcriptLength
378
+ } = {
379
+ ...this._options,
380
+ ...options
381
+ };
382
+
383
+ const { persona, continueReply, anonymize } = replyConfig;
384
+
385
+ const content = req.text();
386
+
387
+ const transcript = await this.getTranscript(res, transcriptLength, anonymize);
388
+ const choice = await this.request(content, replyConfig.system, transcript, options);
389
+
390
+ const messages = this.toMessages(choice, replyConfig.annotation);
391
+
392
+ if (typeof persona === 'string') {
393
+ res.setPersona({ name: persona });
394
+ } else if (persona) {
395
+ res.setPersona(persona);
396
+ }
397
+
398
+ const { sliced } = messages;
399
+
400
+ let qrs;
401
+
402
+ if (!continueReply) {
403
+ qrs = null;
404
+ } else if (continueReply === true) {
405
+ qrs = [];
406
+ } else if (typeof continueReply === 'object') {
407
+ qrs = [{
408
+ title: continueReply.title,
409
+ action: continueReply.action || res.currentAction()
410
+ }];
411
+ }
412
+
413
+ if (messages.length === 0) {
414
+ const err = new Error('#GPT nothing to send');
415
+ this._log.error('#GPT nothing to send', err, { choice, content });
416
+ throw err;
417
+ }
418
+
419
+ this.sendMessages(res, messages, qrs);
420
+
421
+ if (persona) {
422
+ res.setPersona({ name: null });
423
+ }
424
+
425
+ return {
426
+ messages,
427
+ sliced,
428
+ choice
429
+ };
430
+ }
431
+
432
+ }
433
+
434
+ module.exports = ChatGpt;
@@ -3,11 +3,17 @@
3
3
  */
4
4
  'use strict';
5
5
 
6
+ const { EMAIL_REGEX } = require('./regexps');
7
+
6
8
  /** @typedef {import('../wingbot/CustomEntityDetectionModel').EntityDetector} EntityDetector */
7
9
  /** @typedef {import('../wingbot/CustomEntityDetectionModel').DetectorOptions} DetectorOptions */
8
10
 
9
11
  /** @type {[string,EntityDetector|RegExp,DetectorOptions]} */
10
- module.exports = ['email', /(?<=(\s|^|:))[a-zA-Z0-9!#$%&'*+\-=?^_`{|}~"][^@:\s]*@[^.@\s]+\.[^@\s,]+/, {
11
- anonymize: true,
12
- clearOverlaps: true
13
- }];
12
+ module.exports = [
13
+ 'email',
14
+ EMAIL_REGEX,
15
+ {
16
+ anonymize: true,
17
+ clearOverlaps: true
18
+ }
19
+ ];
@@ -3,13 +3,15 @@
3
3
  */
4
4
  'use strict';
5
5
 
6
+ const { PHONE_REGEX } = require('./regexps');
7
+
6
8
  /** @typedef {import('../wingbot/CustomEntityDetectionModel').EntityDetector} EntityDetector */
7
9
  /** @typedef {import('../wingbot/CustomEntityDetectionModel').DetectorOptions} DetectorOptions */
8
10
 
9
11
  /** @type {[string,EntityDetector|RegExp,DetectorOptions]} */
10
12
  module.exports = [
11
13
  'phone',
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|$|[,!.?\-:]))/,
14
+ PHONE_REGEX,
13
15
  {
14
16
  anonymize: true,
15
17
  clearOverlaps: true,
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @author David Menger
3
+ */
4
+ 'use strict';
5
+
6
+ const PHONE_REGEX = /((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|$|[,!.?\-:]))/;
7
+ const EMAIL_REGEX = /(?<=(\s|^|:))[a-zA-Z0-9!#$%&'*+\-=?^_`{|}~"][^@:\s]*@[^.@\s]+\.[^@\s,]+/;
8
+
9
+ module.exports = {
10
+ PHONE_REGEX,
11
+ EMAIL_REGEX
12
+ };