@tiledesk/tiledesk-server 2.28.1 → 2.29.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.
@@ -1,5 +1,6 @@
1
1
  const axios = require("axios").default;
2
2
  const winston = require('../config/winston');
3
+ const botEvent = require('../event/botEvent');
3
4
  let Faq_kb = require("../models/faq_kb");
4
5
 
5
6
  class ChatbotService {
@@ -9,11 +10,20 @@ class ChatbotService {
9
10
  async fork(id_faq_kb, api_url, token, project_id, options) {
10
11
  winston.debug("(ChatbotService) fork");
11
12
 
13
+ // `true` is the older skip-subagents flag; publish passes `{ skipSubagents, forPublish }`.
14
+ const skipSubagents = options === true || (options && options.skipSubagents === true);
12
15
  const forPublish = options && options.forPublish === true;
13
- const forPublishQuery = forPublish ? '&for_publish=true' : '';
16
+
17
+ let url = api_url + '/' + project_id + '/faq_kb/fork/' + id_faq_kb + '?projectid=' + project_id + '&public=false&globals=true';
18
+ if (skipSubagents) {
19
+ url += '&skipSubagents=true';
20
+ }
21
+ if (forPublish) {
22
+ url += '&for_publish=true';
23
+ }
14
24
 
15
25
  return await axios({
16
- url: api_url + '/' + project_id + '/faq_kb/fork/' + id_faq_kb + '?projectid=' + project_id + '&public=false&globals=true' + forPublishQuery,
26
+ url: url,
17
27
  headers: {
18
28
  'Content-Type': 'application/json',
19
29
  'Authorization': token
@@ -45,8 +55,11 @@ class ChatbotService {
45
55
  },
46
56
  method: 'GET'
47
57
  }).then((resbody) => {
48
- winston.debug("(ChatbotService) forking private chatbot " + resbody.data.name)
49
58
  let chatbot = resbody.data;
59
+ if (typeof chatbot === 'string') {
60
+ chatbot = JSON.parse(chatbot);
61
+ }
62
+ winston.debug("(ChatbotService) forking private chatbot " + chatbot.name)
50
63
  return chatbot;
51
64
  }).catch((err) => {
52
65
  winston.error('(ChatbotService) FAQ_KB EXPORTJSON ERROR ' + err);
@@ -63,8 +76,11 @@ class ChatbotService {
63
76
  },
64
77
  method: 'GET'
65
78
  }).then((resbody) => {
66
- winston.debug("(ChatbotService) forking public chatbot " + resbody.data.name);
67
79
  let chatbot = resbody.data;
80
+ if (typeof chatbot === 'string') {
81
+ chatbot = JSON.parse(chatbot);
82
+ }
83
+ winston.debug("(ChatbotService) forking public chatbot " + chatbot.name);
68
84
  return chatbot
69
85
  }).catch((err) => {
70
86
  winston.error('(ChatbotService) FAQ_KB CHATBOT TEMPLATES ERROR ' + err);
@@ -100,8 +116,10 @@ class ChatbotService {
100
116
 
101
117
  winston.debug("(ChatbotService) importFaqs");
102
118
 
119
+ // Full import into the newly created parent: intents + nested subagents.
120
+ // Subagents are parent-scoped and get parent_id remapped by chatbotController.import.
103
121
  return await axios({
104
- url: api_url + '/' + project_id + '/faq_kb/importjson/' + id_faq_kb + "?intentsOnly=true",
122
+ url: api_url + '/' + project_id + '/faq_kb/importjson/' + id_faq_kb,
105
123
  headers: {
106
124
  'Content-Type': 'application/json',
107
125
  'Authorization': token
@@ -125,6 +143,67 @@ class ChatbotService {
125
143
  return Promise.reject(err);
126
144
  });
127
145
  }
146
+
147
+ _tilebotEndpoint() {
148
+ const port = process.env.PORT || '3000';
149
+ let endpoint = process.env.API_ENDPOINT
150
+ ? process.env.API_ENDPOINT + "/modules/tilebot/ext/"
151
+ : ("http://localhost:" + port + "/modules/tilebot/ext/");
152
+
153
+ if (process.env.TILEBOT_ENDPOINT) {
154
+ endpoint = process.env.TILEBOT_ENDPOINT + "/ext/";
155
+ }
156
+ return endpoint;
157
+ }
158
+
159
+ async publish({ id_faq_kb, chatbot_id, api_url, token, project_id, user_id, release_note, req }) {
160
+ const sourceId = chatbot_id || id_faq_kb;
161
+ winston.debug("(ChatbotService) publish id_faq_kb: " + id_faq_kb + " source: " + sourceId);
162
+
163
+ // Keep callsubagent.botId on the development subagent. Its url points at the published snapshot.
164
+ // for_publish skips FAQ_KB_CREATE on the trashed snapshot; FAQ_KB_PUBLISH is emitted below.
165
+ const forked = await this.fork(sourceId, api_url, token, project_id, { skipSubagents: true, forPublish: true });
166
+ const forkedChatBotId = forked.bot_id;
167
+ winston.debug("(ChatbotService) publish forkedChatBotId: " + forkedChatBotId);
168
+
169
+ const updatedForkedChatbot = await Faq_kb.findByIdAndUpdate(
170
+ forkedChatBotId,
171
+ {
172
+ $unset: { slug: 1 },
173
+ trashed: true,
174
+ root_id: id_faq_kb,
175
+ release_note: release_note,
176
+ publishedBy: user_id,
177
+ publishedAt: new Date().getTime()
178
+ },
179
+ { new: true, upsert: true }
180
+ ).exec();
181
+ winston.debug("(ChatbotService) publish updatedForkedChatbot: ", updatedForkedChatbot);
182
+ botEvent.emit('faqbot.update', updatedForkedChatbot);
183
+
184
+ const tilebotEndpoint = this._tilebotEndpoint();
185
+ winston.debug("(ChatbotService) TILEBOT_ENDPOINT: " + tilebotEndpoint);
186
+
187
+ const updatedOriginalChatbot = await Faq_kb.findByIdAndUpdate(
188
+ id_faq_kb,
189
+ { url: tilebotEndpoint + forkedChatBotId },
190
+ { new: true, upsert: true }
191
+ ).exec();
192
+ winston.debug("(ChatbotService) publish updatedOriginalChatbot: ", updatedOriginalChatbot);
193
+
194
+ await this.setModified(id_faq_kb, false);
195
+ botEvent.emit('faqbot.update', updatedOriginalChatbot);
196
+ botEvent.emit('faqbot.publish', {
197
+ req,
198
+ chatbot: updatedOriginalChatbot,
199
+ publishedBotId: forkedChatBotId,
200
+ release_note
201
+ });
202
+
203
+ return { bot_id: forkedChatBotId };
204
+ }
205
+
128
206
  }
129
207
 
208
+
130
209
  module.exports = { ChatbotService }
@@ -5,66 +5,152 @@ const botEvent = require('../event/botEvent');
5
5
  const chatbotTypes = require("../models/chatbotTypes");
6
6
  const chatbotTemplates = require("../models/chatbotTemplates");
7
7
  const templates = require('../template/chatbot');
8
+ const subagentNotes = require('../template/chatbot/subagent_notes');
8
9
 
9
10
  class FaqService {
10
11
 
11
- create(id_project, id_user, data) {
12
+ // create(id_project, id_user, data) {
12
13
 
13
- return new Promise((resolve, reject) => {
14
+ // return new Promise((resolve, reject) => {
14
15
 
15
- var newFaq_kb = new Faq_kb({
16
- name: data.name,
17
- slug: data.slug,
18
- description: data.description,
19
- url: data.url,
20
- id_project: id_project,
21
- webhook_url: data.webhook_url,
22
- webhook_enabled: data.webhook_enabled,
23
- type: data.type,
24
- subtype: data.subtype,
25
- language: data.language,
26
- public: false,
27
- certified: false,
28
- mainCategory: data.mainCategory,
29
- intentsEngine: data.intentsEngine,
30
- trashed: false,
31
- createdBy: id_user,
32
- updatedBy: id_user,
33
- attributes: data.attributes
34
- });
16
+ // if (data.type === "subagent" && !data.parent_id) {
17
+ // return reject('Parent ID is required for subagent');
18
+ // }
35
19
 
36
- newFaq_kb.save((err, savedFaq_kb) => {
20
+ // var newFaq_kb = new Faq_kb({
21
+ // name: data.name,
22
+ // slug: data.slug,
23
+ // description: data.description,
24
+ // url: data.url,
25
+ // id_project: id_project,
26
+ // webhook_url: data.webhook_url,
27
+ // webhook_enabled: data.webhook_enabled,
28
+ // type: data.type,
29
+ // subtype: data.subtype,
30
+ // language: data.language,
31
+ // public: false,
32
+ // certified: false,
33
+ // mainCategory: data.mainCategory,
34
+ // intentsEngine: data.intentsEngine,
35
+ // trashed: false,
36
+ // createdBy: id_user,
37
+ // updatedBy: id_user,
38
+ // attributes: data.attributes,
39
+ // parent_id: data.parent_id
40
+ // });
37
41
 
38
- if (err) {
39
- winston.error('(FaqService) error saving new chatbot ', err)
40
- return reject('Error saving object.');
41
- }
42
+ // newFaq_kb.save((err, savedFaq_kb) => {
42
43
 
43
- winston.debug('(FaqService) saved chatbot ', savedFaq_kb.toObject())
44
- botEvent.emit('faqbot.create', savedFaq_kb);
44
+ // if (err) {
45
+ // winston.error('(FaqService) error saving new chatbot ', err)
46
+ // return reject('Error saving object.');
47
+ // }
45
48
 
46
- winston.debug('type ' + data.type)
49
+ // winston.debug('(FaqService) saved chatbot ', savedFaq_kb.toObject())
50
+ // botEvent.emit('faqbot.create', savedFaq_kb);
47
51
 
48
- if (data.type === "internal" || data.type === "tilebot") {
52
+ // winston.debug('type ' + data.type)
49
53
 
50
- let template = this.#resolveTemplate(data.subtype, data.template);
51
- winston.debug('template ' + template);
54
+ // if (data.type === "internal" || data.type === "tilebot") {
52
55
 
53
- let options = {};
54
- if (data.namespace_id) {
55
- options.namespace_id = data.namespace_id;
56
- }
56
+ // let template = this.#resolveTemplate(data.subtype, data.template);
57
+ // winston.debug('template ' + template);
57
58
 
58
- this.createGreetingsAndOperationalsFaqs(savedFaq_kb._id, savedFaq_kb.createdBy, savedFaq_kb.id_project, template, options);
59
+ // let options = {};
60
+ // if (data.namespace_id) {
61
+ // options.namespace_id = data.namespace_id;
62
+ // }
59
63
 
60
- } else {
61
- winston.debug('(FaqService) Chatbot type: external bot');
62
- }
64
+ // this.createGreetingsAndOperationalsFaqs(savedFaq_kb._id, savedFaq_kb.createdBy, savedFaq_kb.id_project, template, options);
63
65
 
64
- return resolve(savedFaq_kb);
65
- });
66
+ // } else {
67
+ // winston.debug('(FaqService) Chatbot type: external bot');
68
+ // }
69
+
70
+ // return resolve(savedFaq_kb);
71
+ // });
72
+
73
+ // });
74
+ // }
75
+
76
+ async create(id_project, id_user, data) {
77
+
78
+ if (!this.validateSubagent(data)) {
79
+ throw new Error("parent_id is required for subtype 'subagent'");
80
+ }
81
+
82
+ let chatbot = this.createChatbotObject(id_project, id_user, data);
83
+
84
+ let template;
85
+ if (data.type === "internal" || data.type === "tilebot") {
86
+ template = this.#resolveTemplate(data.subtype, data.template);
87
+ if (template === 'blank_subagent') {
88
+ this.#applyBlankSubagentAttributes(chatbot);
89
+ }
90
+ }
91
+
92
+ let savedChatbot;
93
+ try {
94
+ savedChatbot = await chatbot.save();
95
+ winston.verbose('(FaqService) saved chatbot ', savedChatbot.toObject());
96
+ } catch (error) {
97
+ throw error;
98
+ }
99
+
100
+ if (template) {
101
+ let options = {};
102
+ if (data.namespace_id) {
103
+ options.namespace_id = data.namespace_id;
104
+ }
105
+ this.createGreetingsAndOperationalsFaqs(savedChatbot._id, savedChatbot.createdBy, savedChatbot.id_project, template, options);
106
+ }
107
+
108
+ botEvent.emit('faqbot.create', savedChatbot);
109
+ return savedChatbot;
110
+ }
66
111
 
112
+ #applyBlankSubagentAttributes(chatbot) {
113
+ const notes = JSON.parse(JSON.stringify(subagentNotes.notes)).map(note => {
114
+ note.id_faq_kb = chatbot._id.toString();
115
+ return note;
67
116
  });
117
+
118
+ chatbot.attributes = {
119
+ ...(chatbot.attributes || {}),
120
+ notes
121
+ };
122
+ }
123
+
124
+ createChatbotObject(id_project, id_user, data) {
125
+ return new Faq_kb({
126
+ name: data.name,
127
+ slug: data.slug,
128
+ description: data.description,
129
+ url: data.url,
130
+ id_project: id_project.toString(),
131
+ webhook_url: data.webhook_url,
132
+ webhook_enabled: data.webhook_enabled,
133
+ type: data.type,
134
+ subtype: data.subtype,
135
+ language: data.language,
136
+ public: false,
137
+ certified: false,
138
+ mainCategory: data.mainCategory,
139
+ intentsEngine: data.intentsEngine,
140
+ trashed: false,
141
+ createdBy: id_user,
142
+ updatedBy: id_user,
143
+ attributes: data.attributes,
144
+ parent_id: data.parent_id
145
+ })
146
+ }
147
+
148
+ validateSubagent(data) {
149
+ if (data.subtype === "subagent" && !data.parent_id) {
150
+ winston.error('(FaqService) parent_id is required for subtype ' + data.subtype);
151
+ return false;
152
+ }
153
+ return true;
68
154
  }
69
155
 
70
156
  #resolveTemplate(subtype, template) {
@@ -3,9 +3,26 @@ const uuidv4 = require('uuid/v4');
3
3
  module.exports = function generateTemplate(options) {
4
4
 
5
5
  const custom_intent_id = uuidv4();
6
-
6
+ const default_fallback_intent_id = uuidv4();
7
+
7
8
  return [
8
9
  {
10
+ "webhook_enabled": false,
11
+ "enabled": true,
12
+ "actions": [],
13
+ "intent_display_name": "defaultFallback",
14
+ "attributes": {
15
+ "position": {
16
+ "x": 714,
17
+ "y": 528
18
+ },
19
+ "nextBlockAction": {
20
+ "_tdActionId": "5b689996-317b-4761-9bc0-cc78d1f390cb",
21
+ "_tdActionType": "intent",
22
+ "intentName": "#" + default_fallback_intent_id
23
+ }
24
+ }
25
+ }, {
9
26
  "webhook_enabled": false,
10
27
  "enabled": true,
11
28
  "actions": [{
@@ -24,10 +41,11 @@ module.exports = function generateTemplate(options) {
24
41
  }]
25
42
  }
26
43
  }],
27
- "intent_display_name": "defaultFallback",
44
+ "intent_display_name": "untitled_block_2",
45
+ "intent_id": default_fallback_intent_id,
28
46
  "attributes": {
29
47
  "position": {
30
- "x": 714,
48
+ "x": 1114,
31
49
  "y": 528
32
50
  }
33
51
  }
@@ -0,0 +1,139 @@
1
+ const uuidv4 = require('uuid/v4');
2
+
3
+ module.exports = function generateTemplate(options) {
4
+
5
+ const custom_intent_id = uuidv4();
6
+ const return_custom_intent_id = uuidv4();
7
+ const default_fallback_intent_id = uuidv4();
8
+
9
+ return [
10
+ {
11
+ "webhook_enabled": false,
12
+ "enabled": true,
13
+ "actions": [{
14
+ "_tdActionType": "intent",
15
+ "intentName": "#" + custom_intent_id
16
+ }],
17
+ "question": "",
18
+ "intent_display_name": "start",
19
+ "attributes": {
20
+ "position": {
21
+ "x": 172,
22
+ "y": 384
23
+ }
24
+ },
25
+ "agents_available": false,
26
+ },
27
+ {
28
+ "webhook_enabled": false,
29
+ "enabled": true,
30
+ "actions": [{
31
+ "_tdActionTitle": "",
32
+ "_tdActionType": "reply",
33
+ "attributes": {
34
+ "disableInputMessage": false,
35
+ "commands": [
36
+ {
37
+ "type": "wait",
38
+ "time": 500
39
+ },
40
+ {
41
+ "type": "message",
42
+ "message": {
43
+ "type": "text",
44
+ "text": "A chat message will be sent to the visitor",
45
+ "attributes": {
46
+ "attachment": {
47
+ "type": "template",
48
+ "buttons": [],
49
+ "json_buttons": "",
50
+ "json_gallery": ""
51
+ }
52
+ }
53
+ }
54
+ }
55
+ ]
56
+ },
57
+
58
+ }],
59
+ "intent_display_name": "untitled_block_1",
60
+ "intent_id": custom_intent_id,
61
+ "agents_available": false,
62
+ "attributes": {
63
+ "position": {
64
+ "x": 612,
65
+ "y": 312
66
+ },
67
+ "nextBlockAction": {
68
+ "_tdActionId": "5b689996-317b-4761-9bc0-cc78d1f390ca",
69
+ "_tdActionType": "intent",
70
+ "intentName": "#" + return_custom_intent_id
71
+ },
72
+ "connectors": {},
73
+ "color": "156,163,205",
74
+ "readonly": false
75
+ },
76
+ },
77
+ {
78
+ "webhook_enabled": false,
79
+ "enabled": true,
80
+ "actions": [{
81
+ "_tdActionType": "returnstack",
82
+ }],
83
+ "intent_display_name": "return",
84
+ "agents_available": false,
85
+ "intent_id": return_custom_intent_id,
86
+ "attributes": {
87
+ "position": {
88
+ "x": 1129,
89
+ "y": 361
90
+ }
91
+ },
92
+ "agents_available": false,
93
+ },
94
+ {
95
+ "webhook_enabled": false,
96
+ "enabled": true,
97
+ "actions": [],
98
+ "intent_display_name": "defaultFallback",
99
+ "attributes": {
100
+ "position": {
101
+ "x": 315,
102
+ "y": 699
103
+ },
104
+ "nextBlockAction": {
105
+ "_tdActionId": "5b689996-317b-4761-9bc0-cc78d1f390cb",
106
+ "_tdActionType": "intent",
107
+ "intentName": "#" + default_fallback_intent_id
108
+ }
109
+ }
110
+ }, {
111
+ "webhook_enabled": false,
112
+ "enabled": true,
113
+ "actions": [{
114
+ "_tdActionType": "reply",
115
+ "text": "I didn't understand. Can you rephrase your question?",
116
+ "attributes": {
117
+ "commands": [{
118
+ "type": "wait",
119
+ "time": 500
120
+ }, {
121
+ "type": "message",
122
+ "message": {
123
+ "type": "text",
124
+ "text": "I didn't understand. Can you rephrase your question?"
125
+ }
126
+ }]
127
+ }
128
+ }],
129
+ "intent_display_name": "untitled_block_2",
130
+ "intent_id": default_fallback_intent_id,
131
+ "attributes": {
132
+ "position": {
133
+ "x": 721,
134
+ "y": 699
135
+ }
136
+ }
137
+ }
138
+ ]
139
+ }
@@ -3,6 +3,7 @@ const uuidv4 = require('uuid/v4');
3
3
  module.exports = function generateTemplate(options) {
4
4
 
5
5
  const custom_intent_id = uuidv4();
6
+ const default_fallback_intent_id = uuidv4();
6
7
 
7
8
  return [
8
9
  {
@@ -24,6 +25,22 @@ module.exports = function generateTemplate(options) {
24
25
  }
25
26
  },
26
27
  {
28
+ "webhook_enabled": false,
29
+ "enabled": true,
30
+ "actions": [],
31
+ "intent_display_name": "defaultFallback",
32
+ "attributes": {
33
+ "position": {
34
+ "x": 714,
35
+ "y": 528
36
+ },
37
+ "nextBlockAction": {
38
+ "_tdActionId": "5b689996-317b-4761-9bc0-cc78d1f390cb",
39
+ "_tdActionType": "intent",
40
+ "intentName": "#" + default_fallback_intent_id
41
+ }
42
+ }
43
+ }, {
27
44
  "webhook_enabled": false,
28
45
  "enabled": true,
29
46
  "actions": [{
@@ -40,13 +57,13 @@ module.exports = function generateTemplate(options) {
40
57
  "text": "I didn't understand. Can you rephrase your question?"
41
58
  }
42
59
  }]
43
- },
60
+ }
44
61
  }],
45
- "intent_display_name": "defaultFallback",
62
+ "intent_display_name": "untitled_block_2",
63
+ "intent_id": default_fallback_intent_id,
46
64
  "attributes": {
47
- "readonly": true,
48
65
  "position": {
49
- "x": 714,
66
+ "x": 1114,
50
67
  "y": 528
51
68
  }
52
69
  }
@@ -3,6 +3,7 @@ const uuidv4 = require('uuid/v4');
3
3
  module.exports = function generateTemplate(options) {
4
4
 
5
5
  const custom_intent_id = uuidv4();
6
+ const default_fallback_intent_id = uuidv4();
6
7
 
7
8
  return [
8
9
  {
@@ -26,6 +27,22 @@ module.exports = function generateTemplate(options) {
26
27
  }
27
28
  },
28
29
  {
30
+ "webhook_enabled": false,
31
+ "enabled": true,
32
+ "actions": [],
33
+ "intent_display_name": "defaultFallback",
34
+ "attributes": {
35
+ "position": {
36
+ "x": 714,
37
+ "y": 528
38
+ },
39
+ "nextBlockAction": {
40
+ "_tdActionId": "5b689996-317b-4761-9bc0-cc78d1f390cb",
41
+ "_tdActionType": "intent",
42
+ "intentName": "#" + default_fallback_intent_id
43
+ }
44
+ }
45
+ }, {
29
46
  "webhook_enabled": false,
30
47
  "enabled": true,
31
48
  "actions": [{
@@ -39,17 +56,16 @@ module.exports = function generateTemplate(options) {
39
56
  "type": "message",
40
57
  "message": {
41
58
  "type": "text",
42
- "text": "I didn't understand. Can you rephrase your question?",
59
+ "text": "I didn't understand. Can you rephrase your question?"
43
60
  }
44
61
  }]
45
62
  }
46
63
  }],
47
- "intent_display_name": "defaultFallback",
48
- "agents_available": false,
64
+ "intent_display_name": "untitled_block_2",
65
+ "intent_id": default_fallback_intent_id,
49
66
  "attributes": {
50
- "readonly": true,
51
67
  "position": {
52
- "x": 714,
68
+ "x": 1114,
53
69
  "y": 528
54
70
  }
55
71
  }
@@ -3,6 +3,7 @@ module.exports = {
3
3
  blank: require('./blank'),
4
4
  blank_webhook: require('./blank_webhook'),
5
5
  blank_copilot: require('./blank_copilot'),
6
+ blank_subagent: require('./blank_subagent'),
6
7
  official_copilot: require('./official_copilot'),
7
8
  blank_voice: require('./blank_voice'),
8
9
  blank_voice_twilio: require('./blank_voice_twilio'),
@@ -0,0 +1,30 @@
1
+ module.exports = {
2
+ "notes": [
3
+ {
4
+ "id_faq_kb": "6a70ab21cb52270013040601",
5
+ "note_id": "fda256bb-d34a-4bd6-b0d0-018360500318",
6
+ "x": 675.106,
7
+ "y": 202,
8
+ "type": "rect",
9
+ "text": "",
10
+ "width": 130,
11
+ "height": 42,
12
+ "createdAt": "2026-08-03T15:51:49.143Z",
13
+ "backgroundColor": "rgba(75, 79, 82, 100)",
14
+ "borderColor": "rgba(67, 140, 181, 1)",
15
+ "borderWidth": 0,
16
+ "isNew": true,
17
+ "backgroundOpacity": 100,
18
+ "borderOpacity": 100,
19
+ "boxShadow": true,
20
+ "fontSize": "0.96em",
21
+ "fontFamily": "Open Sans, sans-serif",
22
+ "rotation": 0,
23
+ "scale": [
24
+ 1.74501,
25
+ 1.70771
26
+ ],
27
+ "title": "<p><span class=\"ql-color-ffffff\">Replace this block with your subagent logic</span></p>"
28
+ }
29
+ ]
30
+ }