@tiledesk/tiledesk-server 2.7.26 → 2.7.27

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/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