@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
@@ -0,0 +1,659 @@
1
+ const Chatbot = require("../models/faq_kb");
2
+ const Faq = require("../models/faq");
3
+ const faqService = require("../services/faqService");
4
+ const AppError = require("../utils/AppError");
5
+ const intentsController = require("./intents.controller");
6
+ const botEvent = require("../event/botEvent");
7
+ const winston = require("../config/winston");
8
+ const v3FlowService = require("../services/v3FlowService");
9
+
10
+ const IMPORT_OVERRIDE_FIELDS = [
11
+ "webhook_enabled",
12
+ "webhook_url",
13
+ "language",
14
+ "name",
15
+ "description",
16
+ "type",
17
+ "subtype"
18
+ ];
19
+
20
+ class ChatbotController {
21
+
22
+ constructor() { }
23
+
24
+ canAccess(chatbot, id_project) {
25
+ const isPublic = chatbot.public === true;
26
+ const isOwner = chatbot.id_project === id_project;
27
+ if (!isPublic && !isOwner) {
28
+ return false
29
+ }
30
+ return true;
31
+ }
32
+
33
+ _sanitizeIntent(intent) {
34
+ const {
35
+ _id, id_project, id_faq_kb, topic, status, chatbot_id,
36
+ createdBy, createdAt, updatedAt, __v, ...keepAttrs
37
+ } = intent;
38
+ return keepAttrs;
39
+ }
40
+
41
+ _sanitizeSubagent(subagent) {
42
+ return {
43
+ // Keep source _id so import can remap intents.actions[].botId
44
+ // to the newly created subagent. It is stripped before insert.
45
+ _id: subagent._id ? subagent._id.toString() : undefined,
46
+ webhook_enabled: subagent.webhook_enabled,
47
+ webhook_url: subagent.webhook_url,
48
+ language: subagent.language,
49
+ name: subagent.name,
50
+ slug: subagent.slug,
51
+ type: subagent.type,
52
+ subtype: subagent.subtype,
53
+ description: subagent.description,
54
+ attributes: subagent.attributes,
55
+ ...this._v3Fields(subagent),
56
+ intents: (subagent.intents || []).map((intent) => this._sanitizeIntent(intent))
57
+ };
58
+ }
59
+
60
+ /** `ds_version` and `connectors` of a V3 agent, for the export; nothing for any other agent. */
61
+ _v3Fields(chatbot) {
62
+ const fields = {};
63
+ if (chatbot && chatbot.ds_version !== undefined && chatbot.ds_version !== null) {
64
+ fields.ds_version = chatbot.ds_version;
65
+ }
66
+ if (chatbot && chatbot.connectors !== undefined && chatbot.connectors !== null) {
67
+ fields.connectors = chatbot.connectors;
68
+ }
69
+ return fields;
70
+ }
71
+
72
+ export(chatbot, intents, intentsOnly, subagents) {
73
+ const sanitizedIntents = (intents || []).map((intent) => this._sanitizeIntent(intent));
74
+
75
+ if (intentsOnly) {
76
+ return JSON.stringify({ intents: sanitizedIntents });
77
+ }
78
+
79
+ return JSON.stringify({
80
+ webhook_enabled: chatbot.webhook_enabled,
81
+ webhook_url: chatbot.webhook_url,
82
+ language: chatbot.language,
83
+ name: chatbot.name,
84
+ slug: chatbot.slug,
85
+ type: chatbot.type,
86
+ subtype: chatbot.subtype,
87
+ description: chatbot.description,
88
+ attributes: chatbot.attributes,
89
+ ...this._v3Fields(chatbot),
90
+ parent_id: chatbot.parent_id,
91
+ intents: sanitizedIntents,
92
+ subagents: (subagents || []).map((subagent) => this._sanitizeSubagent(subagent))
93
+ })
94
+ }
95
+
96
+ _rewriteRulesParticipants(attributes, chatbot_id) {
97
+ if (!attributes?.rules?.length) {
98
+ return attributes;
99
+ }
100
+ for (const rule of attributes.rules) {
101
+ if (rule.do?.[0]?.message?.participants?.[0]) {
102
+ rule.do[0].message.participants[0] = "bot_" + chatbot_id;
103
+ }
104
+ }
105
+ return attributes;
106
+ }
107
+
108
+ _sourceId(doc) {
109
+ if (!doc || doc._id == null) {
110
+ return null;
111
+ }
112
+ return doc._id.toString();
113
+ }
114
+
115
+ _stripImportedIdentity(doc) {
116
+ if (!doc) {
117
+ return;
118
+ }
119
+ delete doc._id;
120
+ delete doc.id_project;
121
+ delete doc.createdBy;
122
+ delete doc.createdAt;
123
+ delete doc.updatedAt;
124
+ delete doc.__v;
125
+ }
126
+
127
+ _collectBotIds(value, botIds = new Set()) {
128
+ if (!value) {
129
+ return botIds;
130
+ }
131
+ if (Array.isArray(value)) {
132
+ value.forEach((item) => this._collectBotIds(item, botIds));
133
+ return botIds;
134
+ }
135
+ if (typeof value !== "object") {
136
+ return botIds;
137
+ }
138
+ if (value.botId != null) {
139
+ botIds.add(value.botId.toString());
140
+ }
141
+ Object.keys(value).forEach((key) => {
142
+ if (key === "botId") {
143
+ return;
144
+ }
145
+ this._collectBotIds(value[key], botIds);
146
+ });
147
+ return botIds;
148
+ }
149
+
150
+ _mappedBotId(botId, idMap) {
151
+ if (botId == null || !idMap) {
152
+ return null;
153
+ }
154
+ const raw = botId.toString();
155
+ if (idMap[raw]) {
156
+ return idMap[raw];
157
+ }
158
+ if (raw.startsWith("bot_")) {
159
+ const mapped = idMap[raw.slice(4)];
160
+ if (mapped) {
161
+ return "bot_" + mapped;
162
+ }
163
+ }
164
+ return null;
165
+ }
166
+
167
+ _rewriteBotIdsInValue(value, idMap) {
168
+ if (!value || !idMap) {
169
+ return value;
170
+ }
171
+ if (Array.isArray(value)) {
172
+ value.forEach((item) => this._rewriteBotIdsInValue(item, idMap));
173
+ return value;
174
+ }
175
+ if (typeof value !== "object") {
176
+ return value;
177
+ }
178
+ if (value.botId != null) {
179
+ const mapped = this._mappedBotId(value.botId, idMap);
180
+ if (mapped) {
181
+ value.botId = mapped;
182
+ }
183
+ }
184
+ Object.keys(value).forEach((key) => {
185
+ if (key === "botId") {
186
+ return;
187
+ }
188
+ this._rewriteBotIdsInValue(value[key], idMap);
189
+ });
190
+ return value;
191
+ }
192
+
193
+ _rewriteBotIdsInIntents(intents, idMap) {
194
+ (intents || []).forEach((intent) => {
195
+ this._rewriteBotIdsInValue(intent.actions, idMap);
196
+ this._rewriteBotIdsInValue(intent.attributes, idMap);
197
+ });
198
+ }
199
+
200
+ async _fillBotIdMapFromSourceBots(intents, idMap, nameMap) {
201
+ const botIds = this._collectBotIds(intents);
202
+ for (const botId of botIds) {
203
+ const lookupId = botId.startsWith("bot_") ? botId.slice(4) : botId;
204
+ if (idMap[lookupId] || idMap[botId]) {
205
+ continue;
206
+ }
207
+ try {
208
+ const sourceBot = await Chatbot.findById(lookupId).lean();
209
+ if (sourceBot?.name && nameMap[sourceBot.name]) {
210
+ idMap[lookupId] = nameMap[sourceBot.name];
211
+ }
212
+ } catch (err) {
213
+ winston.debug("(ChatbotController) skip botId remap for " + botId);
214
+ }
215
+ }
216
+ }
217
+
218
+ _buildSubagentPayload(subagentJson, parent_id, id_project) {
219
+ this._stripImportedIdentity(subagentJson);
220
+ return {
221
+ name: subagentJson.name,
222
+ description: subagentJson.description,
223
+ type: subagentJson.type || "tilebot",
224
+ subtype: "subagent",
225
+ language: subagentJson.language,
226
+ webhook_url: subagentJson.webhook_url,
227
+ webhook_enabled: subagentJson.webhook_enabled,
228
+ attributes: subagentJson.attributes,
229
+ ds_version: subagentJson.ds_version,
230
+ connectors: subagentJson.connectors,
231
+ parent_id: parent_id.toString(),
232
+ id_project: id_project.toString(),
233
+ template: "empty"
234
+ };
235
+ }
236
+
237
+ async findSubagents(chatbot_id, id_project, select) {
238
+ try {
239
+ let query = Chatbot.find({
240
+ id_project: id_project,
241
+ parent_id: chatbot_id,
242
+ trashed: { $in: [null, false] }
243
+ });
244
+ if (select) {
245
+ query = query.select(select);
246
+ } else {
247
+ // Whole sub agents (export, restore): a V3 sub agent needs its connectors list too.
248
+ query = query.select('+connectors').sort({ createdAt: -1 });
249
+ }
250
+ return await query.lean().exec();
251
+ } catch (err) {
252
+ winston.error("(ChatbotController) findSubagents error: ", err);
253
+ throw new AppError(500, `Error finding subagents with id ${chatbot_id}`);
254
+ }
255
+
256
+ }
257
+
258
+ async findSubagentCaller(chatbot, id_project) {
259
+ if (!chatbot || !chatbot.parent_id) {
260
+ return null;
261
+ }
262
+
263
+ const chatbotId = chatbot._id.toString();
264
+ const parentId = chatbot.parent_id.toString();
265
+
266
+ let siblingIds;
267
+ try {
268
+ siblingIds = await Chatbot.find({
269
+ id_project: id_project,
270
+ parent_id: parentId,
271
+ _id: { $ne: chatbot._id }
272
+ }).select('_id').lean();
273
+ } catch (err) {
274
+ winston.error("(ChatbotController) findSubagentCaller error getting siblings: ", err);
275
+ throw new AppError(500, "Error checking subagent references");
276
+ }
277
+
278
+ const relatedIds = [parentId, ...siblingIds.map((bot) => bot._id.toString())];
279
+
280
+ const filter = {
281
+ id_project: id_project,
282
+ id_faq_kb: { $in: relatedIds },
283
+ $or: [{ 'actions.botId': chatbotId }]
284
+ };
285
+ if (chatbot.slug) {
286
+ filter.$or.push({ 'actions.botSlug': chatbot.slug });
287
+ }
288
+
289
+ try {
290
+ return await Faq.findOne(filter)
291
+ .select('_id id_faq_kb intent_display_name')
292
+ .lean();
293
+ } catch (err) {
294
+ winston.error("(ChatbotController) findSubagentCaller error: ", err);
295
+ throw new AppError(500, "Error checking subagent references");
296
+ }
297
+ }
298
+
299
+ async deleteSubagents(parent_id, id_project) {
300
+ const subagents = await this.findSubagents(parent_id, id_project, '_id');
301
+ await Promise.all(subagents.map(async (subagent) => {
302
+ await intentsController.deleteByChatbotId(subagent._id);
303
+ await Chatbot.deleteOne({ _id: subagent._id, id_project: id_project });
304
+ }));
305
+ }
306
+
307
+ async assertAreSubagents(id_project, parent_id, chatbot_ids) {
308
+ const parentId = parent_id.toString();
309
+ const idsToCheck = [...new Set(
310
+ (chatbot_ids || [])
311
+ .map((id) => (id == null ? '' : id.toString().trim()))
312
+ .filter((id) => id && id !== parentId)
313
+ )];
314
+
315
+ if (!idsToCheck.length) {
316
+ return true;
317
+ }
318
+
319
+ const subagents = await this.findSubagents(parent_id, id_project, '_id');
320
+ const foundSet = new Set(subagents.map((bot) => bot._id.toString()));
321
+ const invalid = idsToCheck.filter((id) => !foundSet.has(id));
322
+ if (invalid.length > 0) {
323
+ throw new AppError(400, `Subagent IDs are not valid: ${invalid.join(", ")}`);
324
+ }
325
+
326
+ return true;
327
+ }
328
+
329
+ async getChatbot(chatbot_id, id_project) {
330
+ let chatbot;
331
+ try {
332
+ chatbot = await Chatbot.findOne({ _id: chatbot_id, id_project: id_project }).select('+connectors');
333
+ } catch (err) {
334
+ winston.error("(ChatbotController) getChatbot error: ", err);
335
+ throw new AppError(500, `Error finding chatbot with id ${chatbot_id}`);
336
+ }
337
+ if (!chatbot) {
338
+ throw new AppError(404, `Chatbot not found with id ${chatbot_id} for project ${id_project}`);
339
+ }
340
+ return chatbot;
341
+ }
342
+
343
+ async getChatbotById(chatbot_id) {
344
+ let chatbot;
345
+ try {
346
+ chatbot = await Chatbot.findById(chatbot_id).select('+connectors');
347
+ } catch (err) {
348
+ winston.error("(ChatbotController) getChatbotById error: ", err);
349
+ throw new AppError(500, `Error finding chatbot with id ${chatbot_id}`);
350
+ }
351
+ if (!chatbot) {
352
+ throw new AppError(404, `Chatbot not found with id ${chatbot_id}`);
353
+ }
354
+ return chatbot;
355
+ }
356
+
357
+ async _importIntents(chatbot_id, id_project, user_id, intents, overwrite) {
358
+ if (!intents?.length) {
359
+ return;
360
+ }
361
+ if (overwrite) {
362
+ await intentsController.upsertMany(chatbot_id, id_project, user_id, intents);
363
+ } else {
364
+ await intentsController.createMany(
365
+ chatbot_id,
366
+ id_project,
367
+ user_id,
368
+ intents,
369
+ { skipDuplicates: true }
370
+ );
371
+ }
372
+ }
373
+
374
+ async _createSubagent(id_project, user_id, payload) {
375
+ let subagent;
376
+ try {
377
+ subagent = await faqService.create(id_project, user_id, payload);
378
+ } catch (err) {
379
+ winston.error("(ChatbotController) create subagent error: ", err);
380
+ throw new AppError(400, "Error creating subagent");
381
+ }
382
+
383
+ if (payload.attributes) {
384
+ this._rewriteRulesParticipants(payload.attributes, subagent._id);
385
+ subagent = await this.updatedChatbot(subagent._id, id_project, { attributes: payload.attributes });
386
+ }
387
+
388
+ return subagent;
389
+ }
390
+
391
+ async _updateSubagent(subagent, payload, id_project) {
392
+ subagent.id_project = id_project.toString();
393
+ for (const field of IMPORT_OVERRIDE_FIELDS) {
394
+ if (payload[field] !== undefined && payload[field] !== null) {
395
+ subagent[field] = payload[field];
396
+ }
397
+ }
398
+
399
+ if (payload.attributes) {
400
+ this._rewriteRulesParticipants(payload.attributes, subagent._id);
401
+ subagent.attributes = payload.attributes;
402
+ }
403
+ this._applyV3Fields(subagent, payload);
404
+
405
+ try {
406
+ subagent = await Chatbot.findByIdAndUpdate(subagent._id, subagent, { new: true });
407
+ } catch (err) {
408
+ winston.error("(ChatbotController) update subagent error: ", err);
409
+ throw new AppError(400, "Error updating subagent");
410
+ }
411
+
412
+ botEvent.emit("faqbot.update", subagent);
413
+ return subagent;
414
+ }
415
+
416
+ async _importSubagents(parent, subagentsJson, id_project, user_id, replace, overwrite, idMap, nameMap) {
417
+ if (replace) {
418
+ await this.deleteSubagents(parent._id, id_project);
419
+ }
420
+
421
+ const imported = [];
422
+ if (!subagentsJson?.length) {
423
+ return imported;
424
+ }
425
+
426
+ for (const subagentJson of subagentsJson) {
427
+ const sourceId = this._sourceId(subagentJson);
428
+ const payload = this._buildSubagentPayload(subagentJson, parent._id, id_project);
429
+ let subagent = await Chatbot.findOne({
430
+ id_project: id_project,
431
+ parent_id: parent._id.toString(),
432
+ name: subagentJson.name
433
+ });
434
+
435
+ if (overwrite) {
436
+ if (subagent) {
437
+ subagent = await this._updateSubagent(subagent, payload, id_project);
438
+ } else {
439
+ subagent = await this._createSubagent(id_project, user_id, payload);
440
+ }
441
+ } else if (!subagent) {
442
+ subagent = await this._createSubagent(id_project, user_id, payload);
443
+ } else {
444
+ winston.debug("Skip existing subagent with name: " + subagentJson.name);
445
+ }
446
+
447
+ if (subagent) {
448
+ const newId = subagent._id.toString();
449
+ if (sourceId) {
450
+ idMap[sourceId] = newId;
451
+ }
452
+ if (subagent.name) {
453
+ nameMap[subagent.name] = newId;
454
+ }
455
+ imported.push({ subagent, intents: subagentJson.intents });
456
+ }
457
+ }
458
+ return imported;
459
+ }
460
+
461
+ async _remapImportedBotIds(json, importedSubagents, idMap, nameMap, keepExternalBotIds) {
462
+ if (!keepExternalBotIds) {
463
+ const allIntents = [
464
+ ...(json.intents || []),
465
+ ...importedSubagents.flatMap((item) => item.intents || [])
466
+ ];
467
+ await this._fillBotIdMapFromSourceBots(allIntents, idMap, nameMap);
468
+ }
469
+ this._rewriteBotIdsInIntents(json.intents, idMap);
470
+ importedSubagents.forEach((item) => {
471
+ this._rewriteBotIdsInIntents(item.intents, idMap);
472
+ });
473
+ }
474
+
475
+ /** `ds_version` and `connectors` from an import payload onto a bot; nothing when the payload has none. */
476
+ _applyV3Fields(chatbot, payload) {
477
+ if (!payload) {
478
+ return;
479
+ }
480
+ if (payload.ds_version !== undefined && payload.ds_version !== null) {
481
+ chatbot.ds_version = payload.ds_version;
482
+ }
483
+ if (payload.connectors !== undefined && payload.connectors !== null) {
484
+ chatbot.connectors = payload.connectors;
485
+ }
486
+ }
487
+
488
+ /**
489
+ * A JSON exported before the two fields existed is promoted; a malformed list is refused before
490
+ * anything is written; a V3 agent whose JSON has no list (blocks still written the V2 way) is
491
+ * converted here, so it is stored in V3 form like any other.
492
+ */
493
+ _prepareImportedV3(json) {
494
+ v3FlowService.promoteLegacyJson(json);
495
+ const targets = [json, ...(Array.isArray(json.subagents) ? json.subagents : [])];
496
+ for (const target of targets) {
497
+ if (!target || !v3FlowService.isV3(target)) {
498
+ continue;
499
+ }
500
+ if (target.connectors === undefined || target.connectors === null) {
501
+ if (Array.isArray(target.intents)) {
502
+ target.connectors = v3FlowService.toV3Flow(target.intents).connectors;
503
+ winston.verbose("(ChatbotController) V3 import without a connectors list: built from the blocks, " + target.connectors.items.length + " records");
504
+ }
505
+ continue;
506
+ }
507
+ const errors = v3FlowService.validateConnectors(target.connectors);
508
+ if (errors.length > 0) {
509
+ throw new AppError(400, "Invalid connectors list: " + errors.join("; "));
510
+ }
511
+ }
512
+ }
513
+
514
+ async import(json, id_project, chatbot_id, create, replace, overwrite, user_id, skipSubagents) {
515
+ let chatbot;
516
+ const sourceParentId = this._sourceId(json);
517
+ this._stripImportedIdentity(json);
518
+ this._prepareImportedV3(json);
519
+
520
+ if (create) {
521
+ json.template = "empty";
522
+ try {
523
+ chatbot = await faqService.create(id_project, user_id, json);
524
+ } catch (err) {
525
+ winston.error("(ChatbotController) import create error: ", err);
526
+ throw new AppError(400, "Error creating new chatbot");
527
+ }
528
+
529
+ if (json.attributes) {
530
+ this._rewriteRulesParticipants(json.attributes, chatbot._id);
531
+ chatbot = await this.updatedChatbot(chatbot._id, id_project, { attributes: json.attributes });
532
+ }
533
+ } else {
534
+ if (!chatbot_id) {
535
+ throw new AppError(400, "With replace or overwrite option a id_faq_kb must be provided");
536
+ }
537
+
538
+ chatbot = await this.getChatbot(chatbot_id, id_project);
539
+
540
+ for (const field of IMPORT_OVERRIDE_FIELDS) {
541
+ if (json[field]) {
542
+ chatbot[field] = json[field];
543
+ }
544
+ }
545
+
546
+ if (json.attributes) {
547
+ this._rewriteRulesParticipants(json.attributes, chatbot._id);
548
+ chatbot.attributes = json.attributes;
549
+ }
550
+ this._applyV3Fields(chatbot, json);
551
+
552
+ try {
553
+ chatbot = await Chatbot.findByIdAndUpdate(chatbot._id, chatbot, { new: true });
554
+ } catch (err) {
555
+ winston.error("(ChatbotController) import update error: ", err);
556
+ throw new AppError(400, "Error updating chatbot");
557
+ }
558
+ if (!chatbot) {
559
+ throw new AppError(404, `Chatbot not found with id ${chatbot_id}`);
560
+ }
561
+
562
+ botEvent.emit("faqbot.update", chatbot);
563
+ }
564
+
565
+ if (replace) {
566
+ await intentsController.deleteByChatbotId(chatbot._id);
567
+ }
568
+
569
+ const idMap = {};
570
+ const nameMap = {};
571
+ if (sourceParentId) {
572
+ idMap[sourceParentId] = chatbot._id.toString();
573
+ }
574
+ if (chatbot.name) {
575
+ nameMap[chatbot.name] = chatbot._id.toString();
576
+ }
577
+
578
+ let importedSubagents = [];
579
+ if (!skipSubagents) {
580
+ importedSubagents = await this._importSubagents(
581
+ chatbot,
582
+ json.subagents,
583
+ id_project,
584
+ user_id,
585
+ replace,
586
+ overwrite,
587
+ idMap,
588
+ nameMap
589
+ );
590
+ }
591
+ await this._remapImportedBotIds(json, importedSubagents, idMap, nameMap, skipSubagents);
592
+
593
+ await this._importIntents(chatbot._id, id_project, user_id, json.intents, overwrite);
594
+ await Promise.all(importedSubagents.map((item) =>
595
+ this._importIntents(item.subagent._id, id_project, user_id, item.intents, overwrite)
596
+ ));
597
+
598
+ return chatbot;
599
+ }
600
+
601
+ async _exportJson(chatbot, id_project, { exportGlobals = false } = {}) {
602
+ const chatbotData = chatbot.toObject ? chatbot.toObject() : { ...chatbot };
603
+ if (!exportGlobals && chatbotData.attributes) {
604
+ chatbotData.attributes = { ...chatbotData.attributes };
605
+ delete chatbotData.attributes.globals;
606
+ }
607
+
608
+ const intents = await intentsController.getIntents(chatbot._id);
609
+ const subagents = await this.findSubagents(chatbot._id, id_project);
610
+ await Promise.all(subagents.map(async (subagent) => {
611
+ subagent.intents = await intentsController.getIntents(subagent._id);
612
+ }));
613
+
614
+ const json = JSON.parse(this.export(chatbotData, intents, false, subagents));
615
+ json._id = chatbot._id.toString();
616
+ return json;
617
+ }
618
+
619
+ async restoreFromPublished(draft_id, publish_id, id_project, user_id) {
620
+ const draft = await this.getChatbot(draft_id, id_project);
621
+ if (draft.root_id) {
622
+ throw new AppError(400, "Cannot restore into a published chatbot");
623
+ }
624
+
625
+ const published = await this.getChatbot(publish_id, id_project);
626
+ if (!published.root_id || published.root_id.toString() !== draft._id.toString()) {
627
+ throw new AppError(400, `Chatbot ${publish_id} is not a published version of ${draft_id}`);
628
+ }
629
+
630
+ const json = await this._exportJson(published, id_project);
631
+ if (draft.attributes?.globals) {
632
+ json.attributes = { ...(json.attributes || {}), globals: draft.attributes.globals };
633
+ }
634
+
635
+ // A publish snapshot has no cloned subagents. Restoring it must not delete the draft ones.
636
+ const skipSubagents = !json.subagents?.length;
637
+ const restored = await this.import(json, id_project, draft_id, false, true, false, user_id, skipSubagents);
638
+ return this.updatedChatbot(restored._id, id_project, { modified: true });
639
+ }
640
+
641
+ async updatedChatbot(chatbot_id, id_project, update) {
642
+ let chatbot;
643
+ try {
644
+ chatbot = await Chatbot.findOneAndUpdate({ _id: chatbot_id, id_project: id_project }, update, { new: true });
645
+ } catch (err) {
646
+ winston.error("(ChatbotController) updatedChatbot error: ", err);
647
+ throw new AppError(500, `Error updating chatbot with id ${chatbot_id}`);
648
+ }
649
+ if (!chatbot) {
650
+ throw new AppError(404, `Chatbot not found with id ${chatbot_id} for project ${id_project}`);
651
+ }
652
+ return chatbot;
653
+ }
654
+
655
+
656
+ }
657
+
658
+ const chatbotController = new ChatbotController();
659
+ module.exports = chatbotController;