@tiledesk/tiledesk-server 2.15.6 โ 2.15.8
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 +7 -0
- package/models/kb_setting.js +5 -0
- package/package.json +3 -3
- package/routes/kb.js +118 -103
- package/services/aiManager.js +4 -3
- package/test/fixtures/example-kb-faqs.csv +2 -2
- package/test/fixtures/exported_namespace.json +2 -1
- package/test/kbRoute.js +125 -12
- package/utils/arrayUtil.js +35 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,13 @@
|
|
|
5
5
|
๐ IN PRODUCTION ๐
|
|
6
6
|
(https://www.npmjs.com/package/@tiledesk/tiledesk-server/v/2.3.77)
|
|
7
7
|
|
|
8
|
+
# 2.15.8
|
|
9
|
+
- Updated tybot-connector to 2.0.45
|
|
10
|
+
- Added support for tags management in knowledge base routes
|
|
11
|
+
|
|
12
|
+
# 2.15.7
|
|
13
|
+
- Updated whatsapp-connector to 1.0.26
|
|
14
|
+
|
|
8
15
|
# 2.15.6
|
|
9
16
|
- Updated voice-twilio-connector to 0.3.2
|
|
10
17
|
- Updated vxml-connector to 0.1.91
|
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.15.
|
|
4
|
+
"version": "2.15.8",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"start": "node ./bin/www",
|
|
7
7
|
"pretest": "mongodb-runner start",
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
"@tiledesk/tiledesk-rasa-connector": "^1.0.10",
|
|
50
50
|
"@tiledesk/tiledesk-sms-connector": "^0.1.13",
|
|
51
51
|
"@tiledesk/tiledesk-telegram-connector": "^0.1.14",
|
|
52
|
-
"@tiledesk/tiledesk-tybot-connector": "^2.0.
|
|
52
|
+
"@tiledesk/tiledesk-tybot-connector": "^2.0.45",
|
|
53
53
|
"@tiledesk/tiledesk-voice-twilio-connector": "^0.3.2",
|
|
54
54
|
"@tiledesk/tiledesk-vxml-connector": "^0.1.91",
|
|
55
|
-
"@tiledesk/tiledesk-whatsapp-connector": "1.0.
|
|
55
|
+
"@tiledesk/tiledesk-whatsapp-connector": "1.0.26",
|
|
56
56
|
"@tiledesk/tiledesk-whatsapp-jobworker": "^0.0.13",
|
|
57
57
|
"amqplib": "^0.5.5",
|
|
58
58
|
"app-root-path": "^3.0.0",
|
package/routes/kb.js
CHANGED
|
@@ -13,6 +13,7 @@ const faq = require('../models/faq');
|
|
|
13
13
|
const faq_kb = require('../models/faq_kb');
|
|
14
14
|
|
|
15
15
|
const { MODELS_MULTIPLIER } = require('../utils/aiUtils');
|
|
16
|
+
const { parseStringArrayField } = require('../utils/arrayUtil');
|
|
16
17
|
const { kbTypes } = require('../models/kbConstants');
|
|
17
18
|
const Sitemapper = require('sitemapper');
|
|
18
19
|
|
|
@@ -80,6 +81,13 @@ const default_engine = require('../config/kb/engine');
|
|
|
80
81
|
const default_engine_hybrid = require('../config/kb/engine.hybrid');
|
|
81
82
|
const default_embedding = require('../config/kb/embedding');
|
|
82
83
|
|
|
84
|
+
function normalizeEmbedding(embedding) {
|
|
85
|
+
const normalizedEmbedding = (embedding && typeof embedding.toObject === 'function')
|
|
86
|
+
? embedding.toObject()
|
|
87
|
+
: (embedding || default_embedding);
|
|
88
|
+
return { ...normalizedEmbedding };
|
|
89
|
+
}
|
|
90
|
+
|
|
83
91
|
let contexts = {
|
|
84
92
|
"gpt-3.5-turbo": process.env.GPT_3_5_CONTEXT || "You are an helpful assistant for question-answering tasks.\nUse ONLY the pieces of retrieved context delimited by #### and the chat history to answer the question.\nIf you don't know the answer, just say: \"I don't know<NOANS>\"\n\n####{context}####",
|
|
85
93
|
"gpt-4": process.env.GPT_4_CONTEXT || "You are an helpful assistant for question-answering tasks.\nUse ONLY the pieces of retrieved context delimited by #### and the chat history to answer the question.\nIf you don't know the answer, just say that you don't know.\nIf and only if none of the retrieved context is useful for your task, add this word to the end <NOANS>\n\n####{context}####",
|
|
@@ -170,7 +178,8 @@ router.post('/scrape/single', async (req, res) => {
|
|
|
170
178
|
sitemap_origin: sitemapKb.source,
|
|
171
179
|
scrape_type: sitemapKb.scrape_type,
|
|
172
180
|
scrape_options: sitemapKb.scrape_options,
|
|
173
|
-
refresh_rate: sitemapKb.refresh_rate
|
|
181
|
+
refresh_rate: sitemapKb.refresh_rate,
|
|
182
|
+
tags: sitemapKb.tags
|
|
174
183
|
}
|
|
175
184
|
aiManager.addMultipleUrls(namespace, addedUrls, options).catch((err) => {
|
|
176
185
|
winston.error("(webhook) error adding multiple urls contents: ", err);
|
|
@@ -215,8 +224,12 @@ router.post('/scrape/single', async (req, res) => {
|
|
|
215
224
|
}
|
|
216
225
|
}
|
|
217
226
|
|
|
227
|
+
if (kb.tags) {
|
|
228
|
+
json.tags = kb.tags;
|
|
229
|
+
}
|
|
230
|
+
|
|
218
231
|
json.engine = namespace.engine || default_engine;
|
|
219
|
-
json.embedding = namespace.embedding
|
|
232
|
+
json.embedding = normalizeEmbedding(namespace.embedding);
|
|
220
233
|
json.embedding.api_key = process.env.EMBEDDING_API_KEY || process.env.GPTKEY;
|
|
221
234
|
|
|
222
235
|
if (namespace.hybrid === true) {
|
|
@@ -357,7 +370,7 @@ router.post('/qa', async (req, res) => {
|
|
|
357
370
|
}
|
|
358
371
|
|
|
359
372
|
data.engine = namespace.engine || default_engine;
|
|
360
|
-
data.embedding = namespace.embedding
|
|
373
|
+
data.embedding = normalizeEmbedding(namespace.embedding);
|
|
361
374
|
data.embedding.api_key = process.env.EMBEDDING_API_KEY || process.env.GPTKEY;
|
|
362
375
|
|
|
363
376
|
if (namespace.hybrid === true) {
|
|
@@ -989,7 +1002,8 @@ router.post('/namespace/import/:id', upload.single('uploadFile'), async (req, re
|
|
|
989
1002
|
type: e.type,
|
|
990
1003
|
content: e.content,
|
|
991
1004
|
namespace: namespace_id,
|
|
992
|
-
status: -1
|
|
1005
|
+
status: -1,
|
|
1006
|
+
...(e.tags && { tags: e.tags })
|
|
993
1007
|
}
|
|
994
1008
|
|
|
995
1009
|
const optionalFields = ['scrape_type', 'scrape_options', 'refresh_rate'];
|
|
@@ -1022,7 +1036,7 @@ router.post('/namespace/import/:id', upload.single('uploadFile'), async (req, re
|
|
|
1022
1036
|
// import operation the content's limit is respected
|
|
1023
1037
|
let ns = namespaces.find(n => n.id === namespace_id);
|
|
1024
1038
|
let engine = ns.engine || default_engine;
|
|
1025
|
-
let embedding = ns.embedding
|
|
1039
|
+
let embedding = normalizeEmbedding(ns.embedding);
|
|
1026
1040
|
embedding.api_key = process.env.EMBEDDING_API_KEY || process.env.GPTKEY;
|
|
1027
1041
|
let hybrid = ns.hybrid;
|
|
1028
1042
|
|
|
@@ -1044,10 +1058,13 @@ router.post('/namespace/import/:id', upload.single('uploadFile'), async (req, re
|
|
|
1044
1058
|
|
|
1045
1059
|
winston.verbose("Content deletetion response: ", deleteResponse);
|
|
1046
1060
|
|
|
1047
|
-
|
|
1061
|
+
try {
|
|
1062
|
+
let addedContents = await KB.insertMany(addingContents);
|
|
1063
|
+
winston.debug("addedContents: ", addedContents);
|
|
1064
|
+
} catch (err) {
|
|
1048
1065
|
winston.error("Error adding contents with insertMany: ", err);
|
|
1049
1066
|
return res.status(500).send({ success: true, error: "Error importing contents" });
|
|
1050
|
-
}
|
|
1067
|
+
}
|
|
1051
1068
|
|
|
1052
1069
|
let new_contents;
|
|
1053
1070
|
try {
|
|
@@ -1362,59 +1379,63 @@ router.get('/:kb_id', async (req, res) => {
|
|
|
1362
1379
|
|
|
1363
1380
|
router.post('/', async (req, res) => {
|
|
1364
1381
|
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
let namespace_id = body.namespace;
|
|
1382
|
+
const id_project = req.projectid;
|
|
1383
|
+
const project = req.project
|
|
1368
1384
|
|
|
1369
|
-
|
|
1385
|
+
const { name, type, source, content, refresh_rate, scrape_type, scrape_options, tags } = req.body;
|
|
1386
|
+
const namespace_id = req.body?.namespace;
|
|
1387
|
+
|
|
1388
|
+
if (!namespace_id) {
|
|
1370
1389
|
return res.status(400).send({ success: false, error: "parameter 'namespace' is not defined" });
|
|
1371
1390
|
}
|
|
1372
1391
|
|
|
1373
1392
|
let namespace;
|
|
1374
1393
|
try {
|
|
1375
|
-
namespace = await aiManager.checkNamespace(
|
|
1394
|
+
namespace = await aiManager.checkNamespace(id_project, namespace_id);
|
|
1376
1395
|
} catch (err) {
|
|
1377
1396
|
let errorCode = err?.errorCode ?? 500;
|
|
1378
1397
|
return res.status(errorCode).send({ success: false, error: err.error });
|
|
1379
1398
|
}
|
|
1380
1399
|
|
|
1381
|
-
|
|
1400
|
+
const quoteManager = req.app.get('quote_manager');
|
|
1382
1401
|
try {
|
|
1383
|
-
await aiManager.checkQuotaAvailability(quoteManager,
|
|
1402
|
+
await aiManager.checkQuotaAvailability(quoteManager, project, 1)
|
|
1384
1403
|
} catch(err) {
|
|
1385
1404
|
let errorCode = err?.errorCode ?? 500;
|
|
1386
1405
|
return res.status(errorCode).send({ success: false, error: err.error, plan_limit: err.plan_limit })
|
|
1387
1406
|
}
|
|
1388
1407
|
|
|
1389
1408
|
let new_kb = {
|
|
1390
|
-
id_project
|
|
1391
|
-
name
|
|
1392
|
-
type
|
|
1393
|
-
source
|
|
1394
|
-
content
|
|
1395
|
-
namespace:
|
|
1409
|
+
id_project,
|
|
1410
|
+
name,
|
|
1411
|
+
type,
|
|
1412
|
+
source,
|
|
1413
|
+
content,
|
|
1414
|
+
namespace: namespace_id,
|
|
1396
1415
|
status: -1
|
|
1397
1416
|
}
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
}
|
|
1401
|
-
if (new_kb.type === 'txt') {
|
|
1417
|
+
|
|
1418
|
+
if (type === 'txt') {
|
|
1402
1419
|
new_kb.scrape_type = 1;
|
|
1403
1420
|
}
|
|
1404
|
-
if (
|
|
1405
|
-
new_kb.refresh_rate =
|
|
1406
|
-
if (!
|
|
1421
|
+
if (type === 'url') {
|
|
1422
|
+
new_kb.refresh_rate = refresh_rate || 'never';
|
|
1423
|
+
if (!scrape_type || scrape_type === 2) {
|
|
1407
1424
|
new_kb.scrape_type = 2;
|
|
1408
1425
|
new_kb.scrape_options = aiManager.setDefaultScrapeOptions();
|
|
1409
1426
|
} else {
|
|
1410
|
-
new_kb.scrape_type =
|
|
1411
|
-
new_kb.scrape_options =
|
|
1427
|
+
new_kb.scrape_type = scrape_type;
|
|
1428
|
+
new_kb.scrape_options = scrape_options;
|
|
1412
1429
|
}
|
|
1413
1430
|
}
|
|
1414
1431
|
|
|
1432
|
+
if (tags && Array.isArray(tags) && tags.every(tag => typeof tag === "string")) {
|
|
1433
|
+
new_kb.tags = tags;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1415
1436
|
winston.debug("adding kb: ", new_kb);
|
|
1416
1437
|
|
|
1417
|
-
KB.findOneAndUpdate({ id_project
|
|
1438
|
+
KB.findOneAndUpdate({ id_project, type, source }, new_kb, { upsert: true, new: true, rawResult: true }, async (err, raw_content) => {
|
|
1418
1439
|
if (err) {
|
|
1419
1440
|
winston.error("findOneAndUpdate with upsert error: ", err);
|
|
1420
1441
|
res.status(500).send({ success: false, error: err });
|
|
@@ -1425,44 +1446,33 @@ router.post('/', async (req, res) => {
|
|
|
1425
1446
|
delete raw_content.$clusterTime;
|
|
1426
1447
|
delete raw_content.operationTime;
|
|
1427
1448
|
|
|
1428
|
-
|
|
1429
|
-
|
|
1449
|
+
const saved_kb = raw_content.value;
|
|
1450
|
+
const webhook = apiUrl + '/webhook/kb/status?token=' + KB_WEBHOOK_TOKEN;
|
|
1451
|
+
const embedding = normalizeEmbedding(namespace.embedding);
|
|
1452
|
+
embedding.api_key = process.env.EMBEDDING_API_KEY || process.env.GPTKEY;
|
|
1430
1453
|
|
|
1431
|
-
|
|
1454
|
+
const json = {
|
|
1432
1455
|
id: saved_kb._id,
|
|
1433
1456
|
type: saved_kb.type,
|
|
1434
1457
|
source: saved_kb.source,
|
|
1435
|
-
content: "",
|
|
1458
|
+
content: saved_kb.content || "",
|
|
1436
1459
|
namespace: saved_kb.namespace,
|
|
1437
|
-
webhook: webhook
|
|
1460
|
+
webhook: webhook,
|
|
1461
|
+
hybrid: namespace.hybrid,
|
|
1462
|
+
engine: namespace.engine || default_engine,
|
|
1463
|
+
embedding: embedding,
|
|
1464
|
+
...(saved_kb.scrape_type && { scrape_type: saved_kb.scrape_type }),
|
|
1465
|
+
...(saved_kb.scrape_options && { parameters_scrape_type_4: saved_kb.scrape_options }),
|
|
1466
|
+
...(saved_kb.tags && { tags: saved_kb.tags }),
|
|
1438
1467
|
}
|
|
1439
|
-
winston.debug("json: ", json);
|
|
1440
1468
|
|
|
1441
|
-
|
|
1442
|
-
json.content = saved_kb.content;
|
|
1443
|
-
}
|
|
1444
|
-
if (saved_kb.scrape_type) {
|
|
1445
|
-
json.scrape_type = saved_kb.scrape_type;
|
|
1446
|
-
}
|
|
1447
|
-
if (saved_kb.scrape_options) {
|
|
1448
|
-
json.parameters_scrape_type_4 = saved_kb.scrape_options;
|
|
1449
|
-
}
|
|
1450
|
-
json.engine = namespace.engine || default_engine;
|
|
1451
|
-
json.hybrid = namespace.hybrid;
|
|
1452
|
-
|
|
1453
|
-
let embedding = namespace.embedding || default_embedding;
|
|
1454
|
-
embedding.api_key = process.env.EMBEDDING_API_KEY || process.env.GPTKEY;
|
|
1455
|
-
json.embedding = embedding;
|
|
1456
|
-
|
|
1457
|
-
let resources = [];
|
|
1469
|
+
winston.debug("json: ", json);
|
|
1458
1470
|
|
|
1459
|
-
resources.push(json);
|
|
1460
|
-
|
|
1461
1471
|
if (process.env.NODE_ENV === 'test') {
|
|
1462
1472
|
return res.status(200).send({ success: true, message: "Schedule scrape skipped in test environment", data: raw_content, schedule_json: json });
|
|
1463
1473
|
}
|
|
1464
1474
|
|
|
1465
|
-
aiManager.scheduleScrape(
|
|
1475
|
+
aiManager.scheduleScrape([json], namespace.hybrid);
|
|
1466
1476
|
return res.status(200).send(raw_content);
|
|
1467
1477
|
|
|
1468
1478
|
}
|
|
@@ -1480,30 +1490,37 @@ router.post('/multi', upload.single('uploadFile'), async (req, res) => {
|
|
|
1480
1490
|
list = req.body.list;
|
|
1481
1491
|
}
|
|
1482
1492
|
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
let scrape_type = req.body
|
|
1486
|
-
let
|
|
1487
|
-
|
|
1488
|
-
|
|
1493
|
+
const id_project = req.projectid;
|
|
1494
|
+
const project = req.project;
|
|
1495
|
+
let { refresh_rate = 'never', scrape_type = 2, scrape_options } = req.body;
|
|
1496
|
+
let tags = parseStringArrayField(req.body.tags);
|
|
1497
|
+
|
|
1498
|
+
|
|
1499
|
+
if (scrape_type === 2 && !scrape_options) {
|
|
1500
|
+
try {
|
|
1501
|
+
scrape_options = aiManager.setDefaultScrapeOptions();
|
|
1502
|
+
} catch (err) {
|
|
1503
|
+
winston.error("Error setting default scrape options: ", err);
|
|
1504
|
+
return res.status(500).send({ success: false, error: err.error });
|
|
1505
|
+
}
|
|
1489
1506
|
}
|
|
1490
1507
|
|
|
1491
|
-
|
|
1508
|
+
const namespace_id = req.query.namespace;
|
|
1492
1509
|
if (!namespace_id) {
|
|
1493
1510
|
return res.status(400).send({ success: false, error: "queryParam 'namespace' is not defined" })
|
|
1494
1511
|
}
|
|
1495
|
-
|
|
1496
1512
|
let namespace;
|
|
1497
1513
|
try {
|
|
1498
|
-
namespace = await aiManager.checkNamespace(
|
|
1514
|
+
namespace = await aiManager.checkNamespace(id_project, namespace_id);
|
|
1499
1515
|
} catch (err) {
|
|
1516
|
+
winston.error("Error checking namespace: ", err);
|
|
1500
1517
|
let errorCode = err?.errorCode ?? 500;
|
|
1501
1518
|
return res.status(errorCode).send({ success: false, error: err.error });
|
|
1502
1519
|
}
|
|
1503
1520
|
|
|
1504
|
-
|
|
1521
|
+
const quoteManager = req.app.get('quote_manager');
|
|
1505
1522
|
try {
|
|
1506
|
-
await aiManager.checkQuotaAvailability(quoteManager,
|
|
1523
|
+
await aiManager.checkQuotaAvailability(quoteManager, project, list.length)
|
|
1507
1524
|
} catch(err) {
|
|
1508
1525
|
let errorCode = err?.errorCode ?? 500;
|
|
1509
1526
|
return res.status(errorCode).send({ success: false, error: err.error, plan_limit: err.plan_limit })
|
|
@@ -1515,14 +1532,14 @@ router.post('/multi', upload.single('uploadFile'), async (req, res) => {
|
|
|
1515
1532
|
}
|
|
1516
1533
|
|
|
1517
1534
|
const options = {
|
|
1518
|
-
scrape_type
|
|
1519
|
-
scrape_options
|
|
1520
|
-
refresh_rate
|
|
1535
|
+
scrape_type,
|
|
1536
|
+
scrape_options,
|
|
1537
|
+
refresh_rate,
|
|
1538
|
+
...(Array.isArray(tags) && tags.length > 0 ? { tags } : {})
|
|
1521
1539
|
}
|
|
1522
1540
|
|
|
1523
|
-
let result;
|
|
1524
1541
|
try {
|
|
1525
|
-
result = await aiManager.addMultipleUrls(namespace, list, options);
|
|
1542
|
+
const result = await aiManager.addMultipleUrls(namespace, list, options);
|
|
1526
1543
|
return res.status(200).send(result);
|
|
1527
1544
|
} catch (err) {
|
|
1528
1545
|
winston.error("addMultipleUrls error: ", err)
|
|
@@ -1539,8 +1556,9 @@ router.post('/csv', upload.single('uploadFile'), async (req, res) => {
|
|
|
1539
1556
|
let csv = req.file.buffer.toString('utf8');
|
|
1540
1557
|
winston.debug("csv: ", csv);
|
|
1541
1558
|
|
|
1542
|
-
|
|
1543
|
-
|
|
1559
|
+
const { delimiter = ';' } = req.body;
|
|
1560
|
+
let tags = parseStringArrayField(req.body.tags);
|
|
1561
|
+
|
|
1544
1562
|
|
|
1545
1563
|
let namespace;
|
|
1546
1564
|
try {
|
|
@@ -1567,14 +1585,15 @@ router.post('/csv', upload.single('uploadFile'), async (req, res) => {
|
|
|
1567
1585
|
type: 'faq',
|
|
1568
1586
|
content: question + "\n" + answer,
|
|
1569
1587
|
namespace: namespace_id,
|
|
1570
|
-
status: -1
|
|
1588
|
+
status: -1,
|
|
1589
|
+
...(Array.isArray(tags) && tags.length > 0 ? { tags } : {})
|
|
1571
1590
|
})
|
|
1572
1591
|
})
|
|
1573
1592
|
.on("end", async () => {
|
|
1574
1593
|
winston.debug("kbs after CSV parsing: ", kbs);
|
|
1575
1594
|
|
|
1595
|
+
const quoteManager = req.app.get('quote_manager');
|
|
1576
1596
|
try {
|
|
1577
|
-
let quoteManager = req.app.get('quote_manager');
|
|
1578
1597
|
await aiManager.checkQuotaAvailability(quoteManager, req.project, kbs.length)
|
|
1579
1598
|
} catch(err) {
|
|
1580
1599
|
let errorCode = err?.errorCode ?? 500;
|
|
@@ -1595,7 +1614,7 @@ router.post('/csv', upload.single('uploadFile'), async (req, res) => {
|
|
|
1595
1614
|
aiManager.saveBulk(operations, kbs, project_id, namespace_id).then((result) => {
|
|
1596
1615
|
|
|
1597
1616
|
let engine = namespace.engine || default_engine;
|
|
1598
|
-
let embedding = namespace.embedding
|
|
1617
|
+
let embedding = normalizeEmbedding(namespace.embedding);
|
|
1599
1618
|
embedding.api_key = process.env.EMBEDDING_API_KEY || process.env.GPTKEY;
|
|
1600
1619
|
let hybrid = namespace.hybrid;
|
|
1601
1620
|
|
|
@@ -1648,11 +1667,15 @@ router.post('/sitemap', async (req, res) => {
|
|
|
1648
1667
|
|
|
1649
1668
|
router.post('/sitemap/import', async (req, res) => {
|
|
1650
1669
|
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1670
|
+
const id_project = req.projectid;
|
|
1671
|
+
const namespace_id = req.query.namespace;
|
|
1672
|
+
|
|
1673
|
+
let { type, source, refresh_rate = 'never', scrape_type = 2, scrape_options, tags } = req.body;
|
|
1674
|
+
if (scrape_type === 2 && !scrape_options) {
|
|
1675
|
+
scrape_options = aiManager.setDefaultScrapeOptions();
|
|
1676
|
+
}
|
|
1654
1677
|
|
|
1655
|
-
if (
|
|
1678
|
+
if (type !== "sitemap") {
|
|
1656
1679
|
return res.status(403).send({success: false, error: "Endpoint available for sitemap type only." });
|
|
1657
1680
|
}
|
|
1658
1681
|
|
|
@@ -1662,13 +1685,12 @@ router.post('/sitemap/import', async (req, res) => {
|
|
|
1662
1685
|
|
|
1663
1686
|
let namespace;
|
|
1664
1687
|
try {
|
|
1665
|
-
namespace = await aiManager.checkNamespace(
|
|
1688
|
+
namespace = await aiManager.checkNamespace(id_project, namespace_id);
|
|
1666
1689
|
} catch (err) {
|
|
1667
1690
|
let errorCode = err?.errorCode ?? 500;
|
|
1668
1691
|
return res.status(errorCode).send({ success: false, error: err.error });
|
|
1669
1692
|
}
|
|
1670
1693
|
|
|
1671
|
-
let sitemap_url = req.body.source;
|
|
1672
1694
|
|
|
1673
1695
|
// let quoteManager = req.app.get('quote_manager');
|
|
1674
1696
|
// let limits = await quoteManager.getPlanLimits(req.project);
|
|
@@ -1679,7 +1701,7 @@ router.post('/sitemap/import', async (req, res) => {
|
|
|
1679
1701
|
// winston.verbose("Kbs count: " + kbs_count);
|
|
1680
1702
|
|
|
1681
1703
|
const sitemap = new Sitemapper({
|
|
1682
|
-
url:
|
|
1704
|
+
url: source,
|
|
1683
1705
|
timeout: 15000,
|
|
1684
1706
|
debug: false
|
|
1685
1707
|
});
|
|
@@ -1704,41 +1726,34 @@ router.post('/sitemap/import', async (req, res) => {
|
|
|
1704
1726
|
// return res.status(403).send({ success: false, error: "Cannot exceed the number of resources in the current plan", plan_limit: kbs_limit })
|
|
1705
1727
|
// }
|
|
1706
1728
|
|
|
1707
|
-
let refresh_rate = req.body.refresh_rate;
|
|
1708
|
-
let scrape_type = req.body.scrape_type ?? 2;
|
|
1709
|
-
let scrape_options = req.body.scrape_options;
|
|
1710
|
-
if (scrape_type === 2 && scrape_options == null) {
|
|
1711
|
-
scrape_options = aiManager.setDefaultScrapeOptions();
|
|
1712
|
-
}
|
|
1713
|
-
|
|
1714
1729
|
let sitemap_content = {
|
|
1715
|
-
id_project
|
|
1716
|
-
name:
|
|
1717
|
-
source:
|
|
1730
|
+
id_project,
|
|
1731
|
+
name: source,
|
|
1732
|
+
source: source,
|
|
1718
1733
|
type: 'sitemap',
|
|
1719
1734
|
content: "",
|
|
1720
1735
|
namespace: namespace_id,
|
|
1721
|
-
scrape_type
|
|
1722
|
-
scrape_options
|
|
1723
|
-
refresh_rate
|
|
1736
|
+
scrape_type,
|
|
1737
|
+
scrape_options,
|
|
1738
|
+
refresh_rate,
|
|
1739
|
+
...(Array.isArray(tags) && tags.length > 0 ? { tags } : {})
|
|
1724
1740
|
}
|
|
1725
1741
|
|
|
1726
1742
|
let saved_content;
|
|
1727
1743
|
try {
|
|
1728
|
-
saved_content = await KB.findOneAndUpdate({ id_project
|
|
1744
|
+
saved_content = await KB.findOneAndUpdate({ id_project, type: 'sitemap', source, namespace: namespace_id }, sitemap_content, { upsert: true, new: true }).lean().exec();
|
|
1729
1745
|
} catch (err) {
|
|
1730
1746
|
winston.error("Error saving content: ", err);
|
|
1731
1747
|
return res.status(500).send({ success: false, error: err });
|
|
1732
1748
|
}
|
|
1733
1749
|
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
1750
|
const options = {
|
|
1737
1751
|
sitemap_origin_id: saved_content._id,
|
|
1738
1752
|
sitemap_origin: saved_content.source,
|
|
1739
|
-
scrape_type
|
|
1740
|
-
scrape_options
|
|
1741
|
-
refresh_rate
|
|
1753
|
+
scrape_type,
|
|
1754
|
+
scrape_options,
|
|
1755
|
+
refresh_rate,
|
|
1756
|
+
...(Array.isArray(tags) && tags.length > 0 ? { tags } : {})
|
|
1742
1757
|
}
|
|
1743
1758
|
|
|
1744
1759
|
try {
|
package/services/aiManager.js
CHANGED
|
@@ -66,11 +66,12 @@ class AiManager {
|
|
|
66
66
|
content: "",
|
|
67
67
|
namespace: namespace.id,
|
|
68
68
|
status: -1,
|
|
69
|
-
sitemap_origin_id: options.sitemap_origin_id,
|
|
70
|
-
sitemap_origin: options.sitemap_origin,
|
|
71
69
|
scrape_type: options.scrape_type,
|
|
72
70
|
scrape_options: options.scrape_options,
|
|
73
|
-
refresh_rate: options.refresh_rate
|
|
71
|
+
refresh_rate: options.refresh_rate,
|
|
72
|
+
...(options.sitemap_origin_id && { sitemap_origin_id: options.sitemap_origin_id }),
|
|
73
|
+
...(options.sitemap_origin && { sitemap_origin: options.sitemap_origin }),
|
|
74
|
+
...(options.tags && { tags: options.tags }),
|
|
74
75
|
}
|
|
75
76
|
return kb;
|
|
76
77
|
})
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
๏ปฟQuestion 1;
|
|
2
|
-
Question 2;
|
|
1
|
+
๏ปฟQuestion 1;Answer 1
|
|
2
|
+
Question 2;Answer 2
|
package/test/kbRoute.js
CHANGED
|
@@ -273,7 +273,7 @@ describe('KbRoute', () => {
|
|
|
273
273
|
chai.request(server)
|
|
274
274
|
.post('/' + savedProject._id + '/kb/namespace/import/' + namespace_id)
|
|
275
275
|
.auth(email, pwd)
|
|
276
|
-
|
|
276
|
+
//.set('Content-Type', 'text/plain')
|
|
277
277
|
.attach('uploadFile', fs.readFileSync(path.resolve(__dirname, './fixtures/exported_namespace.json')), 'exported_namespace.json')
|
|
278
278
|
.end((err, res) => {
|
|
279
279
|
|
|
@@ -285,7 +285,28 @@ describe('KbRoute', () => {
|
|
|
285
285
|
expect(res.body.success).to.equal(true);
|
|
286
286
|
expect(res.body.message).to.equal("Contents imported successfully");
|
|
287
287
|
|
|
288
|
-
|
|
288
|
+
chai.request(server)
|
|
289
|
+
.get('/' + savedProject._id + '/kb/?namespace=' + namespace_id)
|
|
290
|
+
.auth(email, pwd)
|
|
291
|
+
.end((err, res) => {
|
|
292
|
+
if (err) { console.error("err: ", err); }
|
|
293
|
+
if (log) { console.log("get namespace res.body: ", res.body); }
|
|
294
|
+
|
|
295
|
+
res.should.have.status(200);
|
|
296
|
+
res.body.should.be.a('object');
|
|
297
|
+
expect(res.body.count).to.equal(3);
|
|
298
|
+
expect(res.body.kbs.length).to.equal(3);
|
|
299
|
+
|
|
300
|
+
let content_with_tags = res.body.kbs.find(kb => kb.source === 'Example content');
|
|
301
|
+
expect(content_with_tags.tags.length).to.equal(2);
|
|
302
|
+
expect(content_with_tags.tags[0]).to.equal('tag1');
|
|
303
|
+
expect(content_with_tags.tags[1]).to.equal('tag2');
|
|
304
|
+
|
|
305
|
+
let content_without_tags = res.body.kbs.find(kb => kb.source !== 'Example content');
|
|
306
|
+
expect(content_without_tags.tags).to.equal(undefined);
|
|
307
|
+
|
|
308
|
+
done();
|
|
309
|
+
})
|
|
289
310
|
|
|
290
311
|
})
|
|
291
312
|
|
|
@@ -514,7 +535,8 @@ describe('KbRoute', () => {
|
|
|
514
535
|
type: "url",
|
|
515
536
|
source: "https://www.exampleurl5.com",
|
|
516
537
|
content: "",
|
|
517
|
-
namespace: namespace_id
|
|
538
|
+
namespace: namespace_id,
|
|
539
|
+
tags: ["test", "example"]
|
|
518
540
|
}
|
|
519
541
|
|
|
520
542
|
chai.request(server)
|
|
@@ -539,6 +561,9 @@ describe('KbRoute', () => {
|
|
|
539
561
|
expect(realResponse.value.type).to.equal("url")
|
|
540
562
|
expect(realResponse.value.source).to.equal("https://www.exampleurl5.com")
|
|
541
563
|
expect(realResponse.value.status).to.equal(-1)
|
|
564
|
+
expect(realResponse.value.tags.length).to.equal(2);
|
|
565
|
+
expect(realResponse.value.tags[0]).to.equal("test");
|
|
566
|
+
expect(realResponse.value.tags[1]).to.equal("example");
|
|
542
567
|
should.not.exist(realResponse.engine)
|
|
543
568
|
should.not.exist(realResponse.value.engine)
|
|
544
569
|
should.not.exist(realResponse.embedding)
|
|
@@ -549,6 +574,9 @@ describe('KbRoute', () => {
|
|
|
549
574
|
expect(scheduleJson.type).to.equal("url")
|
|
550
575
|
expect(scheduleJson.source).to.equal("https://www.exampleurl5.com")
|
|
551
576
|
expect(scheduleJson.hybrid).to.equal(false);
|
|
577
|
+
expect(scheduleJson.tags.length).to.equal(2);
|
|
578
|
+
expect(scheduleJson.tags[0]).to.equal("test");
|
|
579
|
+
expect(scheduleJson.tags[1]).to.equal("example");
|
|
552
580
|
should.exist(scheduleJson.engine)
|
|
553
581
|
should.exist(scheduleJson.embedding)
|
|
554
582
|
|
|
@@ -587,7 +615,8 @@ describe('KbRoute', () => {
|
|
|
587
615
|
type: "text",
|
|
588
616
|
source: "example_text1",
|
|
589
617
|
content: "Example text",
|
|
590
|
-
namespace: namespace_id
|
|
618
|
+
namespace: namespace_id,
|
|
619
|
+
tags: ["test", "example"]
|
|
591
620
|
}
|
|
592
621
|
|
|
593
622
|
chai.request(server)
|
|
@@ -603,6 +632,7 @@ describe('KbRoute', () => {
|
|
|
603
632
|
res.body.should.be.a('object');
|
|
604
633
|
|
|
605
634
|
let realResponse = res.body.data;
|
|
635
|
+
console.log("realResponse: ", realResponse);
|
|
606
636
|
expect(realResponse.value.id_project).to.equal(namespace_id)
|
|
607
637
|
expect(realResponse.value.name).to.equal("example_text1")
|
|
608
638
|
expect(realResponse.value.type).to.equal("text")
|
|
@@ -610,7 +640,10 @@ describe('KbRoute', () => {
|
|
|
610
640
|
expect(realResponse.value.status).to.equal(-1)
|
|
611
641
|
expect(typeof realResponse.value.scrape_type === "undefined").to.be.true;
|
|
612
642
|
expect(typeof realResponse.value.scrape_options === "undefined").to.be.true;
|
|
613
|
-
|
|
643
|
+
expect(realResponse.value.tags.length).to.equal(2);
|
|
644
|
+
expect(realResponse.value.tags[0]).to.equal("test");
|
|
645
|
+
expect(realResponse.value.tags[1]).to.equal("example");
|
|
646
|
+
|
|
614
647
|
done();
|
|
615
648
|
})
|
|
616
649
|
})
|
|
@@ -885,6 +918,66 @@ describe('KbRoute', () => {
|
|
|
885
918
|
})
|
|
886
919
|
}).timeout(20000)
|
|
887
920
|
|
|
921
|
+
it('add-single-faq-success', (done) => {
|
|
922
|
+
var email = "test-signup-" + Date.now() + "@email.com";
|
|
923
|
+
var pwd = "pwd";
|
|
924
|
+
|
|
925
|
+
userService.signup(email, pwd, "Test Firstname", "Test lastname").then(function (savedUser) {
|
|
926
|
+
projectService.create("test-faqkb-create", savedUser._id).then(function (savedProject) {
|
|
927
|
+
|
|
928
|
+
chai.request(server)
|
|
929
|
+
.get('/' + savedProject._id + '/kb/namespace/all')
|
|
930
|
+
.auth(email, pwd)
|
|
931
|
+
.end((err, res) => {
|
|
932
|
+
if (err) { console.error("err: ", err); }
|
|
933
|
+
if (log) { console.log("res.body: ", res.body) }
|
|
934
|
+
|
|
935
|
+
res.should.have.status(200);
|
|
936
|
+
expect(res.body.length).to.equal(1);
|
|
937
|
+
|
|
938
|
+
let namespace_id = res.body[0].id;
|
|
939
|
+
|
|
940
|
+
let content = {
|
|
941
|
+
name: "Sample question",
|
|
942
|
+
source: "Sample question",
|
|
943
|
+
content: "Sample question\nSample answer",
|
|
944
|
+
type: "faq",
|
|
945
|
+
tags: ["tag1", "tag2"],
|
|
946
|
+
namespace: namespace_id
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
chai.request(server)
|
|
950
|
+
.post('/' + savedProject._id + '/kb')
|
|
951
|
+
.auth(email, pwd)
|
|
952
|
+
.send(content)
|
|
953
|
+
.end((err, res) => {
|
|
954
|
+
if (err) { console.error("err: ", err); }
|
|
955
|
+
if (log) { console.log("res.body: ", res.body) }
|
|
956
|
+
|
|
957
|
+
res.should.have.status(200);
|
|
958
|
+
res.body.should.be.a('object');
|
|
959
|
+
|
|
960
|
+
let realResponse = res.body.data;
|
|
961
|
+
expect(realResponse.value.namespace).to.equal(namespace_id);
|
|
962
|
+
expect(realResponse.value.type).to.equal("faq");
|
|
963
|
+
expect(realResponse.value.source).to.equal("Sample question");
|
|
964
|
+
expect(realResponse.value.content).to.equal("Sample question\nSample answer");
|
|
965
|
+
expect(realResponse.value.tags.length).to.equal(2);
|
|
966
|
+
expect(realResponse.value.tags[0]).to.equal("tag1");
|
|
967
|
+
expect(realResponse.value.tags[1]).to.equal("tag2");
|
|
968
|
+
|
|
969
|
+
done();
|
|
970
|
+
|
|
971
|
+
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
})
|
|
978
|
+
});
|
|
979
|
+
})
|
|
980
|
+
|
|
888
981
|
it('add-multiple-faqs-with-csv', (done) => {
|
|
889
982
|
|
|
890
983
|
var email = "test-signup-" + Date.now() + "@email.com";
|
|
@@ -916,14 +1009,15 @@ describe('KbRoute', () => {
|
|
|
916
1009
|
chai.request(server)
|
|
917
1010
|
.post('/' + savedProject._id + '/kb/csv?namespace=' + namespace_id)
|
|
918
1011
|
.auth(email, pwd)
|
|
919
|
-
|
|
920
|
-
.attach('uploadFile', fs.readFileSync(path.resolve(__dirname, './fixtures/example-kb-faqs.csv')), 'example-kb-faqs.csv')
|
|
1012
|
+
//.set('Content-Type', 'text/csv')
|
|
921
1013
|
.field('delimiter', ';')
|
|
1014
|
+
.field('tags', JSON.stringify(['tag1', 'tag2']))
|
|
1015
|
+
.attach('uploadFile', fs.readFileSync(path.resolve(__dirname, './fixtures/example-kb-faqs.csv')), 'example-kb-faqs.csv')
|
|
922
1016
|
.end((err, res) => {
|
|
923
1017
|
|
|
924
1018
|
if (err) { console.error("err: ", err); }
|
|
925
1019
|
if (log) { console.log("res.body: ", res.body) }
|
|
926
|
-
|
|
1020
|
+
console.log("res.body: ", res.body)
|
|
927
1021
|
res.should.have.status(200);
|
|
928
1022
|
res.body.should.be.a('object');
|
|
929
1023
|
expect(res.body.success).to.equal(true);
|
|
@@ -931,6 +1025,7 @@ describe('KbRoute', () => {
|
|
|
931
1025
|
|
|
932
1026
|
let realResponse = res.body.data;
|
|
933
1027
|
realResponse.should.be.a('array');
|
|
1028
|
+
console.log("realResponse: ", realResponse[0]);
|
|
934
1029
|
expect(realResponse.length).to.equal(2);
|
|
935
1030
|
expect(realResponse[0].namespace).to.equal(namespace_id);
|
|
936
1031
|
expect(realResponse[0].type).to.equal('faq');
|
|
@@ -983,7 +1078,7 @@ describe('KbRoute', () => {
|
|
|
983
1078
|
chai.request(server)
|
|
984
1079
|
.post('/' + savedProject._id + '/kb/multi?namespace=123456')
|
|
985
1080
|
.auth(email, pwd)
|
|
986
|
-
|
|
1081
|
+
//.set('Content-Type', 'text/plain')
|
|
987
1082
|
.attach('uploadFile', fs.readFileSync(path.resolve(__dirname, './fixtures/kbUrlsList.txt')), 'kbUrlsList.txt')
|
|
988
1083
|
.end((err, res) => {
|
|
989
1084
|
|
|
@@ -1030,7 +1125,7 @@ describe('KbRoute', () => {
|
|
|
1030
1125
|
chai.request(server)
|
|
1031
1126
|
.post('/' + savedProject._id + '/kb/multi?namespace=fakenamespaceid')
|
|
1032
1127
|
.auth(email, pwd)
|
|
1033
|
-
|
|
1128
|
+
//.set('Content-Type', 'text/plain')
|
|
1034
1129
|
.attach('uploadFile', fs.readFileSync(path.resolve(__dirname, './fixtures/kbUrlsList.txt')), 'kbUrlsList.txt')
|
|
1035
1130
|
.end((err, res) => {
|
|
1036
1131
|
|
|
@@ -1077,8 +1172,14 @@ describe('KbRoute', () => {
|
|
|
1077
1172
|
chai.request(server)
|
|
1078
1173
|
.post('/' + savedProject._id + '/kb/multi?namespace=' + namespace_id)
|
|
1079
1174
|
.auth(email, pwd)
|
|
1080
|
-
.
|
|
1081
|
-
.
|
|
1175
|
+
.field('refresh_rate', 'never')
|
|
1176
|
+
.field('scrape_type', '2')
|
|
1177
|
+
.field('tags', JSON.stringify(['tag1', 'tag2']))
|
|
1178
|
+
.attach(
|
|
1179
|
+
'uploadFile',
|
|
1180
|
+
fs.readFileSync(path.resolve(__dirname, './fixtures/kbUrlsList.txt')),
|
|
1181
|
+
'kbUrlsList.txt'
|
|
1182
|
+
)
|
|
1082
1183
|
.end((err, res) => {
|
|
1083
1184
|
|
|
1084
1185
|
if (err) { console.error("err: ", err); }
|
|
@@ -1090,15 +1191,24 @@ describe('KbRoute', () => {
|
|
|
1090
1191
|
expect(realResponse.length).to.equal(4);
|
|
1091
1192
|
expect(realResponse[0].namespace).to.equal(namespace_id);
|
|
1092
1193
|
expect(realResponse[0].source).to.equal('https://gethelp.tiledesk.com/articles/article1');
|
|
1194
|
+
expect(realResponse[0].tags.length).to.equal(2);
|
|
1195
|
+
expect(realResponse[0].tags[0]).to.equal('tag1');
|
|
1196
|
+
expect(realResponse[0].tags[1]).to.equal('tag2');
|
|
1093
1197
|
should.not.exist(realResponse[0].engine);
|
|
1094
1198
|
should.not.exist(realResponse[0].embedding);
|
|
1095
1199
|
expect(realResponse[1].namespace).to.equal(namespace_id);
|
|
1096
1200
|
expect(realResponse[1].source).to.equal('https://gethelp.tiledesk.com/articles/article2');
|
|
1201
|
+
expect(realResponse[1].tags.length).to.equal(2);
|
|
1202
|
+
expect(realResponse[1].tags[0]).to.equal('tag1');
|
|
1203
|
+
expect(realResponse[1].tags[1]).to.equal('tag2');
|
|
1097
1204
|
|
|
1098
1205
|
let scheduleJson = res.body.schedule_json;
|
|
1099
1206
|
expect(scheduleJson.length).to.equal(4);
|
|
1100
1207
|
expect(scheduleJson[0].namespace).to.equal(namespace_id);
|
|
1101
1208
|
expect(scheduleJson[0].source).to.equal('https://gethelp.tiledesk.com/articles/article1');
|
|
1209
|
+
expect(scheduleJson[0].tags.length).to.equal(2);
|
|
1210
|
+
expect(scheduleJson[0].tags[0]).to.equal('tag1');
|
|
1211
|
+
expect(scheduleJson[0].tags[1]).to.equal('tag2');
|
|
1102
1212
|
should.exist(scheduleJson[0].engine);
|
|
1103
1213
|
should.exist(scheduleJson[0].embedding);
|
|
1104
1214
|
expect(scheduleJson[0].engine.index_name).to.equal(namespace.engine.index_name);
|
|
@@ -1107,6 +1217,9 @@ describe('KbRoute', () => {
|
|
|
1107
1217
|
|
|
1108
1218
|
expect(scheduleJson[1].namespace).to.equal(namespace_id);
|
|
1109
1219
|
expect(scheduleJson[1].source).to.equal('https://gethelp.tiledesk.com/articles/article2');
|
|
1220
|
+
expect(scheduleJson[1].tags.length).to.equal(2);
|
|
1221
|
+
expect(scheduleJson[1].tags[0]).to.equal('tag1');
|
|
1222
|
+
expect(scheduleJson[1].tags[1]).to.equal('tag2');
|
|
1110
1223
|
|
|
1111
1224
|
done();
|
|
1112
1225
|
|
package/utils/arrayUtil.js
CHANGED
|
@@ -13,4 +13,38 @@ function arraysEqual(a, b) {
|
|
|
13
13
|
if (a[i] !== b[i]) return false;
|
|
14
14
|
}
|
|
15
15
|
return true;
|
|
16
|
-
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Parse a field as an array of strings.
|
|
20
|
+
* Returns the array if valid, otherwise undefined.
|
|
21
|
+
*/
|
|
22
|
+
function parseStringArrayField(field) {
|
|
23
|
+
if (!field) return undefined;
|
|
24
|
+
|
|
25
|
+
let arr;
|
|
26
|
+
|
|
27
|
+
if (typeof field === 'string') {
|
|
28
|
+
try {
|
|
29
|
+
arr = JSON.parse(field); // prova a parsare come JSON
|
|
30
|
+
} catch {
|
|
31
|
+
return undefined; // se malformato, skippa
|
|
32
|
+
}
|
|
33
|
+
} else if (Array.isArray(field)) {
|
|
34
|
+
arr = field;
|
|
35
|
+
} else {
|
|
36
|
+
return undefined; // non stringa nรฉ array
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// controlla che tutti gli elementi siano stringhe
|
|
40
|
+
if (Array.isArray(arr) && arr.every(e => typeof e === 'string')) {
|
|
41
|
+
return arr;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return undefined; // non รจ un array di stringhe
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = {
|
|
48
|
+
arraysEqual,
|
|
49
|
+
parseStringArrayField
|
|
50
|
+
};
|