@tiledesk/tiledesk-server 2.28.2 → 3.0.0

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/app.js +6 -35
  3. package/config/native_mcp/native_mcp_servers.json +7 -0
  4. package/controllers/chatbot.controller.js +659 -0
  5. package/controllers/intents.controller.js +139 -0
  6. package/errorCodes.js +2 -1
  7. package/event/botEvent.js +43 -3
  8. package/middleware/errorHandler.js +41 -0
  9. package/models/chatbotTemplates.js +4 -0
  10. package/models/faq.js +10 -0
  11. package/models/faq_kb.js +49 -0
  12. package/package.json +2 -2
  13. package/pubmodules/aiGenerations/README.md +154 -0
  14. package/pubmodules/aiGenerations/index.js +7 -0
  15. package/pubmodules/aiGenerations/listeners.js +96 -0
  16. package/pubmodules/aiGenerations/models/faqKbRevision.js +51 -0
  17. package/pubmodules/aiGenerations/routes/aiGenerations.js +426 -0
  18. package/pubmodules/aiGenerations/services/agentWriter.js +286 -0
  19. package/pubmodules/aiGenerations/services/atomic.js +70 -0
  20. package/pubmodules/aiGenerations/services/references.js +78 -0
  21. package/pubmodules/aiGenerations/services/revisionService.js +169 -0
  22. package/pubmodules/aiGenerations/services/snapshot.js +109 -0
  23. package/pubmodules/aiGenerations/test/aiGenerationsRoute.js +503 -0
  24. package/pubmodules/pubModulesManager.js +17 -0
  25. package/routes/faq.js +10 -3
  26. package/routes/faq_kb.js +716 -411
  27. package/services/chatbotService.js +84 -5
  28. package/services/faqService.js +159 -45
  29. package/services/v3FlowService.js +416 -0
  30. package/template/chatbot/blank.js +21 -3
  31. package/template/chatbot/blank_subagent.js +139 -0
  32. package/template/chatbot/blank_voice.js +21 -4
  33. package/template/chatbot/blank_voice_twilio.js +21 -5
  34. package/template/chatbot/index.js +1 -0
  35. package/template/chatbot/subagent_notes.js +30 -0
  36. package/test/faqRoute.js +83 -0
  37. package/test/faqkbRoute.js +982 -523
  38. package/test/faqkbV3Route.js +436 -0
  39. package/test/fixtures/example-json-with-subagents.txt +1 -0
  40. package/test/nativemcp.test.js +6 -1
  41. package/test/v3FlowService.js +187 -0
  42. package/test/webhookRoute.js +2 -1
  43. package/utils/AppError.js +9 -0
  44. package/utils/asyncHandler.js +10 -0
package/routes/faq_kb.js CHANGED
@@ -19,6 +19,11 @@ var roleChecker = require('../middleware/has-role');
19
19
  const roleConstants = require('../models/roleConstants');
20
20
  const errorCodes = require('../errorCodes');
21
21
  const faq_kb = require('../models/faq_kb');
22
+ const chatbotController = require('../controllers/chatbot.controller');
23
+ const intentsController = require('../controllers/intents.controller');
24
+ const AppError = require('../utils/AppError');
25
+ const asyncHandler = require('../utils/asyncHandler');
26
+ const v3FlowService = require('../services/v3FlowService');
22
27
 
23
28
  let chatbot_templates_api_url = process.env.CHATBOT_TEMPLATES_API_URL
24
29
 
@@ -261,57 +266,100 @@ router.put('/:faq_kbid/publish', roleChecker.hasRole('admin'), async (req, res)
261
266
  }
262
267
 
263
268
  const api_url = process.env.API_URL || configGlobal.apiUrl;
264
- winston.debug("fork --> base_url: " + api_url); // check if correct
269
+ winston.debug("fork --> base_url: " + api_url);
265
270
 
266
- let current_project_id = req.projectid;
267
- winston.debug("current project id: " + current_project_id);
268
-
269
- let token = req.headers.authorization;
270
-
271
- let cs = req.app.get('chatbot_service')
271
+ const cs = req.app.get('chatbot_service');
272
272
 
273
273
  try {
274
- // fork(id_faq_kb, api_url, token, project_id)
275
- let forked = await cs.fork(chatbot_id, api_url, token, current_project_id, { forPublish: true });
276
- // winston.debug("forked: ", forked)
277
-
278
- let forkedChatBotId = forked.bot_id;
279
- winston.debug("forkedChatBotId: " + forkedChatBotId);
274
+ const published = await cs.publish({
275
+ id_faq_kb,
276
+ chatbot_id,
277
+ api_url,
278
+ token: req.headers.authorization,
279
+ project_id: req.projectid,
280
+ user_id: req.user.id,
281
+ release_note,
282
+ req
283
+ });
280
284
 
281
- let updatedForkedChabot = await Faq_kb.findByIdAndUpdate(forkedChatBotId, { $unset: { slug: 1 }, trashed: true, root_id: id_faq_kb, release_note: release_note, publishedBy: req.user.id, publishedAt: new Date().getTime() }, { new: true, upsert: true }).exec();
282
- //let updatedForkedChabot = await Faq_kb.findByIdAndUpdate(forkedChatBotId, { $unset: { slug: 1 }, trashed: true, root_id: id_faq_kb, publishedBy: req.user.id, publishedAt: new Date().getTime() }, { new: true }).exec();
283
- winston.debug("updatedForkedChabot: ", updatedForkedChabot);
284
- botEvent.emit('faqbot.update', updatedForkedChabot);
285
+ return res.status(200).send({ message: "Chatbot published successfully", bot_id: published.bot_id });
286
+ } catch (e) {
287
+ winston.error("Error Unable publish chatbot: ", e);
288
+ return res.status(500).send({ success: false, message: "Unable publish chatbot" });
289
+ }
290
+ });
285
291
 
