@tiledesk/tiledesk-server 2.10.101 → 2.10.103
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 +6 -0
- package/models/kb_setting.js +4 -0
- package/package.json +8 -6
- package/routes/kb.js +68 -19
- package/routes/unanswered.js +1 -1
- package/services/aiService.js +92 -79
- package/services/emailService.js +9 -2
- package/test/emailService.js +43 -0
- package/test/kbRoute.js +129 -18
package/CHANGELOG.md
CHANGED
@@ -5,6 +5,12 @@
|
|
5
5
|
🚀 IN PRODUCTION 🚀
|
6
6
|
(https://www.npmjs.com/package/@tiledesk/tiledesk-server/v/2.3.77)
|
7
7
|
|
8
|
+
# 2.10.103
|
9
|
+
- Update: standard/hybrid namespace management
|
10
|
+
|
11
|
+
# 2.10.102
|
12
|
+
- Update: substituted encode with DOMPurify.sanitize for direct email
|
13
|
+
|
8
14
|
# 2.10.101
|
9
15
|
- Update: messenger-connector to 0.1.27
|
10
16
|
- Update: multi-worker to 0.3.3
|
package/models/kb_setting.js
CHANGED
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.10.
|
4
|
+
"version": "2.10.103",
|
5
5
|
"scripts": {
|
6
6
|
"start": "node ./bin/www",
|
7
7
|
"pretest": "mongodb-runner start",
|
@@ -45,16 +45,15 @@
|
|
45
45
|
"@tiledesk/tiledesk-json-rules-engine": "^4.0.3",
|
46
46
|
"@tiledesk/tiledesk-kaleyra-proxy": "^0.1.7",
|
47
47
|
"@tiledesk/tiledesk-messenger-connector": "^0.1.27",
|
48
|
+
"@tiledesk/tiledesk-multi-worker": "^0.3.3",
|
48
49
|
"@tiledesk/tiledesk-rasa-connector": "^1.0.10",
|
50
|
+
"@tiledesk/tiledesk-sms-connector": "^0.1.11",
|
49
51
|
"@tiledesk/tiledesk-telegram-connector": "^0.1.14",
|
50
52
|
"@tiledesk/tiledesk-tybot-connector": "^2.0.19",
|
53
|
+
"@tiledesk/tiledesk-voice-twilio-connector": "^0.1.22",
|
54
|
+
"@tiledesk/tiledesk-vxml-connector": "^0.1.76",
|
51
55
|
"@tiledesk/tiledesk-whatsapp-connector": "^0.1.84",
|
52
56
|
"@tiledesk/tiledesk-whatsapp-jobworker": "^0.0.12",
|
53
|
-
"@tiledesk/tiledesk-sms-connector": "^0.1.11",
|
54
|
-
"@tiledesk/tiledesk-vxml-connector": "^0.1.76",
|
55
|
-
"@tiledesk/tiledesk-voice-twilio-connector": "^0.1.22",
|
56
|
-
"@tiledesk/tiledesk-multi-worker": "^0.3.3",
|
57
|
-
"passport-oauth2": "^1.8.0",
|
58
57
|
"amqplib": "^0.5.5",
|
59
58
|
"app-root-path": "^3.0.0",
|
60
59
|
"bcrypt-nodejs": "0.0.3",
|
@@ -65,6 +64,7 @@
|
|
65
64
|
"cors": "^2.8.5",
|
66
65
|
"csv-express": "^1.2.2",
|
67
66
|
"debug": "^4.3.4",
|
67
|
+
"dompurify": "^3.2.6",
|
68
68
|
"dotenv": "^8.6.0",
|
69
69
|
"email-templates": "^8.1.0",
|
70
70
|
"eventemitter2": "^6.4.4",
|
@@ -82,6 +82,7 @@
|
|
82
82
|
"immutable": "^4.1.0",
|
83
83
|
"jade": "~1.11.0",
|
84
84
|
"jobs-worker-queued": "^0.0.5",
|
85
|
+
"jsdom": "^26.1.0",
|
85
86
|
"jsonwebtoken": "^8.5.1",
|
86
87
|
"lodash": "^4.17.21",
|
87
88
|
"marked": "^3.0.4",
|
@@ -104,6 +105,7 @@
|
|
104
105
|
"passport-google-oidc": "^0.1.0",
|
105
106
|
"passport-http": "^0.3.0",
|
106
107
|
"passport-jwt": "^4.0.0",
|
108
|
+
"passport-oauth2": "^1.8.0",
|
107
109
|
"pdfmake": "^0.2.5",
|
108
110
|
"promise-events": "^0.2.4",
|
109
111
|
"request": "^2.88.2",
|
package/routes/kb.js
CHANGED
@@ -22,6 +22,7 @@ const { kbTypes } = require('../models/kbConstants');
|
|
22
22
|
|
23
23
|
const AMQP_MANAGER_URL = process.env.AMQP_MANAGER_URL;
|
24
24
|
const JOB_TOPIC_EXCHANGE = process.env.JOB_TOPIC_EXCHANGE_TRAIN || 'tiledesk-trainer';
|
25
|
+
const JOB_TOPIC_EXCHANGE_HYBRID = process.env.JOB_TOPIC_EXCHANGE_TRAIN_HYBRID || 'tiledesk-trainer-hybrid';
|
25
26
|
const KB_WEBHOOK_TOKEN = process.env.KB_WEBHOOK_TOKEN || 'kbcustomtoken';
|
26
27
|
const apiUrl = process.env.API_URL || configGlobal.apiUrl;
|
27
28
|
|
@@ -52,6 +53,21 @@ jobManager.connectAndStartPublisher((status, error) => {
|
|
52
53
|
}
|
53
54
|
})
|
54
55
|
|
56
|
+
let jobManagerHybrid = new JobManager(AMQP_MANAGER_URL, {
|
57
|
+
debug: false,
|
58
|
+
topic: JOB_TOPIC_EXCHANGE_HYBRID,
|
59
|
+
exchange: JOB_TOPIC_EXCHANGE_HYBRID
|
60
|
+
})
|
61
|
+
|
62
|
+
jobManagerHybrid.connectAndStartPublisher((status, error) => {
|
63
|
+
if (error) {
|
64
|
+
winston.error("connectAndStartPublisher error: ", error);
|
65
|
+
} else {
|
66
|
+
winston.info("KbRoute - ConnectPublisher done with status: ", status);
|
67
|
+
}
|
68
|
+
})
|
69
|
+
|
70
|
+
|
55
71
|
let default_preview_settings = {
|
56
72
|
model: 'gpt-4o',
|
57
73
|
max_tokens: 256,
|
@@ -63,11 +79,18 @@ let default_preview_settings = {
|
|
63
79
|
}
|
64
80
|
let default_engine = {
|
65
81
|
name: "pinecone",
|
66
|
-
type: process.env.PINECONE_TYPE,
|
82
|
+
type: process.env.PINECONE_TYPE || "pod",
|
67
83
|
apikey: "",
|
68
84
|
vector_size: 1536,
|
69
85
|
index_name: process.env.PINECONE_INDEX
|
70
86
|
}
|
87
|
+
let default_engine_hybrid = {
|
88
|
+
name: "pinecone",
|
89
|
+
type: process.env.PINECONE_TYPE_HYBRID || "serverless",
|
90
|
+
apikey: "",
|
91
|
+
vector_size: 1536,
|
92
|
+
index_name: process.env.PINECONE_INDEX_HYBRID
|
93
|
+
}
|
71
94
|
|
72
95
|
//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}"
|
73
96
|
//let default_context = "You are an helpful assistant for question-answering tasks.\nUse ONLY the following pieces of retrieved context to answer the question.\nIf you don't know the answer, just say that you don't know.\nIf none of the retrieved context answer the question, add this word to the end <NOANS>\n\n{context}";
|
@@ -149,7 +172,7 @@ router.post('/scrape/single', async (req, res) => {
|
|
149
172
|
let ns = namespaces.find(n => n.id === kb.namespace);
|
150
173
|
json.engine = ns.engine || default_engine;
|
151
174
|
|
152
|
-
if (
|
175
|
+
if (ns.hybrid === true) {
|
153
176
|
json.hybrid = true;
|
154
177
|
}
|
155
178
|
|
@@ -298,6 +321,7 @@ router.post('/qa', async (req, res) => {
|
|
298
321
|
|
299
322
|
let ns = namespaces.find(n => n.id === data.namespace);
|
300
323
|
data.engine = ns.engine || default_engine;
|
324
|
+
data.hybrid = ns.hybrid;
|
301
325
|
|
302
326
|
if (data.engine.type === 'serverless') {
|
303
327
|
data.search_type = 'hybrid';
|
@@ -638,13 +662,32 @@ router.post('/namespace', async (req, res) => {
|
|
638
662
|
let body = req.body;
|
639
663
|
winston.debug("add namespace body: ", body);
|
640
664
|
|
665
|
+
let engine = default_engine;
|
666
|
+
|
667
|
+
let hybrid = false;
|
668
|
+
if ('hybrid' in req.body) {
|
669
|
+
if (typeof req.body.hybrid !== 'boolean') {
|
670
|
+
return res.status(400).send({ success: false, error: "Value not accepted for 'hybrid' field. Expected boolean." });
|
671
|
+
}
|
672
|
+
hybrid = req.body.hybrid;
|
673
|
+
}
|
674
|
+
|
675
|
+
if (hybrid) {
|
676
|
+
if (req.project?.profile?.customization?.hybrid) {
|
677
|
+
engine = default_engine_hybrid;
|
678
|
+
} else {
|
679
|
+
return res.status(403).send({ success: false, error: "Hybrid mode is not allowed for the current project" });
|
680
|
+
}
|
681
|
+
}
|
682
|
+
|
641
683
|
var namespace_id = mongoose.Types.ObjectId();
|
642
684
|
let new_namespace = new Namespace({
|
643
685
|
id_project: project_id,
|
644
686
|
id: namespace_id,
|
645
687
|
name: body.name,
|
688
|
+
hybrid: hybrid,
|
646
689
|
preview_settings: default_preview_settings,
|
647
|
-
engine:
|
690
|
+
engine: engine
|
648
691
|
})
|
649
692
|
|
650
693
|
let namespaces = await Namespace.find({ id_project: project_id }).catch((err) => {
|
@@ -779,6 +822,7 @@ router.post('/namespace/import/:id', upload.single('uploadFile'), async (req, re
|
|
779
822
|
// import operation the content's limit is respected
|
780
823
|
let ns = namespaces.find(n => n.id === namespace_id);
|
781
824
|
let engine = ns.engine || default_engine;
|
825
|
+
let hybrid = ns.hybrid;
|
782
826
|
|
783
827
|
if (process.env.NODE_ENV !== "test") {
|
784
828
|
await aiService.deleteNamespace({
|
@@ -818,7 +862,7 @@ router.post('/namespace/import/:id', upload.single('uploadFile'), async (req, re
|
|
818
862
|
winston.verbose("resources to be sent to worker: ", resources);
|
819
863
|
|
820
864
|
if (process.env.NODE_ENV !== "test") {
|
821
|
-
scheduleScrape(resources);
|
865
|
+
scheduleScrape(resources, hybrid);
|
822
866
|
}
|
823
867
|
|
824
868
|
res.status(200).send({ success: true, message: "Contents imported successfully" });
|
@@ -1217,16 +1261,14 @@ router.post('/', async (req, res) => {
|
|
1217
1261
|
}
|
1218
1262
|
let ns = namespaces.find(n => n.id === body.namespace);
|
1219
1263
|
json.engine = ns.engine || default_engine;
|
1220
|
-
|
1221
|
-
|
1222
|
-
json.hybrid = true;
|
1223
|
-
}
|
1224
|
-
|
1264
|
+
json.hybrid = ns.hybrid;
|
1265
|
+
|
1225
1266
|
let resources = [];
|
1226
1267
|
|
1227
1268
|
resources.push(json);
|
1269
|
+
|
1228
1270
|
if (process.env.NODE_ENV !== 'test') {
|
1229
|
-
scheduleScrape(resources);
|
1271
|
+
scheduleScrape(resources, ns.hybrid);
|
1230
1272
|
}
|
1231
1273
|
|
1232
1274
|
}
|
@@ -1345,11 +1387,7 @@ router.post('/multi', upload.single('uploadFile'), async (req, res) => {
|
|
1345
1387
|
|
1346
1388
|
let ns = namespaces.find(n => n.id === namespace_id);
|
1347
1389
|
let engine = ns.engine || default_engine;
|
1348
|
-
|
1349
|
-
let hybrid;
|
1350
|
-
if (engine.type === 'serverless') {
|
1351
|
-
hybrid = true;
|
1352
|
-
}
|
1390
|
+
let hybrid = ns.hybrid;
|
1353
1391
|
|
1354
1392
|
let resources = result.map(({ name, status, __v, createdAt, updatedAt, id_project, ...keepAttrs }) => keepAttrs)
|
1355
1393
|
resources = resources.map(({ _id, scrape_options, ...rest }) => {
|
@@ -1358,7 +1396,7 @@ router.post('/multi', upload.single('uploadFile'), async (req, res) => {
|
|
1358
1396
|
winston.verbose("resources to be sent to worker: ", resources);
|
1359
1397
|
|
1360
1398
|
if (process.env.NODE_ENV !== 'test') {
|
1361
|
-
scheduleScrape(resources);
|
1399
|
+
scheduleScrape(resources, hybrid);
|
1362
1400
|
}
|
1363
1401
|
res.status(200).send(result);
|
1364
1402
|
|
@@ -1460,6 +1498,7 @@ router.post('/csv', upload.single('uploadFile'), async (req, res) => {
|
|
1460
1498
|
|
1461
1499
|
let ns = namespaces.find(n => n.id === namespace_id);
|
1462
1500
|
let engine = ns.engine || default_engine;
|
1501
|
+
let hybrid = ns.hybrid;
|
1463
1502
|
|
1464
1503
|
let resources = result.map(({ name, status, __v, createdAt, updatedAt, id_project, ...keepAttrs }) => keepAttrs)
|
1465
1504
|
resources = resources.map(({ _id, ...rest}) => {
|
@@ -1467,7 +1506,7 @@ router.post('/csv', upload.single('uploadFile'), async (req, res) => {
|
|
1467
1506
|
})
|
1468
1507
|
winston.verbose("resources to be sent to worker: ", resources);
|
1469
1508
|
if (process.env.NODE_ENV !== 'test') {
|
1470
|
-
scheduleScrape(resources);
|
1509
|
+
scheduleScrape(resources, hybrid);
|
1471
1510
|
}
|
1472
1511
|
res.status(200).send(result);
|
1473
1512
|
}).catch((err) => {
|
@@ -1685,9 +1724,19 @@ async function updateStatus(id, status) {
|
|
1685
1724
|
})
|
1686
1725
|
}
|
1687
1726
|
|
1688
|
-
async function scheduleScrape(resources) {
|
1727
|
+
async function scheduleScrape(resources, hybrid) {
|
1689
1728
|
|
1690
|
-
let scheduler
|
1729
|
+
let scheduler;
|
1730
|
+
if (hybrid) {
|
1731
|
+
scheduler = new Scheduler({ jobManager: jobManagerHybrid });
|
1732
|
+
} else {
|
1733
|
+
scheduler = new Scheduler({ jobManager: jobManager });
|
1734
|
+
}
|
1735
|
+
|
1736
|
+
if (!scheduler) {
|
1737
|
+
winston.error("ScheduleScrape JobManager is not defined");
|
1738
|
+
return false;
|
1739
|
+
}
|
1691
1740
|
|
1692
1741
|
resources.forEach(r => {
|
1693
1742
|
winston.debug("Schedule job with following data: ", r);
|
package/routes/unanswered.js
CHANGED
@@ -244,7 +244,7 @@ async function validateNamespace(id_project, namespace_id) {
|
|
244
244
|
try {
|
245
245
|
const namespace = await Namespace.findOne({
|
246
246
|
id_project: id_project,
|
247
|
-
|
247
|
+
id: namespace_id
|
248
248
|
});
|
249
249
|
return !!namespace; // return true if namespace exists, false otherwise
|
250
250
|
} catch (err) {
|
package/services/aiService.js
CHANGED
@@ -5,9 +5,10 @@ const jwt = require("jsonwebtoken")
|
|
5
5
|
const FormData = require('form-data');
|
6
6
|
|
7
7
|
let openai_endpoint = process.env.OPENAI_ENDPOINT;
|
8
|
-
let kb_endpoint = process.env.KB_ENDPOINT;
|
9
8
|
let kb_endpoint_train = process.env.KB_ENDPOINT_TRAIN;
|
10
9
|
let kb_endpoint_qa = process.env.KB_ENDPOINT_QA;
|
10
|
+
let kb_endpoint_train_gpu = process.env.KB_ENDPOINT_TRAIN_GPU;
|
11
|
+
let kb_endpoint_qa_gpu = process.env.KB_ENDPOINT_QA_GPU;
|
11
12
|
let secret = process.env.JWT_SECRET_KEY;
|
12
13
|
|
13
14
|
class AiService {
|
@@ -85,79 +86,83 @@ class AiService {
|
|
85
86
|
})
|
86
87
|
}
|
87
88
|
|
89
|
+
// KB - Deprecated?
|
90
|
+
// checkStatus(data) {
|
91
|
+
// winston.debug("[OPENAI SERVICE] kb endpoint: " + kb_endpoint);
|
92
|
+
|
93
|
+
// return new Promise((resolve, reject) => {
|
94
|
+
|
95
|
+
// axios({
|
96
|
+
// url: kb_endpoint + "/scrape/status",
|
97
|
+
// headers: {
|
98
|
+
// 'Content-Type': 'application/json'
|
99
|
+
// },
|
100
|
+
// data: data,
|
101
|
+
// method: 'POST'
|
102
|
+
// }).then((resbody) => {
|
103
|
+
// resolve(resbody);
|
104
|
+
// }).catch((err) => {
|
105
|
+
// reject(err);
|
106
|
+
// })
|
107
|
+
|
108
|
+
// })
|
109
|
+
// }
|
110
|
+
|
111
|
+
// Deprecated?
|
112
|
+
// startScrape(data) {
|
113
|
+
// winston.debug("[OPENAI SERVICE] kb endpoint: " + kb_endpoint);
|
114
|
+
|
115
|
+
// return new Promise((resolve, reject) => {
|
116
|
+
|
117
|
+
// axios({
|
118
|
+
// url: kb_endpoint + "/scrape",
|
119
|
+
// headers: {
|
120
|
+
// 'Content-Type': 'application/json'
|
121
|
+
// },
|
122
|
+
// data: data,
|
123
|
+
// method: 'POST'
|
124
|
+
// }).then((resbody) => {
|
125
|
+
// resolve(resbody);
|
126
|
+
// }).catch((err) => {
|
127
|
+
// reject(err);
|
128
|
+
// })
|
129
|
+
|
130
|
+
// })
|
131
|
+
// }
|
132
|
+
|
133
|
+
// Deprecated?
|
134
|
+
// ask(data) {
|
135
|
+
// winston.debug("[OPENAI SERVICE] kb endpoint: " + kb_endpoint);
|
136
|
+
|
137
|
+
// return new Promise((resolve, reject) => {
|
138
|
+
|
139
|
+
// axios({
|
140
|
+
// url: kb_endpoint + "/qa",
|
141
|
+
// headers: {
|
142
|
+
// 'Content-Type': 'application/json'
|
143
|
+
// },
|
144
|
+
// data: data,
|
145
|
+
// method: 'POST'
|
146
|
+
// }).then((resbody) => {
|
147
|
+
// resolve(resbody);
|
148
|
+
// }).catch((err) => {
|
149
|
+
// reject(err);
|
150
|
+
// })
|
151
|
+
|
152
|
+
// })
|
153
|
+
// }
|
88
154
|
|
89
|
-
// KB
|
90
|
-
checkStatus(data) {
|
91
|
-
winston.debug("[OPENAI SERVICE] kb endpoint: " + kb_endpoint);
|
92
|
-
|
93
|
-
return new Promise((resolve, reject) => {
|
94
|
-
|
95
|
-
axios({
|
96
|
-
url: kb_endpoint + "/scrape/status",
|
97
|
-
headers: {
|
98
|
-
'Content-Type': 'application/json'
|
99
|
-
},
|
100
|
-
data: data,
|
101
|
-
method: 'POST'
|
102
|
-
}).then((resbody) => {
|
103
|
-
resolve(resbody);
|
104
|
-
}).catch((err) => {
|
105
|
-
reject(err);
|
106
|
-
})
|
107
|
-
|
108
|
-
})
|
109
|
-
}
|
110
|
-
|
111
|
-
startScrape(data) {
|
112
|
-
winston.debug("[OPENAI SERVICE] kb endpoint: " + kb_endpoint);
|
113
|
-
|
114
|
-
return new Promise((resolve, reject) => {
|
115
|
-
|
116
|
-
axios({
|
117
|
-
url: kb_endpoint + "/scrape",
|
118
|
-
headers: {
|
119
|
-
'Content-Type': 'application/json'
|
120
|
-
},
|
121
|
-
data: data,
|
122
|
-
method: 'POST'
|
123
|
-
}).then((resbody) => {
|
124
|
-
resolve(resbody);
|
125
|
-
}).catch((err) => {
|
126
|
-
reject(err);
|
127
|
-
})
|
128
|
-
|
129
|
-
})
|
130
|
-
}
|
131
|
-
|
132
|
-
ask(data) {
|
133
|
-
winston.debug("[OPENAI SERVICE] kb endpoint: " + kb_endpoint);
|
134
|
-
|
135
|
-
return new Promise((resolve, reject) => {
|
136
|
-
|
137
|
-
axios({
|
138
|
-
url: kb_endpoint + "/qa",
|
139
|
-
headers: {
|
140
|
-
'Content-Type': 'application/json'
|
141
|
-
},
|
142
|
-
data: data,
|
143
|
-
method: 'POST'
|
144
|
-
}).then((resbody) => {
|
145
|
-
resolve(resbody);
|
146
|
-
}).catch((err) => {
|
147
|
-
reject(err);
|
148
|
-
})
|
149
|
-
|
150
|
-
})
|
151
|
-
}
|
152
|
-
|
153
|
-
// PUGLIA AI V2
|
154
155
|
singleScrape(data) {
|
155
|
-
|
156
|
+
let base_url = kb_endpoint_train;
|
157
|
+
if (data.hybrid) {
|
158
|
+
base_url = kb_endpoint_train_gpu;
|
159
|
+
}
|
160
|
+
winston.debug("[OPENAI SERVICE] kb endpoint: " + base_url);
|
156
161
|
|
157
162
|
return new Promise((resolve, reject) => {
|
158
163
|
|
159
164
|
axios({
|
160
|
-
url:
|
165
|
+
url: base_url + "/scrape/single",
|
161
166
|
headers: {
|
162
167
|
'Content-Type': 'application/json'
|
163
168
|
},
|
@@ -173,12 +178,13 @@ class AiService {
|
|
173
178
|
}
|
174
179
|
|
175
180
|
scrapeStatus(data) {
|
176
|
-
|
181
|
+
let base_url = kb_endpoint_train;
|
182
|
+
winston.debug("[OPENAI SERVICE] kb endpoint: " + base_url);
|
177
183
|
|
178
184
|
return new Promise((resolve, reject) => {
|
179
185
|
|
180
186
|
axios({
|
181
|
-
url:
|
187
|
+
url: base_url + "/scrape/status",
|
182
188
|
headers: {
|
183
189
|
'Content-Type': 'application/json'
|
184
190
|
},
|
@@ -193,12 +199,16 @@ class AiService {
|
|
193
199
|
}
|
194
200
|
|
195
201
|
askNamespace(data) {
|
196
|
-
|
202
|
+
let base_url = kb_endpoint_qa;
|
203
|
+
if (data.hybrid) {
|
204
|
+
base_url = kb_endpoint_qa_gpu;
|
205
|
+
}
|
206
|
+
winston.debug("[OPENAI SERVICE] kb endpoint: " + base_url);
|
197
207
|
|
198
208
|
return new Promise((resolve, reject) => {
|
199
209
|
|
200
210
|
axios({
|
201
|
-
url:
|
211
|
+
url: base_url + "/qa",
|
202
212
|
headers: {
|
203
213
|
'Content-Type': 'application/json'
|
204
214
|
},
|
@@ -213,15 +223,16 @@ class AiService {
|
|
213
223
|
})
|
214
224
|
}
|
215
225
|
|
216
|
-
getContentChunks(namespace_id, content_id, engine) {
|
217
|
-
|
226
|
+
getContentChunks(namespace_id, content_id, engine, hybrid) {
|
227
|
+
let base_url = kb_endpoint_train;
|
228
|
+
winston.debug("[OPENAI SERVICE] kb endpoint: " + base_url);
|
218
229
|
|
219
230
|
return new Promise((resolve, reject) => {
|
220
231
|
|
221
232
|
let payload = { engine: engine };
|
222
233
|
let token = jwt.sign(payload, secret);
|
223
234
|
axios({
|
224
|
-
url:
|
235
|
+
url: base_url + "/id/" + content_id + "/namespace/" + namespace_id + "/" + token,
|
225
236
|
headers: {
|
226
237
|
'Content-Type': 'application/json'
|
227
238
|
},
|
@@ -235,12 +246,13 @@ class AiService {
|
|
235
246
|
}
|
236
247
|
|
237
248
|
deleteIndex(data) {
|
238
|
-
|
249
|
+
let base_url = kb_endpoint_train;
|
250
|
+
winston.debug("[OPENAI SERVICE] kb endpoint: " + base_url);
|
239
251
|
|
240
252
|
return new Promise((resolve, reject) => {
|
241
253
|
|
242
254
|
axios({
|
243
|
-
url:
|
255
|
+
url: base_url + "/delete/id",
|
244
256
|
headers: {
|
245
257
|
'Content-Type': 'application/json'
|
246
258
|
},
|
@@ -255,12 +267,13 @@ class AiService {
|
|
255
267
|
}
|
256
268
|
|
257
269
|
deleteNamespace(data) {
|
258
|
-
|
270
|
+
let base_url = kb_endpoint_train;
|
271
|
+
winston.debug("[OPENAI SERVICE] kb endpoint: " + base_url);
|
259
272
|
|
260
273
|
return new Promise((resolve, reject) => {
|
261
274
|
|
262
275
|
axios({
|
263
|
-
url:
|
276
|
+
url: base_url + "/delete/namespace",
|
264
277
|
headers: {
|
265
278
|
'Content-Type': 'application/json'
|
266
279
|
},
|
package/services/emailService.js
CHANGED
@@ -9,6 +9,11 @@ var handlebars = require('handlebars');
|
|
9
9
|
var encode = require('html-entities').encode;
|
10
10
|
const emailEvent = require('../event/emailEvent');
|
11
11
|
|
12
|
+
const createDOMPurify = require('dompurify');
|
13
|
+
const { JSDOM } = require('jsdom');
|
14
|
+
const window = new JSDOM('').window;
|
15
|
+
const DOMPurify = createDOMPurify(window);
|
16
|
+
|
12
17
|
handlebars.registerHelper('ifEquals', function (arg1, arg2, options) {
|
13
18
|
return (arg1 == arg2) ? options.fn(this) : options.inverse(this);
|
14
19
|
});
|
@@ -1476,13 +1481,15 @@ class EmailService {
|
|
1476
1481
|
var baseScope = JSON.parse(JSON.stringify(that));
|
1477
1482
|
delete baseScope.pass;
|
1478
1483
|
|
1479
|
-
|
1480
1484
|
let msgText = text;
|
1481
|
-
msgText = encode(msgText);
|
1482
1485
|
if (this.markdown) {
|
1483
1486
|
msgText = marked(msgText);
|
1487
|
+
msgText = DOMPurify.sanitize(msgText);
|
1488
|
+
} else {
|
1489
|
+
msgText = encode(msgText);
|
1484
1490
|
}
|
1485
1491
|
|
1492
|
+
|
1486
1493
|
winston.debug("msgText: " + msgText);
|
1487
1494
|
winston.debug("baseScope: " + JSON.stringify(baseScope));
|
1488
1495
|
|
@@ -0,0 +1,43 @@
|
|
1
|
+
//During the test the env variable is set to test
|
2
|
+
process.env.NODE_ENV = 'test';
|
3
|
+
|
4
|
+
var expect = require('chai').expect;
|
5
|
+
|
6
|
+
var assert = require('chai').assert;
|
7
|
+
var config = require('../config/database');
|
8
|
+
var mongoose = require('mongoose');
|
9
|
+
var winston = require('../config/winston');
|
10
|
+
|
11
|
+
mongoose.connect(config.databasetest);
|
12
|
+
|
13
|
+
var projectService = require("../services/projectService");
|
14
|
+
const emailService = require('../services/emailService');
|
15
|
+
|
16
|
+
let log = false;
|
17
|
+
|
18
|
+
describe('EmailService', function () {
|
19
|
+
|
20
|
+
|
21
|
+
it('direct', function (done) {
|
22
|
+
var userid = "5badfe5d553d1844ad654072";
|
23
|
+
projectService.create("test1", userid).then(function (savedProject) {
|
24
|
+
// create(fullname, email, id_project, createdBy)
|
25
|
+
|
26
|
+
let request_id = "support-group-" + savedProject._id + "-123456";
|
27
|
+
|
28
|
+
//let text = "this is\n<b>the</b>\ntext";
|
29
|
+
//let text = 'this is <script>alert("XSS")</script>';
|
30
|
+
let text = 'Go to [Google](https://google.com)';
|
31
|
+
emailService.sendEmailDirect("my@email.com", text, savedProject._id, request_id, "Suubject").then((response) => {
|
32
|
+
console.log("response: ", response);
|
33
|
+
done();
|
34
|
+
}).catch((err) => {
|
35
|
+
console.error("err: ", err)
|
36
|
+
done();
|
37
|
+
})
|
38
|
+
|
39
|
+
|
40
|
+
});
|
41
|
+
});
|
42
|
+
|
43
|
+
});
|
package/test/kbRoute.js
CHANGED
@@ -3,13 +3,11 @@ process.env.NODE_ENV = 'test';
|
|
3
3
|
process.env.GPTKEY = "fakegptkey";
|
4
4
|
process.env.LOG_LEVEL = 'critical'
|
5
5
|
process.env.KB_WEBHOOK_TOKEN = "testtoken"
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
process.env.
|
11
|
-
process.env.PINECONE_TYPE = "serverless"
|
12
|
-
|
6
|
+
process.env.PINECONE_INDEX = "test_index";
|
7
|
+
process.env.PINECONE_TYPE = "pod";
|
8
|
+
process.env.PINECONE_INDEX_HYBRID = "test_index_hybrid";
|
9
|
+
process.env.PINECONE_TYPE_HYBRID = "serverless";
|
10
|
+
process.env.ADMIN_EMAIL = "admin@tiledesk.com";
|
13
11
|
|
14
12
|
var userService = require('../services/userService');
|
15
13
|
var projectService = require('../services/projectService');
|
@@ -36,6 +34,14 @@ const faq = require('../models/faq');
|
|
36
34
|
var expect = chai.expect;
|
37
35
|
var assert = chai.assert;
|
38
36
|
|
37
|
+
let custom_profile_sample = {
|
38
|
+
name: "Custom",
|
39
|
+
type: "payment",
|
40
|
+
subStart: new Date(),
|
41
|
+
subEnd: new Date(new Date().setFullYear(new Date().getFullYear() + 1)),
|
42
|
+
customization: { hybrid: true }
|
43
|
+
}
|
44
|
+
|
39
45
|
mongoose.connect(config.databasetest);
|
40
46
|
|
41
47
|
chai.use(chaiHttp);
|
@@ -63,7 +69,7 @@ describe('KbRoute', () => {
|
|
63
69
|
|
64
70
|
res.should.have.status(200);
|
65
71
|
expect(res.body.length).to.equal(1);
|
66
|
-
expect(res.body[0].engine.index_name).to.equal('
|
72
|
+
expect(res.body[0].engine.index_name).to.equal('test_index')
|
67
73
|
|
68
74
|
let namespace_id = res.body[0].id;
|
69
75
|
|
@@ -1119,7 +1125,7 @@ describe('KbRoute', () => {
|
|
1119
1125
|
// });
|
1120
1126
|
// })
|
1121
1127
|
|
1122
|
-
it('create-namespaces-with-engine', (done) => {
|
1128
|
+
it('create-namespaces-with-engine-similarity', (done) => {
|
1123
1129
|
|
1124
1130
|
var email = "test-signup-" + Date.now() + "@email.com";
|
1125
1131
|
var pwd = "pwd";
|
@@ -1143,7 +1149,7 @@ describe('KbRoute', () => {
|
|
1143
1149
|
expect(res.body.name).to.equal('MyCustomNamespace');
|
1144
1150
|
should.exist(res.body.engine)
|
1145
1151
|
expect(res.body.engine.name).to.equal('pinecone');
|
1146
|
-
expect(res.body.engine.type).to.equal('
|
1152
|
+
expect(res.body.engine.type).to.equal('pod');
|
1147
1153
|
|
1148
1154
|
// Get again all namespace. A new default namespace should not be created.
|
1149
1155
|
chai.request(server)
|
@@ -1167,6 +1173,99 @@ describe('KbRoute', () => {
|
|
1167
1173
|
});
|
1168
1174
|
})
|
1169
1175
|
|
1176
|
+
it('create-namespaces-with-engine-hybrid-rejected', (done) => {
|
1177
|
+
|
1178
|
+
var email = "test-signup-" + Date.now() + "@email.com";
|
1179
|
+
var pwd = "pwd";
|
1180
|
+
|
1181
|
+
userService.signup(email, pwd, "Test Firstname", "Test lastname").then(function (savedUser) {
|
1182
|
+
projectService.create("test-faqkb-create", savedUser._id).then(function (savedProject) {
|
1183
|
+
|
1184
|
+
chai.request(server)
|
1185
|
+
.post('/' + savedProject._id + '/kb/namespace')
|
1186
|
+
.auth(email, pwd)
|
1187
|
+
.send({ name: "MyCustomNamespace", hybrid: true })
|
1188
|
+
.end((err, res) => {
|
1189
|
+
|
1190
|
+
if (err) { console.error("err: ", err) }
|
1191
|
+
if (log) { console.log("create new namespace res.body: ", res.body) }
|
1192
|
+
|
1193
|
+
res.should.have.status(403);
|
1194
|
+
res.body.should.be.a('object');
|
1195
|
+
expect(res.body.success).to.equal(false);
|
1196
|
+
expect(res.body.error).to.equal('Hybrid mode is not allowed for the current project');
|
1197
|
+
|
1198
|
+
done();
|
1199
|
+
})
|
1200
|
+
});
|
1201
|
+
});
|
1202
|
+
})
|
1203
|
+
|
1204
|
+
it('create-namespaces-with-engine-hybrid-accepted', (done) => {
|
1205
|
+
|
1206
|
+
var email = "test-signup-" + Date.now() + "@email.com";
|
1207
|
+
var pwd = "pwd";
|
1208
|
+
|
1209
|
+
userService.signup(email, pwd, "Test Firstname", "Test lastname").then(function (savedUser) {
|
1210
|
+
projectService.create("test-faqkb-create", savedUser._id).then(function (savedProject) {
|
1211
|
+
|
1212
|
+
chai.request(server)
|
1213
|
+
.post('/auth/signin')
|
1214
|
+
.send({ email: "admin@tiledesk.com", password: "adminadmin" })
|
1215
|
+
.end((err, res) => {
|
1216
|
+
|
1217
|
+
if (err) { console.error("err: ", err) }
|
1218
|
+
if (log) { console.log("login with superadmin res.body: ", res.body) };
|
1219
|
+
|
1220
|
+
res.should.have.status(200);
|
1221
|
+
res.body.should.be.a('object');
|
1222
|
+
expect(res.body.success).to.equal(true);
|
1223
|
+
expect(res.body.token).not.equal(null);
|
1224
|
+
|
1225
|
+
let superadmin_token = res.body.token;
|
1226
|
+
|
1227
|
+
|
1228
|
+
chai.request(server)
|
1229
|
+
.put('/projects/' + savedProject._id)
|
1230
|
+
.set('Authorization', superadmin_token)
|
1231
|
+
.send({ profile: custom_profile_sample })
|
1232
|
+
.end((err, res) => {
|
1233
|
+
|
1234
|
+
if (err) { console.error("err: ", err) }
|
1235
|
+
if (log) { console.log("update project res.body: ", res.body) }
|
1236
|
+
|
1237
|
+
res.should.have.status(200);
|
1238
|
+
res.body.should.be.a('object');
|
1239
|
+
expect(res.body.profile.customization.hybrid).to.equal(true);
|
1240
|
+
|
1241
|
+
chai.request(server)
|
1242
|
+
.post('/' + savedProject._id + '/kb/namespace')
|
1243
|
+
.auth(email, pwd)
|
1244
|
+
.send({ name: "MyCustomNamespace", hybrid: true })
|
1245
|
+
.end((err, res) => {
|
1246
|
+
|
1247
|
+
if (err) { console.error("err: ", err) }
|
1248
|
+
if (log) { console.log("create new namespace res.body: ", res.body) }
|
1249
|
+
|
1250
|
+
res.should.have.status(200);
|
1251
|
+
res.body.should.be.a('object');
|
1252
|
+
should.not.exist(res.body._id)
|
1253
|
+
should.exist(res.body.id)
|
1254
|
+
expect(res.body.name).to.equal('MyCustomNamespace');
|
1255
|
+
should.exist(res.body.engine)
|
1256
|
+
expect(res.body.engine.name).to.equal('pinecone');
|
1257
|
+
expect(res.body.engine.type).to.equal('serverless');
|
1258
|
+
|
1259
|
+
done();
|
1260
|
+
})
|
1261
|
+
})
|
1262
|
+
|
1263
|
+
})
|
1264
|
+
|
1265
|
+
})
|
1266
|
+
});
|
1267
|
+
})
|
1268
|
+
|
1170
1269
|
it('import-namespace', (done) => {
|
1171
1270
|
|
1172
1271
|
var email = "test-signup-" + Date.now() + "@email.com";
|
@@ -1488,23 +1587,28 @@ describe('KbRoute', () => {
|
|
1488
1587
|
})
|
1489
1588
|
|
1490
1589
|
describe('Unanswered Questions', () => {
|
1590
|
+
|
1491
1591
|
it('add-unanswered-question', (done) => {
|
1492
1592
|
var email = "test-signup-" + Date.now() + "@email.com";
|
1493
1593
|
var pwd = "pwd";
|
1494
1594
|
|
1495
1595
|
userService.signup(email, pwd, "Test Firstname", "Test lastname").then(function (savedUser) {
|
1496
1596
|
projectService.create("test-unanswered-create", savedUser._id).then(function (savedProject) {
|
1597
|
+
|
1497
1598
|
chai.request(server)
|
1498
1599
|
.get('/' + savedProject._id + '/kb/namespace/all')
|
1499
1600
|
.auth(email, pwd)
|
1500
1601
|
.end((err, res) => {
|
1602
|
+
|
1501
1603
|
if (err) { console.error("err: ", err); }
|
1604
|
+
if (log) { console.log("get namespaces res.body: ", res.body); }
|
1605
|
+
|
1502
1606
|
res.should.have.status(200);
|
1503
1607
|
expect(res.body.length).to.equal(1);
|
1504
1608
|
|
1505
1609
|
let namespace_id = res.body[0].id;
|
1506
1610
|
|
1507
|
-
let
|
1611
|
+
let data = {
|
1508
1612
|
namespace: namespace_id,
|
1509
1613
|
question: "Come funziona il prodotto?"
|
1510
1614
|
}
|
@@ -1512,9 +1616,12 @@ describe('KbRoute', () => {
|
|
1512
1616
|
chai.request(server)
|
1513
1617
|
.post('/' + savedProject._id + '/kb/unanswered')
|
1514
1618
|
.auth(email, pwd)
|
1515
|
-
.send(
|
1619
|
+
.send(data)
|
1516
1620
|
.end((err, res) => {
|
1621
|
+
|
1517
1622
|
if (err) { console.error("err: ", err); }
|
1623
|
+
if (log) { console.log("create unanswered question res.body: ", res.body); }
|
1624
|
+
|
1518
1625
|
res.should.have.status(200);
|
1519
1626
|
res.body.should.be.a('object');
|
1520
1627
|
expect(res.body.namespace).to.equal(namespace_id);
|
@@ -1538,13 +1645,15 @@ describe('KbRoute', () => {
|
|
1538
1645
|
.auth(email, pwd)
|
1539
1646
|
.end((err, res) => {
|
1540
1647
|
if (err) { console.error("err: ", err); }
|
1648
|
+
if (log) { console.log("get namespaces res.body: ", res.body); }
|
1649
|
+
|
1541
1650
|
res.should.have.status(200);
|
1542
1651
|
expect(res.body.length).to.equal(1);
|
1543
1652
|
|
1544
1653
|
let namespace_id = res.body[0].id;
|
1545
1654
|
|
1546
1655
|
// First add a question
|
1547
|
-
let
|
1656
|
+
let data = {
|
1548
1657
|
namespace: namespace_id,
|
1549
1658
|
question: "Come funziona il prodotto?"
|
1550
1659
|
}
|
@@ -1552,14 +1661,16 @@ describe('KbRoute', () => {
|
|
1552
1661
|
chai.request(server)
|
1553
1662
|
.post('/' + savedProject._id + '/kb/unanswered')
|
1554
1663
|
.auth(email, pwd)
|
1555
|
-
.send(
|
1664
|
+
.send(data)
|
1556
1665
|
.end((err, res) => {
|
1557
1666
|
if (err) { console.error("err: ", err); }
|
1667
|
+
if (log) { console.log("add unanswered question res.body: ", res.body); }
|
1668
|
+
|
1558
1669
|
res.should.have.status(200);
|
1559
1670
|
|
1560
1671
|
// Then get all questions
|
1561
1672
|
chai.request(server)
|
1562
|
-
.get('/' + savedProject._id + '/kb/unanswered
|
1673
|
+
.get('/' + savedProject._id + '/kb/unanswered/' + namespace_id)
|
1563
1674
|
.auth(email, pwd)
|
1564
1675
|
.end((err, res) => {
|
1565
1676
|
if (err) { console.error("err: ", err); }
|
@@ -1623,7 +1734,7 @@ describe('KbRoute', () => {
|
|
1623
1734
|
|
1624
1735
|
// Verify it's deleted
|
1625
1736
|
chai.request(server)
|
1626
|
-
.get('/' + savedProject._id + '/kb/unanswered
|
1737
|
+
.get('/' + savedProject._id + '/kb/unanswered/' + namespace_id)
|
1627
1738
|
.auth(email, pwd)
|
1628
1739
|
.end((err, res) => {
|
1629
1740
|
if (err) { console.error("err: ", err); }
|
@@ -1687,7 +1798,7 @@ describe('KbRoute', () => {
|
|
1687
1798
|
|
1688
1799
|
// Verify they're deleted
|
1689
1800
|
chai.request(server)
|
1690
|
-
.get('/' + savedProject._id + '/kb/unanswered
|
1801
|
+
.get('/' + savedProject._id + '/kb/unanswered/' + namespace_id)
|
1691
1802
|
.auth(email, pwd)
|
1692
1803
|
.end((err, res) => {
|
1693
1804
|
if (err) { console.error("err: ", err); }
|
@@ -1789,7 +1900,7 @@ describe('KbRoute', () => {
|
|
1789
1900
|
)).then(() => {
|
1790
1901
|
// Then count them
|
1791
1902
|
chai.request(server)
|
1792
|
-
.get('/' + savedProject._id + '/kb/unanswered/count
|
1903
|
+
.get('/' + savedProject._id + '/kb/unanswered/count/' + namespace_id)
|
1793
1904
|
.auth(email, pwd)
|
1794
1905
|
.end((err, res) => {
|
1795
1906
|
if (err) { console.error("err: ", err); }
|