@tiledesk/tiledesk-server 2.8.0 → 2.8.3

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/CHANGELOG.md CHANGED
@@ -5,7 +5,23 @@
5
5
  🚀 IN PRODUCTION 🚀
6
6
  (https://www.npmjs.com/package/@tiledesk/tiledesk-server/v/2.3.77)
7
7
 
8
+ # 2.8.3
9
+ - Added advanced_context support
10
+ - Updated default preview settings
11
+
12
+ # 2.8.2
13
+ - Updated default preview settings
14
+ - Added scrape_type in /scrape/single for fix indexing on retrain
15
+
8
16
  # 2.8.1
17
+ - Added trashed=false in chatbot namespace query
18
+ - Return empty array if no chatbots are using the namespace
19
+ - Enhanced kb context
20
+ - Added enpoint to retrieve all content's chunks
21
+ - Added limit on namespaces
22
+ - Restore beenInvitedNewUser email
23
+
24
+ # 2.8.0
9
25
  - Enable quotas for conversations, tokens, and direct email.
10
26
  - Added namespaces to knowledge base
11
27
  - Updated tybot-connector to 0.2.82
package/deploy.sh CHANGED
@@ -1,5 +1,5 @@
1
1
  git pull
2
- npm version minor
2
+ npm version patch
3
3
  version=`node -e 'console.log(require("./package.json").version)'`
4
4
  echo "version $version"
5
5
 
package/models/request.js CHANGED
@@ -197,6 +197,9 @@ var RequestSchema = new Schema({
197
197
  closed_by: {
198
198
  type: String
199
199
  },
200
+ duration: {
201
+ type: Number
202
+ },
200
203
 
201
204
  tags: [TagSchema],
202
205
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tiledesk/tiledesk-server",
3
3
  "description": "The Tiledesk server module",
4
- "version": "2.8.0",
4
+ "version": "2.8.3",
5
5
  "scripts": {
6
6
  "start": "node ./bin/www",
7
7
  "pretest": "mongodb-runner start",
@@ -186,6 +186,11 @@ router.get('/requests/aggregate/status', function(req, res) {
186
186
  winston.debug('req.query.participant', req.query.participant);
187
187
  query.participants = req.query.participant;
188
188
  }
189
+
190
+ if (req.query.channel) {
191
+ winston.debug('req.query.channel', req.query.channel);
192
+ query['channel.name'] = req.query.channel;
193
+ }
189
194
 
190
195
  winston.debug("QueryParams_LastDayCHART:", lastdays,req.query.department_id)
191
196
  winston.debug("Query_LastDayCHART", query)
@@ -632,6 +637,11 @@ router.get('/requests/aggregate/status', function(req, res) {
632
637
  query.participants = req.query.participant;
633
638
  }
634
639
 
640
+ if (req.query.channel) {
641
+ winston.debug('req.query.channel', req.query.channel);
642
+ query['channel.name'] = req.query.channel;
643
+ }
644
+
635
645
 
636
646
  winston.debug("QueryParams_AvgTime:", lastdays,req.query.department_id)
637
647
  winston.debug("Query_AvgTIME", query)
@@ -801,6 +811,10 @@ router.get('/requests/aggregate/status', function(req, res) {
801
811
  query.participants = req.query.participant;
802
812
  }
803
813
 
814
+ if (req.query.channel) {
815
+ winston.debug('req.query.channel', req.query.channel);
816
+ query['channel.name'] = req.query.channel;
817
+ }
804
818
 
805
819
  winston.debug("QueryParams_DurationTIME:", lastdays,req.query.department_id)
806
820
  winston.debug("Query_DurationTIME", query)
@@ -930,7 +944,10 @@ router.get('/requests/aggregate/status', function(req, res) {
930
944
  query.participants = req.query.participant;
931
945
  }
932
946
 
933
-
947
+ if (req.query.channel) {
948
+ winston.debug('req.query.channel', req.query.channel);
949
+ query['channel.name'] = req.query.channel;
950
+ }
934
951
 
935
952
  winston.debug("QueryParams_SatisfactionTIME:", lastdays,req.query.department_id)
936
953
  winston.debug("Query_SatisfactionTIME", query)
@@ -1056,6 +1073,11 @@ router.get('/requests/hasBot/count', function(req, res) {
1056
1073
  query.department= new ObjectId(req.query.department_id);
1057
1074
 
1058
1075
  }
1076
+
1077
+ if (req.query.channel) {
1078
+ winston.debug('req.query.channel', req.query.channel);
1079
+ query['channel.name'] = req.query.channel;
1080
+ }
1059
1081
 
1060
1082
  winston.debug("QueryParams_LastDayCHART:", lastdays,req.query.department_id)
1061
1083
  winston.debug("Query_LastDayCHART", query)
@@ -1410,6 +1432,11 @@ router.get('/messages/count', function(req, res) {
1410
1432
  query.recipient = req.query.recipient;
1411
1433
  }
1412
1434
 
1435
+ if (req.query.channel) {
1436
+ winston.debug('req.query.channel', req.query.channel);
1437
+ query['channel.name'] = req.query.channel;
1438
+ }
1439
+
1413
1440
 
1414
1441
  winston.debug("Query_LastDayCHART", query)
1415
1442
 
package/routes/kb.js CHANGED
@@ -32,13 +32,16 @@ jobManager.connectAndStartPublisher(() => {
32
32
  })
33
33
 
34
34
  let default_preview_settings = {
35
- model: 'gpt-3.5-turbo',
36
- max_tokens: 128,
35
+ model: 'gpt-4o',
36
+ max_tokens: 256,
37
37
  temperature: 0.7,
38
38
  top_k: 4,
39
- context: "You are an awesome AI Assistant."
39
+ //context: "You are an awesome AI Assistant."
40
+ context: null
40
41
  }
41
42
 
43
+ let default_context = "Answer if and ONLY if the answer is contained in the context provided. If the answer is not contained in the context provided ALWAYS answer with <NOANS>\n{context}"
44
+
42
45
  /**
43
46
  * ****************************************
44
47
  * Proxy Section - Start
@@ -51,6 +54,10 @@ router.post('/scrape/single', async (req, res) => {
51
54
  let data = req.body;
52
55
  winston.debug("/scrape/single data: ", data);
53
56
 
57
+ if (!data.scrape_type) {
58
+ data.scrape_type = 1;
59
+ }
60
+
54
61
  let namespaces = await Namespace.find({ id_project: project_id }).catch((err) => {
55
62
  winston.error("find namespaces error: ", err)
56
63
  res.status(500).send({ success: false, error: err })
@@ -91,6 +98,11 @@ router.post('/scrape/single', async (req, res) => {
91
98
  json.content = kb.content;
92
99
  }
93
100
 
101
+ json.scrape_type = 1;
102
+ if (data.scrape_type) {
103
+ json.scrape_type = data.scrape_type;
104
+ }
105
+
94
106
  startScrape(json).then((response) => {
95
107
  winston.verbose("startScrape response: ", response);
96
108
  res.status(200).send(response);
@@ -200,10 +212,15 @@ router.post('/qa', async (req, res) => {
200
212
  }
201
213
  }
202
214
 
203
- if (data.system_context) {
204
- data.system_context = data.system_context + " {context}";
215
+ // Check if "Advanced Mode" is active. In such case the default_context must be not appended
216
+ if (!data.advanced_context) {
217
+ if (data.system_context) {
218
+ data.system_context = data.system_context + " \n" + default_context;
219
+ } else {
220
+ data.system_context = default_context;
221
+ }
205
222
  }
206
-
223
+
207
224
  openaiService.askNamespace(data).then((resp) => {
208
225
  winston.debug("qa resp: ", resp.data);
209
226
  let answer = resp.data;
@@ -384,11 +401,52 @@ router.get('/namespace/all', async (req, res) => {
384
401
  })
385
402
  })
386
403
 
404
+ router.get('/namespace/:id/chunks/:content_id', async (req, res) => {
405
+
406
+ let project_id = req.projectid;
407
+ let namespace_id = req.params.id;
408
+ let content_id = req.params.content_id;
409
+
410
+ let namespaces = await Namespace.find({ id_project: project_id }).catch((err) => {
411
+ winston.error("find namespaces error: ", err)
412
+ return res.status(500).send({ success: false, error: err })
413
+ })
414
+
415
+ let namespaceIds = namespaces.map(namespace => namespace.id);
416
+ if (!namespaceIds.includes(namespace_id)) {
417
+ return res.status(403).send({ success: false, error: "Not allowed. The namespace does not belong to the current project." })
418
+ }
419
+
420
+ let content = await KB.find({ id_project: project_id, namespace: namespace_id, _id: content_id }).catch((err) => {
421
+ winston.error("find content error: ", err);
422
+ return res.status(403).send({ success: false, error: err })
423
+ })
424
+
425
+ if(!content) {
426
+ return res.status(403).send({ success: false, error: "Not allowed. The conten does not belong to the current namespace." })
427
+ }
428
+
429
+ openaiService.getContentChunks(namespace_id, content_id).then((resp) => {
430
+ let chunks = resp.data;
431
+ winston.debug("chunks for content " + content_id);
432
+ winston.debug("chunks found ", chunks);
433
+ return res.status(200).send(chunks);
434
+
435
+ }).catch((err) => {
436
+ console.log("error getting content chunks err.response: ", err.response)
437
+ console.log("error getting content chunks err.data: ", err.data)
438
+ return res.status(500).send({ success: false, error: err });
439
+ })
440
+
441
+ })
442
+
387
443
  router.get('/namespace/:id/chatbots', async (req, res) => {
388
444
 
389
445
  let project_id = req.projectid;
390
446
  let namespace_id = req.params.id;
391
447
 
448
+ let chatbotsArray = [];
449
+
392
450
  let namespaces = await Namespace.find({ id_project: project_id }).catch((err) => {
393
451
  winston.error("find namespaces error: ", err)
394
452
  res.status(500).send({ success: false, error: err })
@@ -406,16 +464,15 @@ router.get('/namespace/:id/chatbots', async (req, res) => {
406
464
 
407
465
  if (!intents || intents.length == 0) {
408
466
  winston.verbose("No intents found for the selected chatbot")
409
- return res.status(200).send({ success: false, message: "No intents found for the selected chatbot" });
467
+ return res.status(200).send(chatbotsArray);
410
468
  }
411
469
 
412
470
  let chatbots = intents.map(i => i.id_faq_kb);
413
471
  let uniqueChatbots = [...new Set(chatbots)];
414
472
 
415
- let chatbotsArray = [];
416
473
  let chatbotPromises = uniqueChatbots.map(async (c_id) => {
417
474
  try {
418
- let chatbot = await faq_kb.findById(c_id);
475
+ let chatbot = await faq_kb.findOne({ _id: c_id, trashed: false });
419
476
  if (chatbot) {
420
477
  let data = {
421
478
  _id: chatbot._id,
@@ -431,7 +488,7 @@ router.get('/namespace/:id/chatbots', async (req, res) => {
431
488
  await Promise.all(chatbotPromises);
432
489
 
433
490
  winston.debug("chatbotsArray: ", chatbotsArray);
434
-
491
+
435
492
  res.status(200).send(chatbotsArray);
436
493
  })
437
494
 
@@ -457,6 +514,15 @@ router.post('/namespace', async (req, res) => {
457
514
  new_namespace.default = true;
458
515
  }
459
516
 
517
+ let quoteManager = req.app.get('quote_manager');
518
+ let limits = await quoteManager.getPlanLimits(req.project);
519
+ let ns_limit = limits.namespace;
520
+ console.log("Limit of namespaces for current plan " + ns_limit);
521
+
522
+ if (namespaces.length >= ns_limit) {
523
+ return res.status(403).send({ success: false, error: "Maximum number of resources reached for the current plan", plan_limit: ns_limit });
524
+ }
525
+
460
526
  new_namespace.save((err, savedNamespace) => {
461
527
  if (err) {
462
528
  winston.error("create namespace error: ", err);
@@ -14,11 +14,11 @@ const emailEvent = require('../event/emailEvent');
14
14
  // }
15
15
 
16
16
  const PLANS_LIST = {
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}
17
+ FREE_TRIAL: { requests: 3000, messages: 0, tokens: 5000000, email: 200, chatbots: 20, namespace: 3, kbs: 50 }, // same as PREMIUM
18
+ SANDBOX: { requests: 200, messages: 0, tokens: 100000, email: 200, chatbots: 2, namespace: 1, kbs: 50 },
19
+ BASIC: { requests: 800, messages: 0, tokens: 2000000, email: 200, chatbots: 5, namespace: 1, kbs: 150 },
20
+ PREMIUM: { requests: 3000, messages: 0, tokens: 5000000, email: 200, chatbots: 20, namespace: 3, kbs: 300 },
21
+ CUSTOM: { requests: 3000, messages: 0, tokens: 5000000, email: 200, chatbots: 20, namespace: 3, kbs: 1000 }
22
22
  }
23
23
 
24
24
  const typesList = ['requests', 'messages', 'email', 'tokens', 'chatbots', 'kbs']
@@ -162,6 +162,25 @@ class OpenaiService {
162
162
  })
163
163
  }
164
164
 
165
+ getContentChunks(namespace_id, content_id) {
166
+ winston.info("[OPENAI SERVICE] kb endpoint: " + kb_endpoint_train);
167
+ winston.info(kb_endpoint_train + "/id/" + content_id + "/namespace/" + namespace_id)
168
+ return new Promise((resolve, reject) => {
169
+
170
+ axios({
171
+ url: kb_endpoint_train + "/id/" + content_id + "/namespace/" + namespace_id,
172
+ headers: {
173
+ 'Content-Type': 'application/json'
174
+ },
175
+ method: 'GET'
176
+ }).then((resbody) => {
177
+ resolve(resbody)
178
+ }).catch((err) => {
179
+ reject(err)
180
+ })
181
+ })
182
+ }
183
+
165
184
  deleteIndex(data) {
166
185
  winston.debug("[OPENAI SERVICE] kb endpoint: " + kb_endpoint_train);
167
186
 
@@ -1266,14 +1266,16 @@ class RequestService {
1266
1266
 
1267
1267
  }
1268
1268
 
1269
- setClosedAtByRequestId(request_id, id_project, closed_at, closed_by) {
1269
+ setClosedAtByRequestId(request_id, id_project, created_at, closed_at, closed_by) {
1270
1270
 
1271
1271
  return new Promise(function (resolve, reject) {
1272
1272
  // winston.debug("request_id", request_id);
1273
1273
  // winston.debug("newstatus", newstatus);
1274
1274
 
1275
+ let duration = Math.abs(new Date(created_at) - new Date(closed_at))
1276
+
1275
1277
  return Request
1276
- .findOneAndUpdate({ request_id: request_id, id_project: id_project }, { closed_at: closed_at, closed_by: closed_by }, { new: true, upsert: false })
1278
+ .findOneAndUpdate({ request_id: request_id, id_project: id_project }, { closed_at: closed_at, closed_by: closed_by, duration: duration }, { new: true, upsert: false })
1277
1279
  .populate('lead')
1278
1280
  .populate('department')
1279
1281
  .populate('participatingBots')
@@ -1448,7 +1450,7 @@ class RequestService {
1448
1450
  }
1449
1451
 
1450
1452
  // setClosedAtByRequestId(request_id, id_project, closed_at, closed_by)
1451
- return that.setClosedAtByRequestId(request_id, id_project, new Date().getTime(), closed_by).then(function (updatedRequest) {
1453
+ return that.setClosedAtByRequestId(request_id, id_project, request.createdAt, new Date().getTime(), closed_by).then(function (updatedRequest) {
1452
1454
 
1453
1455
  winston.verbose("Request closed with id: " + updatedRequest.id);
1454
1456
  winston.debug("Request closed ", updatedRequest);
@@ -1,235 +1,230 @@
1
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
-
3
- <html
4
- lang="en"
5
- xmlns="http://www.w3.org/1999/xhtml"
6
- style="
7
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
8
- box-sizing: border-box;
9
- font-size: 14px;
10
- margin: 0;
11
- "
12
- >
13
- <head>
1
+ <!DOCTYPE html
2
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html lang="en" xmlns="http://www.w3.org/1999/xhtml"
4
+ style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
5
+
6
+ <head>
14
7
  <meta name="viewport" content="width=device-width" />
15
8
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
16
- <title>New message from Tiledesk</title>
9
+ <title>New email from Tiledesk</title>
17
10
 
18
11
  <style type="text/css">
19
- img {
20
- max-width: 100%;
21
- margin-left: 16px;
22
- text-align: center !important;
23
- }
24
-
25
- img.CToWUd {
26
- margin-bottom: 16px;
27
- max-width: 200px !important;
28
- width: 200px !important;
29
- min-width: 200px !important;
30
- outline: none;
31
- text-decoration: none;
32
- border: none;
33
- height: auto;
34
- margin-left: 0px;
35
- }
36
-
37
- body {
38
- background-color: #e2e7e9;
39
- -webkit-font-smoothing: antialiased;
40
- -webkit-text-size-adjust: none;
41
- width: 100% !important;
42
- height: 100%;
43
- line-height: 1.6em;
44
- padding: 30px 0px;
45
- }
46
-
47
- .box-container {
48
- display: flex;
49
- flex-direction: column;
50
- justify-content: center;
51
- align-items: center;
52
- width: 100%;
53
- margin-bottom: 30px;
54
- /* position: fixed;
55
- top: 90px; */
56
- }
57
-
58
- .box {
59
- background-color: #fff;
60
- width: 600px;
61
- min-height: 400px;
62
- border-radius: 8px;
63
- }
64
-
65
- .header-image {
66
- background-image: url('https://img.freepik.com/vettori-premium/illustrazione-di-progettazione-del-pacchetto-piatto-di-finanza_205077-4142.jpg?w=2000');
67
- background-size: cover;
68
- background-repeat: no-repeat;
69
- height: 200px;
70
- width: 100%;
71
- margin-bottom: 40px;
72
- border-radius: 8px;
73
- }
74
-
75
- .header {
76
- text-align: center;
77
- }
78
-
79
- h1 {
80
- padding: 0px 40px;
81
- }
82
-
83
- .content-body {
84
- padding: 10px 30px;
85
- }
86
-
87
- a {
88
- /* box-sizing: border-box; */
89
- font-size: 16px;
90
- font-weight: 600;
91
- color: #3c3c3c;
92
- text-decoration: none;
93
- margin: 0;
94
- margin-bottom: 10px;
95
- }
96
-
97
- a:hover {
98
- text-decoration: underline;
99
- }
100
-
101
- @media only screen and (max-width: 640px) {
102
- body {
103
- padding: 0 !important;
104
- }
105
-
106
- h1 {
107
- font-weight: 800 !important;
108
- margin: 20px 0 5px !important;
109
- text-align: center !important;
110
- }
111
-
112
- h2 {
113
- font-weight: 800 !important;
114
- margin: 20px 0 5px !important;
115
- }
116
-
117
- h3 {
118
- font-weight: 800 !important;
119
- margin: 20px 0 5px !important;
120
- }
121
-
122
- h4 {
123
- font-weight: 800 !important;
124
- margin: 20px 0 5px !important;
125
- }
126
-
127
- h1 {
128
- font-size: 22px !important;
129
- }
130
-
131
- h2 {
132
- font-size: 18px !important;
12
+ img {
13
+ max-width: 100%;
14
+ margin-left: 16px;
15
+ text-align: center !important;
133
16
  }
134
17
 
135
- h3 {
136
- font-size: 16px !important;
18
+ img.CToWUd {
19
+ margin-bottom: 16px;
20
+ max-width: 200px !important;
21
+ width: 200px !important;
22
+ min-width: 200px !important;
23
+ outline: none;
24
+ text-decoration: none;
25
+ border: none;
26
+ height: auto;
27
+ margin-left: 0px;
137
28
  }
138
29
 
139
- .container {
140
- padding: 0 !important;
141
- width: 100% !important;
142
- }
143
-
144
- .content {
145
- padding: 0 !important;
30
+ body {
31
+ -webkit-font-smoothing: antialiased;
32
+ -webkit-text-size-adjust: none;
33
+ width: 100% !important;
34
+ height: 100%;
35
+ line-height: 1.6em;
146
36
  }
147
37
 
148
- .content-wrap {
149
- padding: 10px !important;
38
+ body {
39
+ background-color: #f6f6f6;
150
40
  }
151
41
 
152
- .invoice {
153
- width: 100% !important;
42
+ @media only screen and (max-width: 640px) {
43
+ body {
44
+ padding: 0 !important;
45
+ }
46
+
47
+ h1 {
48
+ font-weight: 800 !important;
49
+ margin: 20px 0 5px !important;
50
+ text-align: center !important;
51
+ }
52
+
53
+ h2 {
54
+ font-weight: 800 !important;
55
+ margin: 20px 0 5px !important;
56
+ }
57
+
58
+ h3 {
59
+ font-weight: 800 !important;
60
+ margin: 20px 0 5px !important;
61
+ }
62
+
63
+ h4 {
64
+ font-weight: 800 !important;
65
+ margin: 20px 0 5px !important;
66
+ }
67
+
68
+ h1 {
69
+ font-size: 22px !important;
70
+ }
71
+
72
+ h2 {
73
+ font-size: 18px !important;
74
+ }
75
+
76
+ h3 {
77
+ font-size: 16px !important;
78
+ }
79
+
80
+ .container {
81
+ padding: 0 !important;
82
+ width: 100% !important;
83
+ }
84
+
85
+ .content {
86
+ padding: 0 !important;
87
+ }
88
+
89
+ .content-wrap {
90
+ padding: 10px !important;
91
+ }
92
+
93
+ .invoice {
94
+ width: 100% !important;
95
+ }
154
96
  }
155
- }
156
97
  </style>
157
- </head>
158
-
159
- <body>
160
- <!-- <div style="background-color: #ff5757; height: 300px"></div>
161
- <div style="background-color: #ffdfe0; height: 80px"></div> -->
162
- <div class="box-container">
163
- <div class="box">
164
- <div class="header-image"></div>
165
-
166
- <h1 style="text-align: center; color: #333">
167
- Update on monthly resources usage
168
- </h1>
169
-
170
- <div class="content-body">
171
- <p>Dear {{firstname}},</p>
172
-
173
- <p>
174
- This is to inform you that you reached the <b>{{checkpoint}}%</b> of your
175
- monthly quote for <b>{{resource_name}}</b> on your project <b>{{project_name}}</b>.
176
- </p>
177
-
178
- <p>Below is a summary of the monthly resource usage:</p>
179
-
180
- <table
181
- style="width: 100%; border-collapse: collapse; margin-bottom: 20px"
182
- >
183
- <thead style="">
184
- <tr>
185
- <th style="border: 1px solid #ddd; padding: 8px">Resource</th>
186
- <th style="border: 1px solid #ddd; padding: 8px">Quota</th>
187
- <th style="border: 1px solid #ddd; padding: 8px">Usage</th>
188
- </tr>
189
- </thead>
190
- <tbody style="text-align: center">
191
- <tr>
192
- <td style="border: 1px solid #ddd; padding: 8px">
193
- Conversations
194
- </td>
195
- <td style="border: 1px solid #ddd; padding: 8px">{{requests_quote}}</td>
196
- <td style="border: 1px solid #ddd; padding: 8px">{{requests_perc}}%</td>
197
- </tr>
198
- <tr>
199
- <td style="border: 1px solid #ddd; padding: 8px">AI Tokens</td>
200
- <td style="border: 1px solid #ddd; padding: 8px">{{tokens_quote}}</td>
201
- <td style="border: 1px solid #ddd; padding: 8px">{{tokens_perc}}%</td>
202
- </tr>
203
- <tr>
204
- <td style="border: 1px solid #ddd; padding: 8px">
205
- Chatbot Email
206
- </td>
207
- <td style="border: 1px solid #ddd; padding: 8px">{{email_quote}}</td>
208
- <td style="border: 1px solid #ddd; padding: 8px">{{email_perc}}%</td>
209
- </tr>
210
- <!-- Add more rows for other resources as needed -->
211
- </tbody>
212
- </table>
213
- </div>
214
-
215
- <p style="text-align: center; font-size: 14px; color: #777">
216
- This is an automated message. Please do not reply.
217
- </p>
218
-
219
- <div style="text-align: center; margin-top: 20px; padding: 20px">
220
- <img
221
- src="https://tiledesk.com/wp-content/uploads/2023/10/tiledesk-logo.png"
222
- alt="Company Footer Logo"
223
- style="max-width: 160px"
224
- />
225
- </div>
226
-
227
- </div>
228
- </div>
229
-
230
- <div class="box-container">
231
- <a href="http://www.tiledesk.com" class="company-url">Tiledesk.com</a>
232
- <a href="%unsubscribe_url%" class="unsubscribe">Unsubscribe</a>
233
- </div>
234
- </body>
235
- </html>
98
+ </head>
99
+
100
+ <body itemscope itemtype="http://schema.org/EmailMessage"
101
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;"
102
+ bgcolor="#f6f6f6">
103
+
104
+ <table class="body-wrap"
105
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;"
106
+ bgcolor="#f6f6f6">
107
+ <tr
108
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
109
+ <td style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;"
110
+ valign="top"></td>
111
+ <td class="container" width="600"
112
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;"
113
+ valign="top">
114
+ <div class="content"
115
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;">
116
+
117
+ <div style="text-align:center">
118
+ <!--
119
+ <a href="http://www.tiledesk.com"
120
+ style="color:#2daae1;font-weight:bold;text-decoration:none;word-break:break-word" target="_blank">
121
+ <img src="https://tiledesk.com/wp-content/uploads/2023/01/tiledesk_log_email_200.png" class="CToWUd">
122
+ </a>
123
+ -->
124
+ </div>
125
+ <table class="main" width="100%" cellpadding="0" cellspacing="0"
126
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;"
127
+ bgcolor="#fff">
128
+
129
+ <!-- <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
130
+
131
+ </tr> -->
132
+
133
+ <tr
134
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
135
+ <td class="content-wrap"
136
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;"
137
+ valign="top">
138
+ <table width="100%" cellpadding="0" cellspacing="0"
139
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
140
+
141
+
142
+ <tr
143
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
144
+
145
+ <td class="content-block"
146
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;"
147
+ valign="top">
148
+ <h2
149
+ style="text-align: center; letter-spacing: 1px; font-weight: 400; line-height:24px ">
150
+ {{currentUserFirstname}} {{currentUserLastname}} has invited you to the
151
+ Tiledesk project
152
+ <strong> {{projectName}}</strong>
153
+ </h2>
154
+
155
+ <!-- <br> <br><strong
156
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">{{to}},</strong> -->
157
+
158
+ <br> <br>
159
+ I invited you to take on the role of {{invitedUserRole}} of the Tiledesk
160
+ <strong>
161
+ {{projectName}}</strong> project
162
+
163
+
164
+ <div style="text-align: center;">
165
+ <br><br>
166
+
167
+ <a href="{{baseScope.baseUrl}}/#/handle-invitation/{{pendinginvitationid}}/{{projectName}}/{{currentUserFirstname}}/{{currentUserLastname}}"
168
+ style=" background-color: #ff8574 !important; border: none; color: white; padding: 12px 30px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; font-weight: 600; letter-spacing: 1px; margin: 4px 2px; cursor: pointer;">
169
+ GO TO THE PROJECT
170
+ </a>
171
+ </div>
172
+
173
+ <br><br> The Tiledesk Team
174
+ </td>
175
+ </tr>
176
+
177
+ <tr
178
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
179
+ <td class="content-block"
180
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;"
181
+ valign="top">
182
+ </td>
183
+ </tr>
184
+ </table>
185
+ </td>
186
+ </tr>
187
+
188
+ <tr>
189
+ <td>
190
+ <hr style="width:94%;height:1px;border:none;background-color: #cacaca;">
191
+
192
+ <div style="display: flex; padding: 20px 18px; color: #888888; align-items: center;">
193
+ <span>Powered by </span>
194
+ <span style="display: flex;"><img
195
+ src="https://tiledesk.com/wp-content/uploads/2023/05/tiledesk-solo_logo_new_gray.png"
196
+ width="15" height="15" style="margin-left: 6px; margin-top: 2px;" /></span>
197
+ <span style="font-weight: bold; margin-left: 2px;">Tiledesk</span>
198
+ </div>
199
+
200
+ </td>
201
+ </tr>
202
+
203
+ </table>
204
+ <div class="footer"
205
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;">
206
+ <table width="100%"
207
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
208
+ <tr
209
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
210
+ <td class="aligncenter content-block"
211
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0;"
212
+ align="center" valign="top">
213
+ <span><a href="http://www.tiledesk.com"
214
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;">
215
+ Tiledesk.com </a></span>
216
+ <br><span><a href="%unsubscribe_url%"
217
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;">Unsubscribe</a></span>
218
+ </td>
219
+ </tr>
220
+ </table>
221
+ </div>
222
+ </div>
223
+ </td>
224
+ <td style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;"
225
+ valign="top"></td>
226
+ </tr>
227
+ </table>
228
+ </body>
229
+
230
+ </html>
@@ -84,6 +84,52 @@ describe('RequestRoute', () => {
84
84
  });
85
85
 
86
86
 
87
+ it('createSimpleAndCloseForDuration', function (done) {
88
+ // this.timeout(10000);
89
+
90
+ var email = "test-request-create-" + Date.now() + "@email.com";
91
+ var pwd = "pwd";
92
+
93
+ userService.signup(email, pwd, "Test Firstname", "Test lastname").then(function (savedUser) {
94
+ projectService.create("request-create", savedUser._id, { email: { template: { assignedRequest: "123" } } }).then(function (savedProject) {
95
+
96
+ chai.request(server)
97
+ .post('/' + savedProject._id + '/requests/')
98
+ .auth(email, pwd)
99
+ .set('content-type', 'application/json')
100
+ .send({ "first_text": "first_text" })
101
+ .end(function (err, res) {
102
+ //console.log("res", res);
103
+ //console.log("res.body", res.body);
104
+ res.should.have.status(200);
105
+ res.body.should.be.a('object');
106
+
107
+ //console.log("res.body: ", res.body)
108
+
109
+ setTimeout(() => {
110
+
111
+ chai.request(server)
112
+ .put('/' + savedProject._id + '/requests/' + res.body.request_id + '/close')
113
+ .auth(email, pwd)
114
+ .send()
115
+ .end((err, res) => {
116
+
117
+ if (err) { console.error("err: ", err) };
118
+ console.log("request duration: ", res.body.duration)
119
+
120
+ res.body.should.have.property('duration');
121
+ res.body.duration.should.be.above(2000);
122
+
123
+ done();
124
+ })
125
+
126
+ }, 2000)
127
+
128
+ });
129
+ });
130
+ });
131
+ }).timeout(5000);
132
+
87
133
 
88
134
 
89
135