wingbot 3.53.5 → 3.54.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wingbot",
3
- "version": "3.53.5",
3
+ "version": "3.54.0",
4
4
  "description": "Enterprise Messaging Bot Conversation Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -0,0 +1 @@
1
+ > This plugin allows you to use chatGPT
@@ -0,0 +1,119 @@
1
+ /**
2
+ * @author wingbot.ai
3
+ */
4
+ 'use strict';
5
+
6
+ const fetch = require('node-fetch').default;
7
+ const compileWithState = require('../../src/utils/compileWithState');
8
+
9
+ function chatgptPlugin (params) {
10
+
11
+ async function chatgpt (req, res) {
12
+ const content = req.text();
13
+
14
+ const token = compileWithState(req, res, params.token).trim();
15
+
16
+ // gpt-3.5-turbo-0301 gpt-3.5-turbo
17
+ const model = compileWithState(req, res, params.model) || 'gpt-3.5-turbo';
18
+
19
+ const system = compileWithState(req, res, params.system).trim();
20
+
21
+ // 0 - 2
22
+ const temperature = parseFloat(compileWithState(req, res, params.temperature).trim().replace(',', '.') || '1') || 1;
23
+ // presence_penalty between -2.0 and 2.0
24
+ const presence = parseFloat(compileWithState(req, res, params.presence).trim().replace(',', '.') || '0') || 0;
25
+
26
+ const maxTokens = parseFloat(compileWithState(req, res, params.maxTokens).trim() || '512') || 512;
27
+
28
+ const limit = parseInt(compileWithState(req, res, params.maxTokens).trim() || '10', 10) || 10;
29
+
30
+ const user = `${req.pageId}|${req.senderId}`;
31
+
32
+ const systemAfter = compileWithState(req, res, params.systemAfter).trim();
33
+
34
+ let body;
35
+
36
+ try {
37
+ res.typingOn();
38
+
39
+ body = {
40
+ model,
41
+ frequency_penalty: 0,
42
+ presence_penalty: presence,
43
+ max_tokens: maxTokens,
44
+ temperature,
45
+ user
46
+ };
47
+
48
+ const ts = await res.getTranscript(limit);
49
+
50
+ const messages = [
51
+ ...(system ? [{ role: 'system', content: system }] : []),
52
+ ...ts.map((t) => ({ role: t.fromBot ? 'assistant' : 'user', content: t.text })),
53
+ { role: 'user', content },
54
+ ...(systemAfter ? [{ role: 'system', content: systemAfter }] : [])
55
+ ];
56
+
57
+ Object.assign(body, { messages });
58
+
59
+ const useFetch = params.fetch || fetch;
60
+
61
+ const response = await useFetch('https://api.openai.com/v1/chat/completions', {
62
+ method: 'POST',
63
+ headers: {
64
+ 'Content-Type': 'application/json',
65
+ Authorization: `Bearer ${token}`
66
+ },
67
+ body: JSON.stringify(body)
68
+ });
69
+
70
+ const data = await response.json();
71
+
72
+ if (response.status !== 200
73
+ || !Array.isArray(data.choices)) {
74
+ const { status, statusText } = response;
75
+ // eslint-disable-next-line no-console
76
+ console.log('chat gpt error', { status, statusText, data });
77
+ throw new Error(`Chat GPT ${status}`);
78
+ }
79
+
80
+ // eslint-disable-next-line no-console
81
+ console.log('chat gpt', JSON.stringify(data));
82
+
83
+ const sent = data.choices
84
+ .filter((ch) => ch.message && ch.message.role === 'assistant' && ch.message.content)
85
+ .map((ch) => {
86
+ let filtered = ch.message.content
87
+ .replace(/\n\n/g, '\n')
88
+ .split(/\n+(?!-)/g)
89
+ .filter((t) => !!t.trim());
90
+
91
+ if (filtered.length > 2) {
92
+ filtered = filtered.slice(0, filtered.length - 1);
93
+ }
94
+
95
+ filtered
96
+ .forEach((t) => res.text(t.trim()));
97
+
98
+ return ch;
99
+ });
100
+
101
+ if (sent.length === 0) {
102
+ const { status, statusText } = response;
103
+ // eslint-disable-next-line no-console
104
+ console.log('chat gpt nothing to send', { status, statusText, data });
105
+ throw new Error('Chat GPT empty');
106
+ }
107
+
108
+ } catch (e) {
109
+ // eslint-disable-next-line no-console
110
+ console.error('chat gpt fail', e, body);
111
+ await res.run('fallback');
112
+ }
113
+
114
+ }
115
+
116
+ return chatgpt;
117
+ }
118
+
119
+ module.exports = chatgptPlugin;
@@ -484,6 +484,86 @@
484
484
  ],