286
- const port = process.env.PORT || '3000';
287
- let TILEBOT_ENDPOINT = process.env.API_ENDPOINT ? process.env.API_ENDPOINT + "/modules/tilebot/ext/" : ("http://localhost:" + port + "/modules/tilebot/ext/");
292
+ router.put('/:faq_kbid/publish/multi', roleChecker.hasRole('admin'), asyncHandler(async (req, res) => {
288
293
 
289
- if (process.env.TILEBOT_ENDPOINT) {
290
- TILEBOT_ENDPOINT = process.env.TILEBOT_ENDPOINT + "/ext/";
291
- }
292
- winston.info("[RequestService] TILEBOT_ENDPOINT: " + TILEBOT_ENDPOINT);
294
+ const id_project = req.projectid;
295
+ const parent_id = req.params.faq_kbid;
296
+ const chatbots = req.body.chatbots;
297
+ const isValidChatbot = (chatbot) => chatbot && typeof chatbot === 'object' && !Array.isArray(chatbot) && typeof chatbot.id === 'string' && chatbot.id.trim();
293
298
 
294
- let updatedOriginalChabot = await Faq_kb.findByIdAndUpdate(id_faq_kb, { url: TILEBOT_ENDPOINT + forkedChatBotId }, { new: true, upsert: true }).exec();
295
- winston.debug("updatedOriginalChabot: ", updatedOriginalChabot);
299
+ if (!Array.isArray(chatbots) || chatbots.length === 0 || chatbots.some((chatbot) => !isValidChatbot(chatbot))) {
300
+ return res.status(400).send({ success: false, message: "chatbots must be a non-empty array of objects with an id" });
301
+ }
296
302
 
297
- cs.setModified(id_faq_kb, false);
303
+ await chatbotController.getChatbot(parent_id, id_project);
304
+ await chatbotController.assertAreSubagents(
305
+ id_project,
306
+ parent_id,
307
+ chatbots.map((chatbot) => chatbot.id)
308
+ );
298
309
 
299
- botEvent.emit('faqbot.update', updatedOriginalChabot);
310
+ const api_url = process.env.API_URL || configGlobal.apiUrl;
311
+ const token = req.headers.authorization;
312
+ const user_id = req.user.id;
313
+ const release_note = req.body.release_note || "No comment";
314
+ const cs = req.app.get('chatbot_service');
315
+ const results = [];
316
+
317
+ for (const chatbot of chatbots) {
318
+ const id_faq_kb = chatbot.id;
319
+ try {
320
+ const published = await cs.publish({
321
+ id_faq_kb,
322
+ chatbot_id: id_faq_kb,
323
+ api_url,
324
+ token,
325
+ project_id: id_project,
326
+ user_id,
327
+ release_note: chatbot.release_note || release_note,
328
+ req
329
+ });
330
+ results.push({ id: id_faq_kb, success: true, bot_id: published.bot_id });
331
+ } catch (err) {
332
+ const error = err?.response?.data?.message || err?.response?.data?.error || err?.message || "Publish failed";
333
+ winston.error("Error publishing chatbot " + id_faq_kb + ": ", err);
334
+ results.push({ id: id_faq_kb, success: false, error });
335
+ }
336
+ }
300
337
 
301
- botEvent.emit('faqbot.publish', {
302
- req: req,
303
- chatbot: updatedOriginalChabot,
304
- publishedBotId: forkedChatBotId,
305
- release_note: release_note
338
+ const failed = results.filter((result) => !result.success);
339
+ if (failed.length > 0) {
340
+ return res.status(500).send({
341
+ success: false,
342
+ message: "Some chatbots failed to publish",
343
+ results
306
344
  });
307
-
308
- return res.status(200).send({ message: "Chatbot published successfully", bot_id: forkedChatBotId });
309
-
310
- } catch (e) {
311
- winston.error("Error Unable publish chatbot: ", e);
312
- return res.status(500).send({ success: false, message: "Unable publish chatbot" });
313
345
  }
314
- });
346
+
347
+ return res.status(200).send({
348
+ success: true,
349
+ message: "Chatbots published successfully",
350
+ results
351
+ });
352
+ }));
353
+
354
+ router.put('/:faq_kbid/restore/:publish_id', roleChecker.hasRole('admin'), asyncHandler(async (req, res) => {
355
+ const chatbot = await chatbotController.restoreFromPublished(
356
+ req.params.faq_kbid,
357
+ req.params.publish_id,
358
+ req.projectid,
359
+ req.user.id
360
+ );
361
+ return res.status(200).send(chatbot);
362
+ }));
315
363
 
