@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 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
@@ -175,6 +175,11 @@ var KBSchema = new Schema({
175
175
  last_error: {
176
176
  type: Object,
177
177
  required: false
178
+ },
179
+ tags: {
180
+ type: Array,
181
+ default: undefined,
182
+ required: false
178
183
  }
179
184
  }, {
180
185
  timestamps: true
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.6",
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.44",
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.25",
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 || default_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 || default_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 || default_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
- await KB.insertMany(addingContents).catch((err) => {
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
- let project_id = req.projectid;
1366
- let body = req.body;
1367
- let namespace_id = body.namespace;
1382
+ const id_project = req.projectid;
1383
+ const project = req.project
1368
1384
 
1369
- if (!body.namespace) {
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(project_id, namespace_id);
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
- let quoteManager = req.app.get('quote_manager');
1400
+ const quoteManager = req.app.get('quote_manager');
1382
1401
  try {
1383
- await aiManager.checkQuotaAvailability(quoteManager, req.project, 1)
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: project_id,
1391
- name: body.name,
1392
- type: body.type,
1393
- source: body.source,
1394
- content: body.content,
1395
- namespace: body.namespace,
1409
+ id_project,
1410
+ name,
1411
+ type,
1412
+ source,
1413
+ content,
1414
+ namespace: namespace_id,
1396
1415
  status: -1
1397
1416
  }
1398
- if (!new_kb.namespace) {
1399
- new_kb.namespace = project_id;
1400
- }
1401
- if (new_kb.type === 'txt') {
1417
+
1418
+ if (type === 'txt') {
1402
1419
  new_kb.scrape_type = 1;
1403
1420
  }
1404
- if (new_kb.type === 'url') {
1405
- new_kb.refresh_rate = body.refresh_rate;
1406
- if (!body.scrape_type || body.scrape_type === 2) {
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 = body.scrape_type;
1411
- new_kb.scrape_options = body.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: project_id, type: 'url', source: new_kb.source }, new_kb, { upsert: true, new: true, rawResult: true }, async (err, raw_content) => {
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
- let saved_kb = raw_content.value;
1429
- let webhook = apiUrl + '/webhook/kb/status?token=' + KB_WEBHOOK_TOKEN;
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
- let json = {
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
- if (saved_kb.content) {
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(resources, namespace.hybrid);
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
- let project_id = req.projectid;
1484
- let refresh_rate = req.body.refresh_rate;
1485
- let scrape_type = req.body.scrape_type ?? 2;
1486
- let scrape_options = req.body.scrape_options;
1487
- if (scrape_type === 2 && scrape_options == null) {
1488
- scrape_options = aiManager.setDefaultScrapeOptions();
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
- let namespace_id = req.query.namespace;
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(project_id, namespace_id);
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
- let quoteManager = req.app.get('quote_manager');
1521
+ const quoteManager = req.app.get('quote_manager');
1505
1522
  try {
1506
- await aiManager.checkQuotaAvailability(quoteManager, req.project, list.length)
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: scrape_type,
1519
- scrape_options: scrape_options,
1520
- refresh_rate: 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
- let delimiter = req.body.delimiter || ";";
1543
- winston.debug("delimiter: ", delimiter);
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 || default_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
- let project_id = req.projectid;
1652
- let namespace_id = req.query.namespace;
1653
- let content = req.body;
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 (content.type !== "sitemap") {
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(project_id, namespace_id);
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: sitemap_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: project_id,
1716
- name: sitemap_url,
1717
- source: sitemap_url,
1730
+ id_project,
1731
+ name: source,
1732
+ source: source,
1718
1733
  type: 'sitemap',
1719
1734
  content: "",
1720
1735
  namespace: namespace_id,
1721
- scrape_type: scrape_type,
1722
- scrape_options: scrape_options,
1723
- refresh_rate: 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: project_id, type: 'sitemap', source: sitemap_url, namespace: namespace_id }, sitemap_content, { upsert: true, new: true }).lean().exec();
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: saved_content.scrape_type,
1740
- scrape_options: saved_content.scrape_options,
1741
- refresh_rate: saved_content.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 {
@@ -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;Question 1 Answer 1
2
- Question 2;Question 2 Answer 2
1
+ ๏ปฟQuestion 1;Answer 1
2
+ Question 2;Answer 2
@@ -19,7 +19,8 @@
19
19
  "name": "Example content",
20
20
  "namespace": "6835be87d0a352002d806f73",
21
21
  "status": -1,
22
- "updatedAt": "2025-05-27T13:32:18.078Z"
22
+ "updatedAt": "2025-05-27T13:32:18.078Z",
23
+ "tags": ["tag1", "tag2"]
23
24
  },
24
25
  {
25
26
  "_id": "6835bed02c061f7021f46e99",
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
- .set('Content-Type', 'text/plain')
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
- done();
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
- .set('Content-Type', 'text/csv')
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
- .set('Content-Type', 'text/plain')
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
- .set('Content-Type', 'text/plain')
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
- .set('Content-Type', 'text/plain')
1081
- .attach('uploadFile', fs.readFileSync(path.resolve(__dirname, './fixtures/kbUrlsList.txt')), 'kbUrlsList.txt')
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
 
@@ -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
+ };