485
485
  "items": [
486
486
  ]
487
+ },
488
+ {
489
+ "id": "ai.wingbot.openai",
490
+ "name": "OpenAI - Chat",
491
+ "description": "Use within fallback to attach ChatGPT to your chatbot",
492
+ "availableSince": 3.54,
493
+ "editable": false,
494
+ "isFactory": true,
495
+ "category": "conversation",
496
+ "inputs": [
497
+ {
498
+ "name": "token",
499
+ "label": "API Key",
500
+ "type": "text",
501
+ "validations": [
502
+ { "type": "regexp", "value": "^.+$", "message": "the token should be filled" }
503
+ ]
504
+ },
505
+ {
506
+ "type": "select",
507
+ "name": "model",
508
+ "label": "Chat GPT model",
509
+ "options": [
510
+ { "value": "gpt-3.5-turbo", "label": "gpt-3.5-turbo" },
511
+ { "value": "gpt-3.5-turbo-0301", "label": "gpt-3.5-turbo-0301" }
512
+ ]
513
+ },
514
+ {
515
+ "type": "textarea",
516
+ "name": "system",
517
+ "label": "Initial preset message (system)"
518
+ },
519
+ {
520
+ "type": "text",
521
+ "name": "temperature",
522
+ "label": "Temperature: 0.0 - 2.0 (1.0) - randomness of answers",
523
+ "validations": [
524
+ {
525
+ "type": "regexp",
526
+ "value": "^[0-9]*([.,][0-9]+)?$",
527
+ "message": "Temperature should be a valid float number"
528
+ }
529
+ ]
530
+ },
531
+ {
532
+ "type": "text",
533
+ "name": "presence",
534
+ "label": "Presence: -2.0 - 2.0 (0.0) - likelihood to talk about new topics",
535
+ "validations": [
536
+ {
537
+ "type": "regexp",
538
+ "value": "^-?[0-9]*([.,][0-9]+)?$",
539
+ "message": "Presence should be a valid float number"
540
+ }
541
+ ]
542
+ },
543
+ {
544
+ "type": "text",
545
+ "name": "maxTokens",
546
+ "label": "Max Tokens: 0 - 4096 (512) - max. number of returned tokens",
547
+ "validations": [
548
+ {
549
+ "type": "regexp",
550
+ "value": "^[0-9]*$",
551
+ "message": "Max tokens should be a valid number"
552
+ }
553
+ ]
554
+ },
555
+ {
556
+ "type": "textarea",
557
+ "name": "systemAfter",
558
+ "label": "Current message (system)"
559
+ }
560
+ ],
561
+ "items": [
562
+ {
563
+ "id": "fallback",
564
+ "description": "If API call fails"
565
+ }
566
+ ]
487
567
  }
488
568
  ],
