@tiledesk/tiledesk-server 2.7.25 → 2.7.27

Sign up to get free protection for your applications and to get access to all the features.
package/routes/openai.js CHANGED
@@ -4,7 +4,7 @@ var { KBSettings } = require('../models/kb_setting');
4
4
  var openaiService = require('../services/openaiService');
5
5
  var winston = require('../config/winston');
6
6
  const { QuoteManager } = require('../services/QuoteManager');
7
- const { MODEL_MULTIPLIER } = require('../utils/aiUtils');
7
+ const { MODELS_MULTIPLIER } = require('../utils/aiUtils');
8
8
 
9
9
  router.post('/', async (req, res) => {
10
10
 
@@ -37,7 +37,7 @@ router.post('/', async (req, res) => {
37
37
  if (usePublicKey === true) {
38
38
  let isAvailable = await quoteManager.checkQuote(req.project, obj, 'tokens');
39
39
  if (isAvailable === false) {
40
- return res.status(403).send("Tokens quota exceeded")
40
+ return res.status(403).send({ success: false, message: "Tokens quota exceeded", error_code: 13001})
41
41
  }
42
42
  }
43
43
 
@@ -61,7 +61,7 @@ router.post('/', async (req, res) => {
61
61
  json.messages.unshift(message);
62
62
  }
63
63
 
64
- let multiplier = MODEL_MULTIPLIER[json.model];
64
+ let multiplier = MODELS_MULTIPLIER[json.model];
65
65
  if (!multiplier) {
66
66
  multiplier = 1;
67
67
  winston.info("No multiplier found for AI model")
package/routes/project.js CHANGED
@@ -239,6 +239,7 @@ router.put('/:projectid', [passport.authenticate(['basic', 'jwt'], { session: fa
239
239
  winston.debug('UPDATE PROJECT REQ BODY ', req.body);
240
240
 
241
241
  var update = {};
242
+ let updating_quotes = false;
242
243
 
243
244
  if (req.body.profile) {
244
245
 
@@ -248,14 +249,22 @@ router.put('/:projectid', [passport.authenticate(['basic', 'jwt'], { session: fa
248
249
 
249
250
  winston.debug("Superadmin can modify the project profile")
250
251
  update.profile = req.body.profile;
252
+ if (req.body.profile.quotes) {
253
+ updating_quotes = true;
254
+ }
251
255
 
252
- delete req.user.attributes.isSuperadmin;
253
- }
256
+ /**
257
+ * Possibile Miglioramento
258
+ * Eliminare solo le chiavi di redis di notify solo per le quote che si stanno modificando.
259
+ * Per farlo è necessario permettere la modifica puntuale del project profile, attualmente non disponibile.
260
+ */
254
261
 
255
- else {
256
- winston.verbose("Project profile can't be modified by the current user " + req.user._id);
257
- return res.status(403).send({ success: false, error: "You don't have the permission required to modify the project profile"});
258
- }
262
+ delete req.user.attributes.isSuperadmin;
263
+ }
264
+ else {
265
+ winston.verbose("Project profile can't be modified by the current user " + req.user._id);
266
+ return res.status(403).send({ success: false, error: "You don't have the permission required to modify the project profile"});
267
+ }
259
268
 
260
269
  // check if super admin
261
270
  // let token = req.headers.authorization
@@ -457,6 +466,12 @@ router.put('/:projectid', [passport.authenticate(['basic', 'jwt'], { session: fa
457
466
  return res.status(500).send({ success: false, msg: 'Error updating object.' });
458
467
  }
459
468
  projectEvent.emit('project.update', updatedProject );
469
+
470
+ if (updating_quotes == true) {
471
+ let obj = { createdAt: new Date() };
472
+ let quoteManager = req.app.get('quote_manager');
473
+ quoteManager.invalidateCheckpointKeys(updatedProject, obj);
474
+ }
460
475
  res.json(updatedProject);
461
476
  });
462
477
  });
package/routes/quotes.js CHANGED
@@ -2,6 +2,7 @@ var express = require('express');
2
2
  var router = express.Router();
3
3
  const { QuoteManager } = require('../services/QuoteManager');
4
4
  let winston = require('../config/winston');
5
+ const { MODELS_MULTIPLIER } = require('../utils/aiUtils');
5
6
 
6
7
 
7
8
  router.post('/', async (req, res) => {
@@ -39,12 +40,20 @@ router.get('/:type', async (req, res) => {
39
40
  router.post('/incr/:type', async (req, res) => {
40
41
 
41
42
  let type = req.params.type;
42
- let body = req.body;
43
- body.createdAt = new Date();
43
+ let data = req.body;
44
44
 
45
45
  let quoteManager = req.app.get('quote_manager');
46
- let incremented_key = await quoteManager.incrementTokenCount(req.project, req.body);
47
- let quote = await quoteManager.getCurrentQuote(req.project, req.body, type);
46
+
47
+ let multiplier = MODELS_MULTIPLIER[data.model];
48
+ if (!multiplier) {
49
+ multiplier = 1;
50
+ winston.info("No multiplier found for AI model")
51
+ }
52
+ data.multiplier = multiplier;
53
+ data.createdAt = new Date();
54
+
55
+ let incremented_key = await quoteManager.incrementTokenCount(req.project, data);
56
+ let quote = await quoteManager.getCurrentQuote(req.project, data, type);
48
57
 
49
58
  res.status(200).send({ message: "value incremented for key " + incremented_key, key: incremented_key, currentQuote: quote });
50
59
  })
@@ -4,12 +4,21 @@ const requestEvent = require('../event/requestEvent');
4
4
  const messageEvent = require('../event/messageEvent');
5
5
  const emailEvent = require('../event/emailEvent');
6
6
 
7
+ // NEW
8
+ // const PLANS_LIST = {
9
+ // FREE_TRIAL: { requests: 3000, messages: 0, tokens: 250000, email: 200, chatbots: 20, kbs: 50 }, // same as PREMIUM
10
+ // SANDBOX: { requests: 200, messages: 0, tokens: 100000, email: 200, chatbots: 2, kbs: 50 },
11
+ // BASIC: { requests: 1000, messages: 0, tokens: 2000000, email: 200, chatbots: 10, kbs: 200},
12
+ // PREMIUM: { requests: 3000, messages: 0, tokens: 5000000, email: 200, chatbots: 20, kbs: 500},
13
+ // CUSTOM: { requests: 3000, messages: 0, tokens: 5000000, email: 200, chatbots: 20, kbs: 500}
14
+ // }
15
+
7
16
  const PLANS_LIST = {
8
- FREE_TRIAL: { requests: 3000, messages: 0, tokens: 250000, email: 200, chatbots: 20, kbs: 50 }, // same as PREMIUM
9
- SANDBOX: { requests: 200, messages: 0, tokens: 10000, email: 200, chatbots: 2, kbs: 50 },
10
- BASIC: { requests: 800, messages: 0, tokens: 50000, email: 200, chatbots: 5, kbs: 150},
11
- PREMIUM: { requests: 3000, messages: 0, tokens: 250000, email: 200, chatbots: 20, kbs: 300},
12
- CUSTOM: { requests: 3000, messages: 0, tokens: 250000, email: 200, chatbots: 20, kbs: 1000}
17
+ FREE_TRIAL: { requests: 3000, messages: 0, tokens: 5000000, email: 200, chatbots: 20, kbs: 50 }, // same as PREMIUM
18
+ SANDBOX: { requests: 200, messages: 0, tokens: 100000, email: 200, chatbots: 2, kbs: 50 },
19
+ BASIC: { requests: 800, messages: 0, tokens: 2000000, email: 200, chatbots: 5, kbs: 150},
20
+ PREMIUM: { requests: 3000, messages: 0, tokens: 5000000, email: 200, chatbots: 20, kbs: 300},
21
+ CUSTOM: { requests: 3000, messages: 0, tokens: 5000000, email: 200, chatbots: 20, kbs: 1000}
13
22
  }
14
23
 
15
24
  const typesList = ['requests', 'messages', 'email', 'tokens', 'chatbots', 'kbs']
@@ -41,6 +50,7 @@ class QuoteManager {
41
50
  winston.verbose("[QuoteManager] incrementRequestsCount key: " + key);
42
51
 
43
52
  await this.tdCache.incr(key)
53
+ this.sendEmailIfQuotaExceeded(project, request, 'requests', key);
44
54
  return key;
45
55
  }
46
56
 
@@ -61,6 +71,7 @@ class QuoteManager {
61
71
  winston.verbose("[QuoteManager] incrementEmailCount key: " + key);
62
72
 
63
73
  await this.tdCache.incr(key)
74
+ this.sendEmailIfQuotaExceeded(project, email, 'email', key);
64
75
  return key;
65
76
  }
66
77
 
@@ -78,7 +89,7 @@ class QuoteManager {
78
89
  let tokens = data.tokens * data.multiplier;
79
90
  await this.tdCache.incrbyfloat(key, tokens);
80
91
  // await this.tdCache.incrby(key, tokens);
81
-
92
+ this.sendEmailIfQuotaExceeded(project, data, 'tokens', key);
82
93
  return key;
83
94
  }
84
95
  // INCREMENT KEY SECTION - END
@@ -89,11 +100,22 @@ class QuoteManager {
89
100
  winston.debug("generateKey object ", object)
90
101
  winston.debug("generateKey type " + type)
91
102
  let subscriptionDate;
92
- if (this.project.profile.subStart) {
93
- subscriptionDate = this.project.profile.subStart;
103
+
104
+ if (this.project.isActiveSubscription === true) {
105
+ if (this.project.profile.subStart) {
106
+ subscriptionDate = this.project.profile.subStart;
107
+ } else {
108
+ // it should never happen
109
+ winston.error("Error: quote manager - isActiveSubscription is true but subStart does not exists.")
110
+ }
94
111
  } else {
95
- subscriptionDate = this.project.createdAt;
112
+ if (this.project.profile.subEnd) {
113
+ subscriptionDate = this.project.profile.subEnd;
114
+ } else {
115
+ subscriptionDate = this.project.createdAt;
116
+ }
96
117
  }
118
+
97
119
  let objectDate = object.createdAt;
98
120
  winston.debug("objectDate " + objectDate);
99
121
 
@@ -124,7 +146,7 @@ class QuoteManager {
124
146
  }
125
147
 
126
148
  /**
127
- * Get quotes for a all types (tokens and request and ...)
149
+ * Get quotes for all types (tokens and request and ...)
128
150
  */
129
151
  async getAllQuotes(project, obj) {
130
152
 
@@ -174,6 +196,124 @@ class QuoteManager {
174
196
  }
175
197
  }
176
198
 
199
+ async checkQuoteForAlert(project, object, type) {
200
+
201
+ if (quotes_enabled === false) {
202
+ winston.verbose("QUOTES DISABLED - checkQuote for type " + type);
203
+ return (null, null);
204
+ }
205
+
206
+ this.project = project;
207
+ let limits = await this.getPlanLimits();
208
+ winston.verbose("limits for current plan: ", limits)
209
+
210
+ let quote = await this.getCurrentQuote(project, object, type);
211
+ winston.verbose("getCurrentQuote resp: ", quote)
212
+
213
+ let data = {
214
+ limits: limits,
215
+ quote: quote
216
+ }
217
+
218
+ return data;
219
+ }
220
+
221
+ async sendEmailIfQuotaExceeded(project, object, type, key) {
222
+
223
+ let data = await this.checkQuoteForAlert(project, object, type);
224
+ let limits = data.limits;
225
+ let limit = data.limits[type];
226
+ let quote = data.quote;
227
+
228
+ const checkpoint = await this.percentageCalculator(limit, quote);
229
+ if (checkpoint == 0) {
230
+ return;
231
+ }
232
+ winston.verbose("checkpoint perc: ", checkpoint);
233
+
234
+ // Generate redis key
235
+ let nKey = key + ":notify:" + checkpoint;
236
+ let result = await this.tdCache.get(nKey);
237
+ if (!result) {
238
+
239
+ let allQuotes = await this.getAllQuotes(project, object);
240
+ let quotes = await this.generateQuotesObject(allQuotes, limits);
241
+
242
+ let data = {
243
+ id_project: project._id,
244
+ project_name: project.name,
245
+ type: type,
246
+ checkpoint: checkpoint,
247
+ quotes: quotes
248
+ }
249
+
250
+ emailEvent.emit('email.send.quote.checkpoint', data);
251
+ await this.tdCache.set(nKey, 'true', {EX: 2592000}); //seconds in one month = 2592000
252
+ } else {
253
+ winston.verbose("Quota checkpoint reached email already sent.")
254
+ }
255
+
256
+ }
257
+
258
+ async percentageCalculator(limit, quote) {
259
+
260
+ let p = (quote / limit) * 100;
261
+
262
+ if (p >= 100) { return 100; }
263
+ if (p >= 95) { return 95; }
264
+ if (p >= 75) { return 75; }
265
+ if (p >= 50) { return 50; }
266
+
267
+ return 0;
268
+
269
+ }
270
+
271
+ async invalidateCheckpointKeys(project, obj) {
272
+
273
+ this.project = project;
274
+ winston.verbose("invalidateCheckpointKeys project " + project._id);
275
+ let requests_key = await this.generateKey(obj, 'requests');
276
+ let tokens_key = await this.generateKey(obj, 'tokens');
277
+ let email_key = await this.generateKey(obj, 'email');
278
+
279
+ let checkpoints = ['50', '75', '95', '100']
280
+
281
+ checkpoints.forEach( async (checkpoint) => {
282
+ let nrequests_key = requests_key + ":notify:" + checkpoint;
283
+ let ntokens_key = tokens_key + ":notify:" + checkpoint;
284
+ let nemail_key = email_key + ":notify:" + checkpoint;
285
+
286
+ winston.verbose("invalidateCheckpointKeys nrequests_key: " + nrequests_key);
287
+ winston.verbose("invalidateCheckpointKeys ntokens_key: " + ntokens_key);
288
+ winston.verbose("invalidateCheckpointKeys nemail_key: " + nemail_key);
289
+
290
+ this.tdCache.del(nrequests_key);
291
+ this.tdCache.del(ntokens_key);
292
+ this.tdCache.del(nemail_key);
293
+
294
+ return true;
295
+ })
296
+
297
+ }
298
+
299
+ async generateQuotesObject(quotes, limits) {
300
+ let quotes_obj = {
301
+ requests: {
302
+ quote: quotes.requests.quote,
303
+ perc: ((quotes.requests.quote / limits['requests']) * 100).toFixed(1)
304
+ },
305
+ tokens: {
306
+ quote: quotes.tokens.quote,
307
+ perc: ((quotes.tokens.quote / limits['tokens']) * 100).toFixed(1)
308
+ },
309
+ email: {
310
+ quote: quotes.email.quote,
311
+ perc: ((quotes.email.quote / limits['email']) * 100).toFixed(1)
312
+ }
313
+ }
314
+ return quotes_obj
315
+ }
316
+
177
317
 
178
318
  async getPlanLimits(project) {
179
319
 
@@ -182,13 +322,11 @@ class QuoteManager {
182
322
  };
183
323
 
184
324
  let limits;
325
+ const plan = this.project.profile.name;
326
+
185
327
  if (this.project.profile.type === 'payment') {
186
328
 
187
- const plan = this.project.profile.name;
188
329
  switch (plan) {
189
- case 'Sandbox':
190
- limits = PLANS_LIST.SANDBOX;
191
- break;
192
330
  case 'Basic':
193
331
  limits = PLANS_LIST.BASIC;
194
332
  break;
@@ -211,14 +349,21 @@ class QuoteManager {
211
349
  limits = PLANS_LIST.FREE_TRIAL;
212
350
  }
213
351
  } else {
214
- limits = PLANS_LIST.FREE_TRIAL;
215
- }
216
352
 
353
+ if (this.project.trialExpired === false) {
354
+ limits = PLANS_LIST.FREE_TRIAL
355
+ } else {
356
+ limits = PLANS_LIST.SANDBOX;
357
+ }
358
+
359
+ }
217
360
  if (this.project?.profile?.quotes) {
218
361
  let profile_quotes = this.project?.profile?.quotes;
219
362
  const merged_quotes = Object.assign({}, limits, profile_quotes);
363
+ winston.verbose("Custom Limits: ", limits)
220
364
  return merged_quotes;
221
365
  } else {
366
+ winston.verbose("Default Limits: ", limits)
222
367
  return limits;
223
368
  }
224
369
  }
@@ -256,6 +401,8 @@ class QuoteManager {
256
401
  if (quotes_enabled === true) {
257
402
  winston.verbose("request.create.quote event catched");
258
403
  let result = await this.incrementRequestsCount(payload.project, payload.request);
404
+
405
+
259
406
  return result;
260
407
  } else {
261
408
  winston.verbose("QUOTES DISABLED - request.create.quote event")
@@ -149,7 +149,6 @@ class EmailService {
149
149
  var that = this;
150
150
  winston.debug('EmailService readTemplate: ' + templateName + ' environmentVariableKey: ' + environmentVariableKey + ' setting ' + JSON.stringify(settings));
151
151
 
152
-
153
152
  if (settings && settings.email && settings.email.templates) {
154
153
 
155
154
  var templates = settings.email.templates;
@@ -168,6 +167,21 @@ class EmailService {
168
167
  return resolve(template);
169
168
  });
170
169
  }
170
+ else {
171
+ var envTemplate = process.env[environmentVariableKey];
172
+ winston.debug('EmailService envTemplate: ' + envTemplate);
173
+
174
+ if (envTemplate) {
175
+ winston.debug('EmailService return envTemplate: ' + envTemplate);
176
+
177
+ return envTemplate;
178
+ } else {
179
+ winston.debug('EmailService return file: ' + templateName);
180
+
181
+ return that.readTemplateFile(templateName);
182
+ }
183
+ }
184
+
171
185
  // else {
172
186
  // return that.readTemplateFile(templateName);
173
187
  // }
@@ -1816,6 +1830,44 @@ class EmailService {
1816
1830
 
1817
1831
  }
1818
1832
 
1833
+ async sendEmailQuotaCheckpointReached(to, firstname, project_name, resource_name, checkpoint, quotes) {
1834
+
1835
+ winston.info("sendEmailQuotaCheckpointReached: " + to);
1836
+
1837
+ var that = this;
1838
+
1839
+ let html = await this.readTemplate('checkpointReachedEmail.html', undefined, "EMAIL_QUOTA_CHECKPOINT_REACHED");
1840
+ winston.debug("html: " + html);
1841
+
1842
+ let template = handlebars.compile(html);
1843
+
1844
+ let requests_quote = quotes.requests.quote;
1845
+ let requests_perc = quotes.requests.perc;
1846
+
1847
+ let tokens_quote = quotes.tokens.quote;
1848
+ let tokens_perc = quotes.tokens.perc;
1849
+
1850
+ let email_quote = quotes.email.quote;
1851
+ let email_perc = quotes.email.perc;
1852
+
1853
+ let replacements = {
1854
+ firstname: firstname,
1855
+ project_name: project_name,
1856
+ resource_name: resource_name,
1857
+ checkpoint: checkpoint,
1858
+ requests_quote: requests_quote,
1859
+ requests_perc: requests_perc,
1860
+ tokens_quote: tokens_quote,
1861
+ tokens_perc: tokens_perc,
1862
+ email_quote: email_quote,
1863
+ email_perc: email_perc
1864
+ }
1865
+
1866
+ html = template(replacements);
1867
+
1868
+ that.send({ to: to, subject: "Update on resources usage", html: html });
1869
+ }
1870
+
1819
1871
  parseText(text, payload) {
1820
1872
 
1821
1873
 
@@ -525,6 +525,8 @@ class RequestService {
525
525
 
526
526
  }
527
527
 
528
+ let isTestConversation = false;
529
+
528
530
  var that = this;
529
531
 
530
532
  return new Promise(async (resolve, reject) => {
@@ -548,10 +550,16 @@ class RequestService {
548
550
  request: request
549
551
  }
550
552
 
551
- let available = await qm.checkQuote(p, request, 'requests');
552
- if (available === false) {
553
- winston.info("Requests limits reached for project " + p._id)
554
- return false;
553
+ if (attributes && attributes.sourcePage && (attributes.sourcePage.indexOf("td_draft=true") > -1)) {
554
+ winston.verbose("is a test conversation --> skip quote availability check")
555
+ isTestConversation = true;
556
+ }
557
+ else {
558
+ let available = await qm.checkQuote(p, request, 'requests');
559
+ if (available === false) {
560
+ winston.info("Requests limits reached for project " + p._id)
561
+ return false;
562
+ }
555
563
  }
556
564
 
557
565
 
@@ -717,7 +725,10 @@ class RequestService {
717
725
  winston.verbose("Performance Request created in millis: " + endDate - startDate);
718
726
 
719
727
  requestEvent.emit('request.create.simple', savedRequest);
720
- requestEvent.emit('request.create.quote', payload);;
728
+
729
+ if (!isTestConversation) {
730
+ requestEvent.emit('request.create.quote', payload);;
731
+ }
721
732
 
722
733
  return resolve(savedRequest);
723
734