316
364
  router.put('/:faq_kbid', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), function (req, res) {
317
365
 
@@ -410,12 +458,81 @@ router.put('/:faq_kbid/language/:language', roleChecker.hasRoleOrTypes('admin',
410
458
 
411
459
  })
412
460
 
461
+ /**
462
+ * V3 agents: the connectors list, read and written as a whole.
463
+ * `GET` answers the list (or null for an agent that has none).
464
+ * `PUT` replaces it: the body is the list (`{ version: 1, items: [...] }`), validated in shape; an
465
+ * `If-Match` header (or an `if_match` query parameter) carrying the `updatedAt` the client last saw turns a save on top of a newer
466
+ * list into a 409 that returns the current one, so two editors cannot overwrite each other blindly.
467
+ * The agent is marked as modified (it has to be published again) and the engine's cache is cleared.
468
+ * Only a V3 agent has a list: for any other agent the PUT is refused and nothing changes.
469
+ */
470
+ router.get('/:faq_kbid/connectors', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), asyncHandler(async (req, res) => {
471
+ const faq_kb = await Faq_kb.findOne({ _id: req.params.faq_kbid, id_project: req.projectid }).select('+connectors').exec();
472
+ if (!faq_kb) {
473
+ return res.status(404).send({ success: false, msg: "Chatbot not found with id " + req.params.faq_kbid + " for project " + req.projectid });
474
+ }
475
+ return res.json(faq_kb.connectors === undefined ? null : faq_kb.connectors);
476
+ }));
477
+
478
+ router.put('/:faq_kbid/connectors', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), asyncHandler(async (req, res) => {
479
+ const chatbot_id = req.params.faq_kbid;
480
+ const id_project = req.projectid;
481
+ const collection = req.body;
482
+
483
+ const errors = v3FlowService.validateConnectors(collection);
484
+ if (errors.length > 0) {
485
+ return res.status(400).send({ success: false, msg: "Invalid connectors list", errors: errors });
486
+ }
487
+
488
+ const faq_kb = await Faq_kb.findOne({ _id: chatbot_id, id_project: id_project }).select('+connectors').exec();
489
+ if (!faq_kb) {
490
+ return res.status(404).send({ success: false, msg: "Chatbot not found with id " + chatbot_id + " for project " + id_project });
491
+ }
492
+ if (!v3FlowService.isV3(faq_kb)) {
493
+ return res.status(400).send({ success: false, msg: "The connectors list belongs to V3 agents only (ds_version 'v3')" });
494
+ }
495
+
496
+ // The `updatedAt` the client last saw: `If-Match` header, or `if_match` query parameter for the
497
+ // clients that cannot send a custom header through a CORS gateway.
498
+ const expected = req.get('If-Match') || req.query.if_match;
499
+ const current = faq_kb.connectors && faq_kb.connectors.updatedAt ? new Date(faq_kb.connectors.updatedAt) : null;
500
+ if (expected && current && new Date(expected).getTime() !== current.getTime()) {
501
+ winston.verbose("(faq_kb) connectors of " + chatbot_id + " changed since " + expected + ": conflict");
502
+ return res.status(409).send({ success: false, msg: "The connectors list changed since " + expected, connectors: faq_kb.connectors });
503
+ }
504
+
505
+ faq_kb.connectors = { version: collection.version, updatedAt: new Date(), items: collection.items };
506
+ faq_kb.modified = true;
507
+ faq_kb.markModified('connectors');
508
+ const saved = await faq_kb.save();
509
+ botEvent.emit('faqbot.update', saved);
510
+ winston.debug("(faq_kb) connectors of " + chatbot_id + " saved: " + saved.connectors.items.length + " records");
511
+ return res.json(saved.connectors);
512
+ }));
513
+
413
514
  router.patch('/:faq_kbid/attributes', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), function (req, res) { //TODO add cache_bot_here
414
515
  var data = req.body;
415
516
 
416
517
  const chatbot_id = req.params.faq_kbid;
417
518
  const id_project = req.projectid;
418
519
 
520
+ // The version label and the connectors list are fields of the bot since 15/10/2026, not attributes.
521
+ // A client still sending them the old way (`dsVersion`, `connectors`) gets them promoted: written
522
+ // to the fields, removed from the attributes, exactly as the import does. The Design Studio V3
523
+ // sends them this way on purpose when it repairs an agent saved before that date.
524
+ let promoted = null;
525
+ if (v3FlowService.legacyAttributeKeys(data).length > 0) {
526
+ promoted = { attributes: data };
527
+ v3FlowService.promoteLegacyJson(promoted);
528
+ if (promoted.connectors !== undefined && promoted.connectors !== null) {
529
+ const errors = v3FlowService.validateConnectors(promoted.connectors);
530
+ if (errors.length > 0) {
531
+ return res.status(400).send({ success: false, msg: "Invalid connectors list", errors: errors });
532
+ }
533
+ }
534
+ }
535
+
419
536
  // TODO use service method
420
537
  Faq_kb.findOne({ _id: chatbot_id, id_project: id_project }, function (err, updatedBot) {
421
538
  if (err) {
@@ -440,6 +557,20 @@ router.patch('/:faq_kbid/attributes', roleChecker.hasRoleOrTypes('admin', ['bot'
440
557
  updatedBot.attributes[key] = val;
441
558
  });
442
559
 
560
+ if (promoted) {
561
+ if (promoted.ds_version && !updatedBot.ds_version) {
562
+ updatedBot.ds_version = promoted.ds_version;
563
+ }
564
+ if (promoted.connectors !== undefined && promoted.connectors !== null) {
565
+ updatedBot.connectors = { version: promoted.connectors.version, updatedAt: new Date(), items: promoted.connectors.items };
566
+ updatedBot.markModified('connectors');
567
+ updatedBot.modified = true;
568
+ }
569
+ delete updatedBot.attributes.dsVersion;
570
+ delete updatedBot.attributes.connectors;
571
+ winston.verbose("(faq_kb) attributes of " + chatbot_id + ": dsVersion/connectors promoted to the bot fields");
572
+ }
573
+
443
574
  winston.debug("updatedBot attributes", updatedBot.attributes)
444
575
 
445
576
  // https://stackoverflow.com/questions/24054552/mongoose-not-saving-nested-object
@@ -459,29 +590,69 @@ router.patch('/:faq_kbid/attributes', roleChecker.hasRoleOrTypes('admin', ['bot'
459
590
 
460
591
  });
461
592
 
462
- router.delete('/:faq_kbid', roleChecker.hasRole('admin'), function (req, res) {
593
+ router.delete('/:faq_kbid', roleChecker.hasRole('admin'), asyncHandler(async function (req, res) {
463
594
 
464
595
  winston.debug(req.body);
465
596
 
466
597
  const chatbot_id = req.params.faq_kbid;
467
598
  const id_project = req.projectid;
468
599
 
469
- Faq_kb.findOneAndDelete({ _id: chatbot_id, id_project: id_project }, function (err, faq_kb) {
470
- if (err) {
471
- return res.status(500).send({ success: false, msg: 'Error deleting object.' });
600
+ let chatbot;
601
+ try {
602
+ chatbot = await Faq_kb.findOne({ _id: chatbot_id, id_project: id_project });
603
+ } catch (err) {
604
+ winston.error("Error getting chatbot: ", err);
605
+ return res.status(500).send({ success: false, msg: 'Error getting chatbot.' });
606
+ }
607
+
608
+ if (!chatbot) {
609
+ return res.status(404).send({
610
+ success: false,
611
+ msg: "Chatbot not found with id " + chatbot_id + " for project " + id_project
612
+ });
613
+ }
614
+
615
+ if (chatbot.subtype === 'subagent') {
616
+ let referencingFaq;
617
+ try {
618
+ referencingFaq = await chatbotController.findSubagentCaller(chatbot, id_project);
619
+ } catch (err) {
620
+ winston.error("Error checking subagent references: ", err);
621
+ return res.status(500).send({ success: false, msg: 'Error checking subagent references.' });
472
622
  }
473
623
 
474
- if (!faq_kb) {
475
- return res.status(404).send({ success: false, msg: "Chatbot not found with id " + chatbot_id + " for project " + id_project });
624
+ if (referencingFaq) {
625
+ return res.status(409).send({
626
+ success: false,
627
+ msg: "Cannot delete subagent because it is still referenced by another chatbot",
628
+ error_code: errorCodes.CHATBOT.ERRORS.SUBAGENT_IN_USE,
629
+ referenced_by: {
630
+ id_faq_kb: referencingFaq.id_faq_kb,
631
+ intent_display_name: referencingFaq.intent_display_name
632
+ }
633
+ });
476
634
  }
477
- /**
478
- * WARNING: faq_kb is the operation result, not the faq_kb object. The event subscriber will not receive the object as expected.
479
- */
480
- botEvent.emit('faqbot.delete', faq_kb);
481
- botEvent.emit('faqbot.deleted', { req, chatbot: faq_kb });
482
- res.status(200).send({ success: true, message: "Chatbot with id " + req.params.faq_kbid + " deleted successfully" })
483
- });
484
- });
635
+ }
636
+
637
+ let deletedFaqKb;
638
+ try {
639
+ deletedFaqKb = await Faq_kb.findOneAndDelete({ _id: chatbot_id, id_project: id_project });
640
+ } catch (err) {
641
+ winston.error("Error deleting chatbot: ", err);
642
+ return res.status(500).send({ success: false, msg: 'Error deleting object.' });
643
+ }
644
+
645
+ if (!deletedFaqKb) {
646
+ return res.status(404).send({ success: false, msg: "Chatbot not found with id " + chatbot_id + " for project " + id_project });
647
+ }
648
+
649
+ /**
650
+ * WARNING: faq_kb is the operation result, not the faq_kb object. The event subscriber will not receive the object as expected.
651
+ */
652
+ botEvent.emit('faqbot.delete', deletedFaqKb);
653
+ botEvent.emit('faqbot.deleted', { req, chatbot: deletedFaqKb });
654
+ return res.status(200).send({ success: true, message: "Chatbot with id " + req.params.faq_kbid + " deleted successfully" });
655
+ }));
485
656
 
486
657
  router.get('/:faq_kbid', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), function (req, res) {
487
658
 
@@ -490,7 +661,8 @@ router.get('/:faq_kbid', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscripti
490
661
  const chatbot_id = req.params.faq_kbid;
491
662
  const id_project = req.projectid;
492
663
 
493
- Faq_kb.findOne({ _id: chatbot_id, id_project: id_project }, function (err, faq_kb) { //TODO add cache_bot_here
664
+ // The single GET is how the Design Studio opens an agent: a V3 agent needs its connectors list here.
665
+ Faq_kb.findOne({ _id: chatbot_id, id_project: id_project }).select('+connectors').exec(function (err, faq_kb) { //TODO add cache_bot_here
494
666
  if (err) {
495
667
  return res.status(500).send({ success: false, msg: 'Error getting object.' });
496
668
  }
@@ -549,6 +721,26 @@ router.get('/:faq_kbid/published', roleChecker.hasRoleOrTypes('admin', ['bot', '
549
721
 
550
722
  })
551
723
 
724
+ router.get('/:faq_kbid/subagents', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), async function (req, res) {
725
+
726
+ const id_project = req.projectid;
727
+ const chatbot_id = req.params.faq_kbid;
728
+
729
+ let subagents;
730
+ try {
731
+ subagents = await Faq_kb.find({
732
+ id_project: id_project,
733
+ parent_id: chatbot_id,
734
+ trashed: { $in: [null, false] }
735
+ }).sort({ createdAt: -1 }).lean().exec();
736
+ return res.status(200).send(subagents);
737
+ } catch (err) {
738
+ winston.error("Error finding subagents: ", err);
739
+ return res.status(500).send({ success: false, error: "Error finding subagents from root " + chatbot_id });
740
+ }
741
+
742
+ })
743
+
552
744
  router.get('/:faq_kbid/jwt', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), function (req, res) {
553
745
 
554
746
  winston.debug(req.query);
@@ -613,7 +805,7 @@ router.get('/', roleChecker.hasRoleOrTypes('agent', ['bot', 'subscription']), fu
613
805
  * if filter only for 'trashed = false',
614
806
  * the bots created before the implementation of the 'trashed' property are not returned
615
807
  */
616
- let query = { id_project: id_project, trashed: { $in: [null, false] } };
808
+ let query = { id_project: id_project, subtype: { $ne: "subagent" }, trashed: { $in: [null, false] } };
617
809
 
618
810
  if (restricted_mode === true) {
619
811
  query.agents_available = {
@@ -656,97 +848,151 @@ router.get('/', roleChecker.hasRoleOrTypes('agent', ['bot', 'subscription']), fu
656
848
  })
657
849
  });
658
850
 
659
- router.post('/fork/:id_faq_kb', roleChecker.hasRole('admin'), async (req, res) => {
660
-
661
- let id_faq_kb = req.params.id_faq_kb;
662
- winston.debug('id_faq_kb: ' + id_faq_kb);
663
-
851
+ // DEPRECATED: create shell via createBot, then import intents/subagents into it
852
+ // router.post('/fork/:id_faq_kb', roleChecker.hasRole('admin'), async (req, res) => {
853
+ //
854
+ // let id_faq_kb = req.params.id_faq_kb;
855
+ // winston.debug('id_faq_kb: ' + id_faq_kb);
856
+ //
857
+ // const api_url = process.env.API_URL || configGlobal.apiUrl;
858
+ // winston.debug("fork --> base_url: " + api_url); // check if correct
859
+ //
860
+ // let current_project_id = req.projectid;
861
+ // winston.debug("current project id: " + current_project_id);
862
+ //
863
+ // let landing_project_id = req.query.projectid;
864
+ // winston.debug("landing project id " + landing_project_id)
865
+ //
866
+ // let public = req.query.public;
867
+ // winston.debug("public " + public);
868
+ //
869
+ // let globals = req.query.globals;
870
+ // winston.debug("export globals " + globals);
871
+ //
872
+ // let token = req.headers.authorization;
873
+ //
874
+ // let cs = req.app.get('chatbot_service')
875
+ // let chatbot;
876
+ // try {
877
+ // chatbot = await cs.getBotById(id_faq_kb, public, api_url, chatbot_templates_api_url, token, current_project_id, globals);
878
+ // } catch (err) {
879
+ // return res.status(500).send({ success: false, error: "Unable to get chatbot to be forked"});
880
+ // }
881
+ //
882
+ // if (!chatbot) {
883
+ // return res.status(500).send({ success: false, message: "Unable to get chatbot to be forked" });
884
+ // }
885
+ //
886
+ // if (!globals) {
887
+ // if (chatbot.attributes) {
888
+ // delete chatbot.attributes.globals
889
+ // }
890
+ // }
891
+ //
892
+ // delete chatbot.modified;
893
+ //
894
+ // chatbot.template = "empty";
895
+ //
896
+ // let savedChatbot
897
+ // try {
898
+ // savedChatbot = await cs.createBot(api_url, token, chatbot, landing_project_id);
899
+ // winston.debug("savedChatbot: ", savedChatbot)
900
+ // } catch(err) {
901
+ // winston.error("Error creating new chatbot: ", err);
902
+ // return res.status(500).send({ success: false, message: "An error occurred during chatbot creation"});
903
+ // }
904
+ //
905
+ // if (!savedChatbot) {
906
+ // return res.status(500).send({ success: false, message: "Unable to create new chatbot" });
907
+ // }
908
+ //
909
+ // let import_result
910
+ // try {
911
+ // // Imports parent intents and linked subagents (with their intents) into the new parent.
912
+ // import_result = await cs.importFaqs(api_url, savedChatbot._id, token, chatbot, landing_project_id);
913
+ // winston.debug("imported: ", import_result);
914
+ // } catch(err) {
915
+ // winston.error("Error importing chatbot content on fork: ", err);
916
+ // return res.status(500).send({ success: false, message: "Error importing chatbot content (intents/subagents)"});
917
+ // }
918
+ //
919
+ // if (import_result?.success == false) {
920
+ // return res.status(500).send({ success: false, message: "Unable to import chatbot content in the new chatbot" });
921
+ // }
922
+ //
923
+ // return res.status(200).send({ message: "Chatbot forked successfully", bot_id: savedChatbot._id });
924
+ //
925
+ // })
926
+
927
+ // Export source chatbot, then import with create=true (parent + intents + linked subagents)
928
+ router.post('/fork/:id_faq_kb', roleChecker.hasRole('admin'), asyncHandler(async (req, res) => {
929
+
930
+ const id_faq_kb = req.params.id_faq_kb;
664
931
  const api_url = process.env.API_URL || configGlobal.apiUrl;
665
- winston.debug("fork --> base_url: " + api_url); // check if correct
666
-
667
- let current_project_id = req.projectid;
932
+ const current_project_id = req.projectid;
933
+ const landing_project_id = req.query.projectid;
934
+ const public = req.query.public;
935
+ const globals = req.query.globals;
936
+ const token = req.headers.authorization;
937
+ const user_id = req.user.id;
938
+
939
+ winston.debug('fork id_faq_kb: ' + id_faq_kb);
940
+ winston.debug("fork --> base_url: " + api_url);
668
941
  winston.debug("current project id: " + current_project_id);
669
-
670
- let landing_project_id = req.query.projectid;
671
- winston.debug("landing project id " + landing_project_id)
672
-
673
- let public = req.query.public;
942
+ winston.debug("landing project id " + landing_project_id);
674
943
  winston.debug("public " + public);
675
-
676
- let globals = req.query.globals;
677
944
  winston.debug("export globals " + globals);
678
945
 
679
- let token = req.headers.authorization;
680
-
681
- let cs = req.app.get('chatbot_service')
946
+ const cs = req.app.get('chatbot_service');
682
947
  let chatbot;
683
948
  try {
684
949
  chatbot = await cs.getBotById(id_faq_kb, public, api_url, chatbot_templates_api_url, token, current_project_id, globals);
685
950
  } catch (err) {
686
- return res.status(500).send({ success: false, error: "Unable to get chatbot to be forked"});
951
+ return res.status(500).send({ success: false, error: "Unable to get chatbot to be forked" });
687
952
  }
688
953
 
689
954
  if (!chatbot) {
690
955
  return res.status(500).send({ success: false, message: "Unable to get chatbot to be forked" });
691
956
  }
692
957
 
693
- if (!globals) {
694
- if (chatbot.attributes) {
695
- delete chatbot.attributes.globals
696
- }
697
- }
698
-
699
- delete chatbot.modified;
700
-
701
- chatbot.template = "empty";
702
-
703
- let savedChatbot
704
- try {
705
- savedChatbot = await cs.createBot(api_url, token, chatbot, landing_project_id);
706
- winston.debug("savedChatbot: ", savedChatbot)
707
- } catch(err) {
708
- winston.error("Error creating new chatbot: ", err);
709
- return res.status(500).send({ success: false, message: "An error occurred during chatbot creation"});
958
+ if (!globals && chatbot.attributes) {
959
+ delete chatbot.attributes.globals;
710
960
  }
711
961
 
712
- if (!savedChatbot) {
713
- return res.status(500).send({ success: false, message: "Unable to create new chatbot" });
714
- }
715
-
716
- let import_result
717
- try {
718
- import_result = await cs.importFaqs(api_url, savedChatbot._id, token, chatbot, landing_project_id);
719
- winston.debug("imported: ", import_result);
720
- } catch(err) {
721
- winston.error("Error importing intents on new chatbot: ", err);
722
- return res.status(500).send({ success: false, message: "Error importing intents in the chatbot"});
723
- }
962
+ delete chatbot.modified;
724
963
 
725
- if (import_result?.success == false) {
726
- return res.status(500).send({ success: false, message: "Unable to import intents in the new chatbot" });
727
- }
964
+ // Publish passes skipSubagents so actions keep the development subagent id.
965
+ // A plain fork still clones subagents into the copy.
966
+ const skipSubagents = req.query.skipSubagents === 'true';
967
+
968
+ const savedChatbot = await chatbotController.import(
969
+ chatbot,
970
+ landing_project_id,
971
+ null,
972
+ true,
973
+ false,
974
+ false,
975
+ user_id,
976
+ skipSubagents
977
+ );
728
978
 
729
979
  if (req.query.for_publish !== 'true') {
730
980
  botEvent.emit('faqbot.created', { req, chatbot: savedChatbot, id_project: landing_project_id });
731
981
  }
732
982
 
733
983
  return res.status(200).send({ message: "Chatbot forked successfully", bot_id: savedChatbot._id });
984
+ }))
734
985
 
735
- })
986
+ router.post('/importjson/:id_faq_kb', roleChecker.hasRole('admin'), upload.single('uploadFile'), asyncHandler(async (req, res) => {
736
987
 
737
- router.post('/importjson/:id_faq_kb', roleChecker.hasRole('admin'), upload.single('uploadFile'), async (req, res) => {
738
-
739
- let chatbot_id = req.params.id_faq_kb;
740
- let id_project = req.projectid;
741
-
742
- winston.debug('import on id_faq_kb: ' + chatbot_id);
743
-
744
- winston.debug('import with option create: ' + req.query.create);
745
- winston.debug('import with option replace: ' + req.query.replace);
746
- winston.debug('import with option overwrite: ' + req.query.overwrite);
988
+ const chatbot_id = req.params.id_faq_kb;
989
+ const id_project = req.projectid;
990
+ const create = req.query.create === 'true';
991
+ const replace = req.query.replace === 'true';
992
+ const overwrite = req.query.overwrite === 'true';
993
+ const user_id = req.user.id;
747
994
 
748
- let json_string;
749
- let json;
995
+ let json, json_string;
750
996
  if (req.file) {
751
997
  json_string = req.file.buffer.toString('utf-8');
752
998
  json = JSON.parse(json_string);
@@ -754,313 +1000,372 @@ router.post('/importjson/:id_faq_kb', roleChecker.hasRole('admin'), upload.singl
754
1000
  json = req.body;
755
1001
  }
756
1002
 
757
- winston.debug("json source " + json_string)
758
-
759
- // ****************************
760
- // **** CREATE TRUE option ****
761
- // ****************************
762
- if (req.query.create === 'true') {
763
- // if (json.subtype && (json.subtype === 'webhook' || json.subtype === 'copilot')) {
764
- // json.template = 'empty';
765
- // }
766
- json.template = 'empty';
767
- let savedChatbot = await faqService.create(req.projectid, req.user.id, json)
768
- .catch((err) => {
769
- winston.error("Error creating new chatbot")
770
- return res.status(400).send({ succes: false, message: "Error creatings new chatbot", error: err })
771
- })
772
-
773
- // Edit attributes.rules
774
- let attributes = json.attributes;
775
- if (attributes &&
776
- attributes.rules &&
777
- attributes.rules.length > 0) {
778
-
779
- await attributes.rules.forEach((rule) => {
780
- if (rule.do &&
781
- rule.do[0] &&
782
- rule.do[0].message &&
783
- rule.do[0].message.participants &&
784
- rule.do[0].message.participants[0]) {
785
- rule.do[0].message.participants[0] = "bot_" + savedChatbot._id
786
- winston.debug("attributes rule new participant: ", rule.do[0].message.participants[0])
787
- }
788
- })
789
- }
790
-
791
- let chatbot_edited = { attributes: attributes };
792
-
793
- let updatedChatbot = await Faq_kb.findByIdAndUpdate(savedChatbot._id, chatbot_edited, { new: true }).catch((err) => {
794
- winston.error("Error updating chatbot attributes: ", err);
795
- winston.debug("Skip Error updating chatbot attributes: ", err);
796
- // return res.status(400).send({ success: false, message: "Error updating chatbot attributes", error: err })
797
- })
798
-
799
- botEvent.emit('faqbot.create', savedChatbot);
800
- botEvent.emit('faqbot.created', { req, chatbot: savedChatbot });
801
-
802
- if (json.intents) {
803
-
804
- json.intents.forEach(async (intent) => {
805
-
806
- let new_faq = {
807
- id_faq_kb: savedChatbot._id,
808
- id_project: req.projectid,
809
- createdBy: req.user.id,
810
- intent_display_name: intent.intent_display_name,
811
- intent_id: intent.intent_id,
812
- question: intent.question,
813
- answer: intent.answer,
814
- reply: intent.reply,
815
- form: intent.form,
816
- enabled: intent.enabled,
817
- webhook_enabled: intent.webhook_enabled,
818
- language: intent.language,
819
- actions: intent.actions,
820
- attributes: intent.attributes
821
- }
822
-
823
- let faq = await Faq.create(new_faq).catch((err) => {
824
- if (err.code == 11000) { // should never happen
825
- winston.error("Duplicate intent_display_name.");
826
- winston.debug("Skip duplicated intent_display_name"); // Don't stop the flow
827
- } else {
828
- winston.error("Error saving new faq: ", err)
829
- }
830
- })
831
-
832
- if (faq) {
833
- winston.debug("new intent created")
834
- faqBotEvent.emit('faq.create', faq);
835
- }
836
- })
837
- }
838
-
839
- if (updatedChatbot) {
840
- return res.status(200).send(updatedChatbot)
841
- } else {
842
- return res.status(200).send(savedChatbot)
843
- }
844
- }
845
- // *****************************
846
- // **** CREATE FALSE option ****
847
- // *****************************
848
- else {
849
-
850
- if (!chatbot_id) {
851
- return res.status(400).send({ success: false, message: "With replace or overwrite option a id_faq_kb must be provided" })
852
- }
853
-
854
- let chatbot = await Faq_kb.findOne({ _id: chatbot_id, id_project: id_project }).catch((err) => {
855
- winston.error("Error finding chatbot with id " + chatbot_id);
856
- return res.status(404).send({ success: false, message: "Error finding chatbot with id " + chatbot_id, error: err });
857
- })
858
-
859
- if (!chatbot) {
860
- winston.error("Chatbot not found with id " + chatbot_id + " for project " + id_project);
861
- return res.status(404).send({ success: false, message: "Chatbot not found with id " + chatbot_id + " for project " + id_project });
862
- }
863
-
864
- if (json.webhook_enabled) {
865
- chatbot.webhook_enabled = json.webhook_enabled;
866
- }
867
- if (json.webhook_url) {
868
- chatbot.webhook_url = json.webhook_url;
869
- }
870
- if (json.language) {
871
- chatbot.language = json.language;
872
- }
873
- if (json.name) {
874
- chatbot.name = json.name;
875
- }
876
- if (json.description) {
877
- chatbot.description = json.description;
878
- }
879
-
880
- if (json.attributes) {
881
- let attributes = json.attributes;
882
- if (attributes.rules &&
883
- attributes.rules.length > 0) {
884
- await attributes.rules.forEach((rule) => {
885
- if (rule.do &&
886
- rule.do[0] &&
887
- rule.do[0].message &&
888
- rule.do[0].message.participants &&
889
- rule.do[0].message.participants[0]) {
890
- rule.do[0].message.participants[0] = "bot_" + chatbot._id
891
- winston.debug("attributes rule new participant: " + rule.do[0].message.participants[0])
892
- }
893
- })
894
- chatbot.attributes = json.attributes;
895
- }
896
- }
897
-
898
- let updatedChatbot = await Faq_kb.findByIdAndUpdate(chatbot._id, chatbot, { new: true }).catch((err) => {
899
- winston.error("Error updating chatbot");
900
- return res.status(400).send({ success: false, message: "Error updating chatbot", error: err })
901
- })
902
-
903
- botEvent.emit('faqbot.update', updatedChatbot);
904
-
905
- // *****************************
906
- // **** REPLACE TRUE option ****
907
- // *****************************
908
- if (req.query.replace === 'true') {
909
- let result = await Faq.deleteMany({ id_faq_kb: chatbot._id }).catch((err) => {
910
- winston.error("Unable to delete all existing faqs with id_faq_kb " + chatbot._id);
911
- })
912
-
913
- winston.verbose("All faq for chatbot " + chatbot._id + " deleted successfully")
914
- winston.debug("DeleteMany faqs result ", result);
915
- }
916
-
917
- if (json.intents) {
918
- await json.intents.forEach(async (intent) => {
919
-
920
- let new_faq = {
921
- id_faq_kb: updatedChatbot._id,
922
- id_project: req.projectid,
923
- createdBy: req.user.id,
924
- intent_display_name: intent.intent_display_name,
925
- intent_id: intent.intent_id,
926
- question: intent.question,
927
- answer: intent.answer,
928
- reply: intent.reply,
929
- form: intent.form,
930
- enabled: intent.enabled,
931
- webhook_enabled: intent.webhook_enabled,
932
- language: intent.language,
933
- actions: intent.actions,
934
- attributes: intent.attributes
935
- }
936
-
937
- // *******************************
938
- // **** OVERWRITE TRUE option ****
939
- // *******************************
940
- if (req.query.overwrite == "true") {
941
-
942
- let savingResult = await Faq.findOneAndUpdate({ id_faq_kb: chatbot._id, intent_display_name: intent.intent_display_name }, new_faq, { new: true, upsert: true, rawResult: true }).catch((err) => {
943
- winston.error("Unable to create faq: ", err);
944
- })
945
-
946
- if (savingResult) {
947
- if (savingResult.lastErrorObject.updatedExisting === true) {
948
- winston.verbose("updated existing intent")
949
- faqBotEvent.emit('faq.update', savingResult.value);
950
- } else {
951
- winston.verbose("new intent created")
952
- faqBotEvent.emit('faq.create', savingResult.value);
953
- }
954
- }
955
-
956
- // ********************************
957
- // **** OVERWRITE FALSE option ****
958
- // ********************************
959
- } else {
960
-
961
- let faq = await Faq.create(new_faq).catch((err) => {
962
- if (err.code == 11000) {
963
- winston.error("Duplicate intent_display_name.");
964
- winston.verbose("Skip duplicated intent_display_name");
965
- } else {
966
- winston.error("Error creating new intent: ", err);
967
- }
968
- })
969
-
970
- if (faq) {
971
- winston.debug("new intent created: ", faq)
972
- faqBotEvent.emit('faq.create', faq);
973
- }
974
- }
975
- })
976
- }
977
-
978
- if (updatedChatbot) {
979
- return res.send(updatedChatbot);
980
- } else {
981
- return res.send(chatbot);
982
- }
1003
+ const chatbot = await chatbotController.import(json, id_project, chatbot_id, create, replace, overwrite, user_id);
1004
+ if (create) {
1005
+ botEvent.emit('faqbot.created', { req, chatbot });
983
1006
  }
984
- })
985
-
986
- router.get('/exportjson/:id_faq_kb', roleChecker.hasRole('admin'), (req, res) => {
987
-
988
- winston.debug("exporting bot...")
1007
+ return res.status(200).send(chatbot);
1008
+ }))
1009
+
1010
+ // router.post('/importjson/:id_faq_kb', roleChecker.hasRole('admin'), upload.single('uploadFile'), async (req, res) => {
1011
+
1012
+ // let chatbot_id = req.params.id_faq_kb;
1013
+ // let id_project = req.projectid;
1014
+
1015
+ // winston.debug('import on id_faq_kb: ' + chatbot_id);
1016
+
1017
+ // winston.debug('import with option create: ' + req.query.create);
1018
+ // winston.debug('import with option replace: ' + req.query.replace);
1019
+ // winston.debug('import with option overwrite: ' + req.query.overwrite);
1020
+
1021
+ // let json_string;
1022
+ // let json;
1023
+ // if (req.file) {
1024
+ // json_string = req.file.buffer.toString('utf-8');
1025
+ // json = JSON.parse(json_string);
1026
+ // } else {
1027
+ // json = req.body;
1028
+ // }
1029
+
1030
+ // winston.debug("json source " + json_string)
1031
+
1032
+ // // ****************************
1033
+ // // **** CREATE TRUE option ****
1034
+ // // ****************************
1035
+ // if (req.query.create === 'true') {
1036
+ // // if (json.subtype && (json.subtype === 'webhook' || json.subtype === 'copilot')) {
1037
+ // // json.template = 'empty';
1038
+ // // }
1039
+ // json.template = 'empty';
1040
+ // let savedChatbot = await faqService.create(req.projectid, req.user.id, json)
1041
+ // .catch((err) => {
1042
+ // winston.error("Error creating new chatbot")
1043
+ // return res.status(400).send({ succes: false, message: "Error creatings new chatbot", error: err })
1044
+ // })
1045
+
1046
+ // // Edit attributes.rules
1047
+ // let attributes = json.attributes;
1048
+ // if (attributes &&
1049
+ // attributes.rules &&
1050
+ // attributes.rules.length > 0) {
1051
+
1052
+ // await attributes.rules.forEach((rule) => {
1053
+ // if (rule.do &&
1054
+ // rule.do[0] &&
1055
+ // rule.do[0].message &&
1056
+ // rule.do[0].message.participants &&
1057
+ // rule.do[0].message.participants[0]) {
1058
+ // rule.do[0].message.participants[0] = "bot_" + savedChatbot._id
1059
+ // winston.debug("attributes rule new participant: ", rule.do[0].message.participants[0])
1060
+ // }
1061
+ // })
1062
+ // }
1063
+
1064
+ // let chatbot_edited = { attributes: attributes };
1065
+
1066
+ // let updatedChatbot = await Faq_kb.findByIdAndUpdate(savedChatbot._id, chatbot_edited, { new: true }).catch((err) => {
1067
+ // winston.error("Error updating chatbot attributes: ", err);
1068
+ // winston.debug("Skip Error updating chatbot attributes: ", err);
1069
+ // // return res.status(400).send({ success: false, message: "Error updating chatbot attributes", error: err })
1070
+ // })
1071
+
1072
+ // botEvent.emit('faqbot.create', savedChatbot);
1073
+
1074
+ // if (json.intents) {
1075
+
1076
+ // json.intents.forEach(async (intent) => {
1077
+
1078
+ // let new_faq = {
1079
+ // id_faq_kb: savedChatbot._id,
1080
+ // id_project: req.projectid,
1081
+ // createdBy: req.user.id,
1082
+ // intent_display_name: intent.intent_display_name,
1083
+ // intent_id: intent.intent_id,
1084
+ // question: intent.question,
1085
+ // answer: intent.answer,
1086
+ // reply: intent.reply,
1087
+ // form: intent.form,
1088
+ // enabled: intent.enabled,
1089
+ // webhook_enabled: intent.webhook_enabled,
1090
+ // language: intent.language,
1091
+ // actions: intent.actions,
1092
+ // attributes: intent.attributes
1093
+ // }
1094
+
1095
+ // let faq = await Faq.create(new_faq).catch((err) => {
1096
+ // if (err.code == 11000) { // should never happen
1097
+ // winston.error("Duplicate intent_display_name.");
1098
+ // winston.debug("Skip duplicated intent_display_name"); // Don't stop the flow
1099
+ // } else {
1100
+ // winston.error("Error saving new faq: ", err)
1101
+ // }
1102
+ // })
1103
+
1104
+ // if (faq) {
1105
+ // winston.debug("new intent created")
1106
+ // faqBotEvent.emit('faq.create', faq);
1107
+ // }
1108
+ // })
1109
+ // }
1110
+
1111
+ // if (updatedChatbot) {
1112
+ // return res.status(200).send(updatedChatbot)
1113
+ // } else {
1114
+ // return res.status(200).send(savedChatbot)
1115
+ // }
1116
+ // }
1117
+ // // *****************************
1118
+ // // **** CREATE FALSE option ****
1119
+ // // *****************************
1120
+ // else {
1121
+
1122
+ // if (!chatbot_id) {
1123
+ // return res.status(400).send({ success: false, message: "With replace or overwrite option a id_faq_kb must be provided" })
1124
+ // }
1125
+
1126
+ // let chatbot = await Faq_kb.findOne({ _id: chatbot_id, id_project: id_project }).catch((err) => {
1127
+ // winston.error("Error finding chatbot with id " + chatbot_id);
1128
+ // return res.status(404).send({ success: false, message: "Error finding chatbot with id " + chatbot_id, error: err });
1129
+ // })
1130
+
1131
+ // if (!chatbot) {
1132
+ // winston.error("Chatbot not found with id " + chatbot_id + " for project " + id_project);
1133
+ // return res.status(404).send({ success: false, message: "Chatbot not found with id " + chatbot_id + " for project " + id_project });
1134
+ // }
1135
+
1136
+ // if (json.webhook_enabled) {
1137
+ // chatbot.webhook_enabled = json.webhook_enabled;
1138
+ // }
1139
+ // if (json.webhook_url) {
1140
+ // chatbot.webhook_url = json.webhook_url;
1141
+ // }
1142
+ // if (json.language) {
1143
+ // chatbot.language = json.language;
1144
+ // }
1145
+ // if (json.name) {
1146
+ // chatbot.name = json.name;
1147
+ // }
1148
+ // if (json.description) {
1149
+ // chatbot.description = json.description;
1150
+ // }
1151
+
1152
+ // if (json.attributes) {
1153
+ // let attributes = json.attributes;
1154
+ // if (attributes.rules &&
1155
+ // attributes.rules.length > 0) {
1156
+ // await attributes.rules.forEach((rule) => {
1157
+ // if (rule.do &&
1158
+ // rule.do[0] &&
1159
+ // rule.do[0].message &&
1160
+ // rule.do[0].message.participants &&
1161
+ // rule.do[0].message.participants[0]) {
1162
+ // rule.do[0].message.participants[0] = "bot_" + chatbot._id
1163
+ // winston.debug("attributes rule new participant: " + rule.do[0].message.participants[0])
1164
+ // }
1165
+ // })
1166
+ // chatbot.attributes = json.attributes;
1167
+ // }
1168
+ // }
1169
+
1170
+ // let updatedChatbot = await Faq_kb.findByIdAndUpdate(chatbot._id, chatbot, { new: true }).catch((err) => {
1171
+ // winston.error("Error updating chatbot");
1172
+ // return res.status(400).send({ success: false, message: "Error updating chatbot", error: err })
1173
+ // })
1174
+
1175
+ // botEvent.emit('faqbot.update', updatedChatbot);
1176
+
1177
+ // // *****************************
1178
+ // // **** REPLACE TRUE option ****
1179
+ // // *****************************
1180
+ // if (req.query.replace === 'true') {
1181
+ // let result = await Faq.deleteMany({ id_faq_kb: chatbot._id }).catch((err) => {
1182
+ // winston.error("Unable to delete all existing faqs with id_faq_kb " + chatbot._id);
1183
+ // })
1184
+
1185
+ // winston.verbose("All faq for chatbot " + chatbot._id + " deleted successfully")
1186
+ // winston.debug("DeleteMany faqs result ", result);
1187
+ // }
1188
+
1189
+ // if (json.intents) {
1190
+ // await json.intents.forEach(async (intent) => {
1191
+
1192
+ // let new_faq = {
1193
+ // id_faq_kb: updatedChatbot._id,
1194
+ // id_project: req.projectid,
1195
+ // createdBy: req.user.id,
1196
+ // intent_display_name: intent.intent_display_name,
1197
+ // intent_id: intent.intent_id,
1198
+ // question: intent.question,
1199
+ // answer: intent.answer,
1200
+ // reply: intent.reply,
1201
+ // form: intent.form,
1202
+ // enabled: intent.enabled,
1203
+ // webhook_enabled: intent.webhook_enabled,
1204
+ // language: intent.language,
1205
+ // actions: intent.actions,
1206
+ // attributes: intent.attributes
1207
+ // }
1208
+
1209
+ // // *******************************
1210
+ // // **** OVERWRITE TRUE option ****
1211
+ // // *******************************
1212
+ // if (req.query.overwrite == "true") {
1213
+
1214
+ // let savingResult = await Faq.findOneAndUpdate({ id_faq_kb: chatbot._id, intent_display_name: intent.intent_display_name }, new_faq, { new: true, upsert: true, rawResult: true }).catch((err) => {
1215
+ // winston.error("Unable to create faq: ", err);
1216
+ // })
1217
+
1218
+ // if (savingResult) {
1219
+ // if (savingResult.lastErrorObject.updatedExisting === true) {
1220
+ // winston.verbose("updated existing intent")
1221
+ // faqBotEvent.emit('faq.update', savingResult.value);
1222
+ // } else {
1223
+ // winston.verbose("new intent created")
1224
+ // faqBotEvent.emit('faq.create', savingResult.value);
1225
+ // }
1226
+ // }
1227
+
1228
+ // // ********************************
1229
+ // // **** OVERWRITE FALSE option ****
1230
+ // // ********************************
1231
+ // } else {
1232
+
1233
+ // let faq = await Faq.create(new_faq).catch((err) => {
1234
+ // if (err.code == 11000) {
1235
+ // winston.error("Duplicate intent_display_name.");
1236
+ // winston.verbose("Skip duplicated intent_display_name");
1237
+ // } else {
1238
+ // winston.error("Error creating new intent: ", err);
1239
+ // }
1240
+ // })
1241
+
1242
+ // if (faq) {
1243
+ // winston.debug("new intent created: ", faq)
1244
+ // faqBotEvent.emit('faq.create', faq);
1245
+ // }
1246
+ // }
1247
+ // })
1248
+ // }
1249
+
1250
+ // if (updatedChatbot) {
1251
+ // return res.send(updatedChatbot);
1252
+ // } else {
1253
+ // return res.send(chatbot);
1254
+ // }
1255
+ // }
1256
+ // })
1257
+
1258
+ router.get('/exportjson/:id_faq_kb', roleChecker.hasRole('admin'), asyncHandler(async (req, res) => {
989
1259
 
990
1260
  const chatbot_id = req.params.id_faq_kb;
991
1261
  const id_project = req.projectid;
1262
+ const exportGlobals = req.query.globals === 'true';
1263
+ const intentsOnly = req.query.intentsOnly === 'true';
992
1264
 
993
- Faq_kb.findOne({ _id: chatbot_id }, (err, faq_kb) => {
994
- if (err) {
995
- winston.error('GET FAQ-KB ERROR ', err)
996
- return res.status(500).send({ success: false, msg: 'Error getting bot.' });
997
- }
998
-
999
- if (!faq_kb) {
1000
- return res.status(404).send({ success: false, msg: "Chatbot not found with id " + chatbot_id });
1001
- }
1002
-
1003
- const isPublic = faq_kb.public === true;
1004
- const isOwner = faq_kb.id_project === id_project;
1265
+ const chatbot = await chatbotController.getChatbotById(chatbot_id);
1005
1266
 
1006
- if (!isPublic && !isOwner) {
1007
- return res.status(403).send({
1008
- success: false,
1009
- msg: "Chatbot not found with id " + chatbot_id + " for project " + id_project
1010
- });
1011
- }
1267
+ if (!chatbotController.canAccess(chatbot, id_project)) {
1268
+ throw new AppError(403, `Unauthorized to access chatbot with id ${chatbot_id} for project ${id_project}`);
1269
+ }
1012
1270
 
1013
- winston.debug('FAQ-KB: ', faq_kb)
1271
+ let intents = await intentsController.getIntents(chatbot_id);
1014
1272
 
1015
- faqService.getAll(chatbot_id).then((faqs) => {
1273
+ if (!exportGlobals) {
1274
+ winston.verbose("Delete globals from attributes")
1275
+ delete chatbot.attributes?.globals;
1276
+ }
1016
1277
 
1017
- // delete from exclude map intent_id
1018
- const intents = faqs.map(({ _id, id_project, topic, status, chatbot_id, createdBy, createdAt, updatedAt, __v, ...keepAttrs }) => keepAttrs)
1278
+ let subagents = await chatbotController.findSubagents(chatbot_id, id_project);
1279
+ await Promise.all(subagents.map(async (subagent) => {
1280
+ subagent.intents = await intentsController.getIntents(subagent._id);
1281
+ }));
1019
1282
 
1020
- if (!req.query.globals) {
1021
- winston.verbose("Delete globals from attributes!")
1022
- if (faq_kb.attributes) {
1023
- delete faq_kb.attributes.globals;
1024
- }
1025
- }
1283
+ let exportData = chatbotController.export(chatbot, intents, intentsOnly, subagents);
1284
+ let filename = intentsOnly ? "intents.json" : "bot.json";
1285
+ res.set({ "Content-Disposition": `attachment; filename="${filename}"` });
1286
+ return res.send(exportData);
1026
1287
 
1027
- let json = {
1028
- webhook_enabled: faq_kb.webhook_enabled,
1029
- webhook_url: faq_kb.webhook_url,
1030
- language: faq_kb.language,
1031
- name: faq_kb.name,
1032
- slug: faq_kb.slug,
1033
- type: faq_kb.type,
1034
- subtype: faq_kb.subtype,
1035
- description: faq_kb.description,
1036
- attributes: faq_kb.attributes,
1037
- intents: intents
1038
- }
1288
+ }))
1039
1289
 
1040
- if (req.query.intentsOnly == 'true') {
1041
- let intents_obj = {
1042
- intents: intents
1043
- }
1044
- let intents_string = JSON.stringify(intents_obj);
1045
- res.set({ "Content-Disposition": "attachment; filename=\"intents.json\"" });
1046
- return res.send(intents_string);
1290
+ // DEPRECATED
1291
+ // router.get('/exportjson/:id_faq_kb', roleChecker.hasRole('admin'), (req, res) => {
1047
1292
 
1048
- } else {
1293
+ // winston.debug("exporting bot...")
1049
1294
 
1050
- // if (req.query.file == "false") {
1051
- // return res.status(200).send(json);
1052
- // }
1053
- let json_string = JSON.stringify(json);
1054
- res.set({ "Content-Disposition": "attachment; filename=\"bot.json\"" });
1055
- return res.send(json_string);
1056
- }
1295
+ // const chatbot_id = req.params.id_faq_kb;
1296
+ // const id_project = req.projectid;
1057
1297
 
1058
- }).catch((err) => {
1059
- winston.error('GET FAQ ERROR: ', err)
1060
- return res.status(500).send({ success: false, msg: 'Error getting faqs.' });
1061
- })
1062
- })
1063
- })
1298
+ // Faq_kb.findOne({ _id: chatbot_id }, (err, faq_kb) => {
1299
+ // if (err) {
1300
+ // winston.error('GET FAQ-KB ERROR ', err)
1301
+ // return res.status(500).send({ success: false, msg: 'Error getting bot.' });
1302
+ // }
1303
+
1304
+ // if (!faq_kb) {
1305
+ // return res.status(404).send({ success: false, msg: "Chatbot not found with id " + chatbot_id });
1306
+ // }
1307
+
1308
+ // const isPublic = faq_kb.public === true;
1309
+ // const isOwner = faq_kb.id_project === id_project;
1310
+
1311
+ // if (!isPublic && !isOwner) {
1312
+ // return res.status(403).send({
1313
+ // success: false,
1314
+ // msg: "Chatbot not found with id " + chatbot_id + " for project " + id_project
1315
+ // });
1316
+ // }
1317
+
1318
+ // winston.debug('FAQ-KB: ', faq_kb)
1319
+
1320
+ // faqService.getAll(chatbot_id).then((faqs) => {
1321
+
1322
+ // // delete from exclude map intent_id
1323
+ // const intents = faqs.map(({ _id, id_project, topic, status, chatbot_id, createdBy, createdAt, updatedAt, __v, ...keepAttrs }) => keepAttrs)
1324
+
1325
+ // if (!req.query.globals) {
1326
+ // winston.verbose("Delete globals from attributes!")
1327
+ // if (faq_kb.attributes) {
1328
+ // delete faq_kb.attributes.globals;
1329
+ // }
1330
+ // }
1331
+
1332
+ // let json = {
1333
+ // webhook_enabled: faq_kb.webhook_enabled,
1334
+ // webhook_url: faq_kb.webhook_url,
1335
+ // language: faq_kb.language,
1336
+ // name: faq_kb.name,
1337
+ // slug: faq_kb.slug,
1338
+ // type: faq_kb.type,
1339
+ // subtype: faq_kb.subtype,
1340
+ // description: faq_kb.description,
1341
+ // attributes: faq_kb.attributes,
1342
+ // intents: intents
1343
+ // }
1344
+
1345
+ // if (req.query.intentsOnly == 'true') {
1346
+ // let intents_obj = {
1347
+ // intents: intents
1348
+ // }
1349
+ // let intents_string = JSON.stringify(intents_obj);
1350
+ // res.set({ "Content-Disposition": "attachment; filename=\"intents.json\"" });
1351
+ // return res.send(intents_string);
1352
+
1353
+ // } else {
1354
+
1355
+ // // if (req.query.file == "false") {
1356
+ // // return res.status(200).send(json);
1357
+ // // }
1358
+ // let json_string = JSON.stringify(json);
1359
+ // res.set({ "Content-Disposition": "attachment; filename=\"bot.json\"" });
1360
+ // return res.send(json_string);
1361
+ // }
1362
+
1363
+ // }).catch((err) => {
1364
+ // winston.error('GET FAQ ERROR: ', err)
1365
+ // return res.status(500).send({ success: false, msg: 'Error getting faqs.' });
1366
+ // })
1367
+ // })
1368
+ // })
1064
1369
 
1065
1370
  router.post('/:faq_kbid/training', roleChecker.hasRole('admin'), function (req, res) {
1066
1371