489
569
  "categories": [
package/src/Processor.js CHANGED
@@ -646,7 +646,8 @@ class Processor extends EventEmitter {
646
646
  const options = {
647
647
  ...this.options,
648
648
  state: Object.freeze({ ...state }),
649
- features
649
+ features,
650
+ pageId
650
651
  };
651
652
 
652
653
  res = new Responder(senderId, messageSender, token, options, responderData);
package/src/Responder.js CHANGED
@@ -16,6 +16,7 @@ const {
16
16
  FEATURE_SSML,
17
17
  FEATURE_PHRASES
18
18
  } = require('./features');
19
+ const transcriptFromHistory = require('./transcript/transcriptFromHistory');
19
20
 
20
21
  const TYPE_RESPONSE = 'RESPONSE';
21
22
  const TYPE_UPDATE = 'UPDATE';
@@ -26,6 +27,7 @@ const EXCEPTION_HOPCOUNT_THRESHOLD = 5;
26
27
  /** @typedef {import('./ReturnSender').UploadResult} UploadResult */
27
28
  /** @typedef {import('./analytics/consts').TrackingCategory} TrackingCategory */
28
29
  /** @typedef {import('./analytics/consts').TrackingType} TrackingType */
30
+ /** @typedef {import('./transcript/transcriptFromHistory').Transcript} Transcript */
29
31
 
30
32
  /**
31
33
  * @enum {string} ExpectedInput
@@ -89,6 +91,7 @@ class Responder {
89
91
  constructor (senderId, messageSender, token = null, options = {}, data = {}) {
90
92
  this._messageSender = messageSender;
91
93
  this._senderId = senderId;
94
+ this._pageId = options.pageId;
92
95
  this.token = token;
93
96
 
94
97
  /**
@@ -178,6 +181,33 @@ class Responder {
178
181
  this._recipient = { id: senderId };
179
182
 
180
183
  this._textResponses = [];
184
+
185
+ this._typingSent = false;
186
+ }
187
+
188
+ /**
189
+ *
190
+ * Returns current conversation transcript
191
+ *
192
+ * @param {number} [limit]
193
+ * @returns {Promise<Transcript[]>}
194
+ */
195
+ async getTranscript (limit = 10) {
196
+ const { chatLogStorage } = this._messageSender;
197
+ if (!chatLogStorage) {
198
+ return [];
199
+ }
200
+ const transcript = await transcriptFromHistory(
201
+ chatLogStorage,
202
+ this._senderId,
203
+ this._pageId,
204
+ limit
205
+ );
206
+ const { responseTexts = [] } = chatLogStorage;
207
+ transcript.push(...responseTexts.map((text) => ({
208
+ fromBot: true, text
209
+ })));
210
+ return transcript;
181
211
  }
182
212
 
183
213
  /**
@@ -287,6 +317,7 @@ class Responder {
287
317
  });
288
318
  }
289
319
  this.startedOutput = true;
320
+ this._typingSent = data.sender_action === 'typing_on';
290
321
  this._messageSender.send(data);
291
322
  return this;
292
323
  }
@@ -979,10 +1010,11 @@ class Responder {
979
1010
  /**
980
1011
  * Sends "typing..." information
981
1012
  *
1013
+ * @param {boolean} [force] - send even if was recently sent
982
1014
  * @returns {this}
983
1015
  */
984
- typingOn () {
985
- return this._senderAction('typing_on');
1016
+ typingOn (force = false) {
1017
+ return this._senderAction('typing_on', force);
986
1018
  }
987
1019
 
988
1020
  /**
@@ -1243,7 +1275,10 @@ class Responder {
1243
1275
  return this._textResponses;
1244
1276
  }
1245
1277
 
1246
- _senderAction (action) {
1278
+ _senderAction (action, force = false) {
1279
+ if (action === 'typing_on' && this._typingSent && !force) {
1280
+ return this;
1281
+ }
1247
1282
  const messageData = {
1248
1283
  sender_action: action
1249
1284
  };
@@ -12,10 +12,21 @@ const extractText = require('./transcript/extractText');
12
12
  /** @typedef {import('./Responder')} Responder */
13
13
  /** @typedef {import('./Processor').TrackingObject} TrackingObject */
14
14
 
15
+ /**
16
+ * @callback GetInteractions
17
+ * @param {string} senderId
18
+ * @param {string} pageId
19
+ * @param {number} [limit]
20
+ * @param {number} [endAt] - iterate backwards to history
21
+ * @param {number} [startAt] - iterate forward to last interaction
22
+ * @returns {Promise<object[]>}
23
+ */
24
+
15
25
  /**
16
26
  * @typedef {object} ChatLogStorage
17
- * @property {Function} log
18
- * @property {Function} error
27
+ * @prop {Function} log
28
+ * @prop {Function} error
29
+ * @prop {GetInteractions} [getInteractions]
19
30
  */
20
31
 
21
32
  /**
@@ -165,6 +176,13 @@ class ReturnSender {
165
176
  .filter((t) => t && `${t}`.trim());
166
177
  }
167
178
 
179
+ /**
180
+ * @returns {ChatLogStorage}
181
+ */
182
+ get chatLogStorage () {
183
+ return this._logger;
184
+ }
185
+
168
186
  _gotAnotherEvent () {
169
187
  if (this._gotAnotherEventDefer) {
170
188
  this._gotAnotherEventDefer();
@@ -33,6 +33,9 @@ const extractText = require('./extractText');
33
33
  * @returns {Promise<Transcript[]>}
34
34
  */
35
35
  async function transcriptFromHistory (chatLogStorage, senderId, pageId, limit = 20) {
36
+ if (typeof chatLogStorage.getInteractions !== 'function') {
37
+ return [];
38
+ }
36
39
  const data = await chatLogStorage.getInteractions(senderId, pageId, limit);
37
40
 
38
41
  return data