@tiledesk/tiledesk-voice-twilio-connector 0.1.27 → 0.2.0-rc3

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 (50) hide show
  1. package/LICENSE +179 -0
  2. package/README.md +44 -0
  3. package/index.js +7 -1529
  4. package/package.json +23 -22
  5. package/src/app.js +146 -0
  6. package/src/config/index.js +32 -0
  7. package/src/controllers/VoiceController.js +488 -0
  8. package/src/controllers/VoiceController.original.js +811 -0
  9. package/src/middlewares/httpLogger.js +31 -0
  10. package/src/models/KeyValueStore.js +78 -0
  11. package/src/routes/manageApp.js +298 -0
  12. package/src/routes/voice.js +22 -0
  13. package/src/services/AiService.js +219 -0
  14. package/src/services/AiService.sdk.js +367 -0
  15. package/src/services/IntegrationService.js +74 -0
  16. package/src/services/MessageService.js +133 -0
  17. package/src/services/README_SDK.md +107 -0
  18. package/src/services/SessionService.js +143 -0
  19. package/src/services/SpeechService.js +134 -0
  20. package/src/services/TiledeskMessageBuilder.js +135 -0
  21. package/src/services/TwilioService.js +122 -0
  22. package/src/services/UploadService.js +78 -0
  23. package/src/services/channels/TiledeskChannel.js +269 -0
  24. package/{tiledesk → src/services/channels}/VoiceChannel.js +17 -56
  25. package/src/services/clients/TiledeskSubscriptionClient.js +78 -0
  26. package/src/services/index.js +45 -0
  27. package/src/services/translators/TiledeskTwilioTranslator.js +509 -0
  28. package/{tiledesk/TiledeskTwilioTranslator.js → src/services/translators/TiledeskTwilioTranslator.original.js} +119 -202
  29. package/src/utils/fileUtils.js +24 -0
  30. package/src/utils/logger.js +32 -0
  31. package/{tiledesk → src/utils}/utils-message.js +6 -21
  32. package/logs/app.log +0 -3082
  33. package/routes/manageApp.js +0 -419
  34. package/tiledesk/KVBaseMongo.js +0 -101
  35. package/tiledesk/TiledeskChannel.js +0 -363
  36. package/tiledesk/TiledeskSubscriptionClient.js +0 -135
  37. package/tiledesk/fileUtils.js +0 -55
  38. package/tiledesk/services/AiService.js +0 -230
  39. package/tiledesk/services/IntegrationService.js +0 -81
  40. package/tiledesk/services/UploadService.js +0 -88
  41. /package/{winston.js → src/config/logger.js} +0 -0
  42. /package/{tiledesk → src}/services/voiceEventEmitter.js +0 -0
  43. /package/{template → src/template}/configure.html +0 -0
  44. /package/{template → src/template}/css/configure.css +0 -0
  45. /package/{template → src/template}/css/error.css +0 -0
  46. /package/{template → src/template}/css/style.css +0 -0
  47. /package/{template → src/template}/error.html +0 -0
  48. /package/{tiledesk → src/utils}/constants.js +0 -0
  49. /package/{tiledesk → src/utils}/errors.js +0 -0
  50. /package/{tiledesk → src/utils}/utils.js +0 -0
@@ -1,26 +1,26 @@
1
1
  const { v4: uuidv4 } = require("uuid");
2
- const winston = require("../winston");
2
+ const winston = require("../../utils/logger");
3
3
  const xmlbuilder = require("xmlbuilder");
4
4
  const querystring = require("querystring");
5
5
 
6
- const utils = require("./utils.js");
7
- const utils_message = require("./utils-message.js");
8
- const MENU_CHOICE = require("./constants.js").MENU_CHOICE;
9
- const WAIT_MESSAGE = require("./constants.js").WAIT_MESSAGE;
10
- const TEXT_MESSAGE = require("./constants.js").TEXT_MESSAGE;
11
- const SETTING_MESSAGE = require('./constants').SETTING_MESSAGE
12
- const CHANNEL_NAME = require('./constants').CHANNEL_NAME
13
- const VOICE_PROVIDER = require('./constants').VOICE_PROVIDER;
14
- const OPENAI_SETTINGS = require('./constants').OPENAI_SETTINGS;
15
- const ELEVENLABS_SETTINGS = require('./constants').ELEVENLABS_SETTINGS;
6
+ const utils = require("../../utils/utils.js");
7
+ const utils_message = require("../../utils/utils-message.js");
8
+ const MENU_CHOICE = require("../../utils/constants.js").MENU_CHOICE;
9
+ const WAIT_MESSAGE = require("../../utils/constants.js").WAIT_MESSAGE;
10
+ const TEXT_MESSAGE = require("../../utils/constants.js").TEXT_MESSAGE;
11
+ const SETTING_MESSAGE = require('../../utils/constants').SETTING_MESSAGE
12
+ const CHANNEL_NAME = require('../../utils/constants').CHANNEL_NAME
13
+ const VOICE_PROVIDER = require('../../utils/constants').VOICE_PROVIDER;
14
+ const OPENAI_SETTINGS = require('../../utils/constants').OPENAI_SETTINGS;
15
+ const ELEVENLABS_SETTINGS = require('../../utils/constants').ELEVENLABS_SETTINGS;
16
16
 
17
- const TYPE_ACTION_VXML = require('./constants').TYPE_ACTION_VXML
18
- const TYPE_MESSAGE = require('./constants').TYPE_MESSAGE
19
- const INFO_MESSAGE_TYPE = require('./constants').INFO_MESSAGE_TYPE
17
+ const TYPE_ACTION_VXML = require('../../utils/constants').TYPE_ACTION_VXML
18
+ const TYPE_MESSAGE = require('../../utils/constants').TYPE_MESSAGE
19
+ const INFO_MESSAGE_TYPE = require('../../utils/constants').INFO_MESSAGE_TYPE
20
20
 
21
- const voiceEventEmitter = require('./services/voiceEventEmitter');
21
+ const voiceEventEmitter = require('../voiceEventEmitter');
22
22
 
23
- const { SttError } = require('./errors');
23
+ const { SttError } = require('../../utils/errors');
24
24
 
25
25
  class TiledeskTwilioTranslator {
26
26
  /**
@@ -52,7 +52,7 @@ class TiledeskTwilioTranslator {
52
52
  this.uploadService = config.uploadService
53
53
  }
54
54
 
55
-
55
+ this.lastCallSidVerb = {};
56
56
  this.log = false;
57
57
  }
58
58
 
@@ -115,8 +115,8 @@ class TiledeskTwilioTranslator {
115
115
  vxmlAttributes = this.manageVoiceAttributes(msg, vxmlAttributes)
116
116
 
117
117
 
118
- this.user = sessionInfo.user
119
- this.integrations = sessionInfo.integrations
118
+ // this.user = sessionInfo.user
119
+ // this.integrations = sessionInfo.integrations
120
120
 
121
121
 
122
122
  const xml = xmlbuilder.create("Response", {});
@@ -141,7 +141,7 @@ class TiledeskTwilioTranslator {
141
141
  const isWait = this.checkIfIsWait(msg);
142
142
  winston.debug("[TiledeskVXMLTranslator] toVXML: isWait:"+ isWait);
143
143
  if(isWait){
144
- const delayForm = await this.delayVXMLConverter(xml, msg, vxmlAttributes);
144
+ const delayForm = await this.delayVXMLConverter(xml, msg, vxmlAttributes, sessionInfo);
145
145
  return delayForm;
146
146
  }
147
147
 
@@ -149,7 +149,7 @@ class TiledeskTwilioTranslator {
149
149
  const isDtmfForm = this.checkIfIsDTMFForm(msg);
150
150
  winston.debug("[TiledeskVXMLTranslator] toVXML: isDtmfForm: "+ isDtmfForm);
151
151
  if(isDtmfForm){
152
- const DTMFForm = await this.dtmfFormVXMLConverter(xml, msg, vxmlAttributes);
152
+ const DTMFForm = await this.dtmfFormVXMLConverter(xml, msg, vxmlAttributes, sessionInfo);
153
153
  return DTMFForm;
154
154
  }
155
155
 
@@ -157,7 +157,7 @@ class TiledeskTwilioTranslator {
157
157
  const isBlindFransfer = this.checkIfIsBlindFransfer(msg);
158
158
  winston.debug("[TiledeskVXMLTranslator] toVXML: isBlindFransfer: "+ isBlindFransfer);
159
159
  if(isBlindFransfer){
160
- const blindTransfer = await this.blindTransferVXMLConverter(xml, msg, vxmlAttributes);
160
+ const blindTransfer = await this.blindTransferVXMLConverter(xml, msg, vxmlAttributes, sessionInfo);
161
161
  return blindTransfer;
162
162
  }
163
163
 
@@ -165,7 +165,7 @@ class TiledeskTwilioTranslator {
165
165
  const isMenu = this.checkIfIsDTMFMenuMessage(msg);
166
166
  winston.debug("[TiledeskVXMLTranslator] toVXML: isMenu: "+ isMenu);
167
167
  if(isMenu){
168
- const menu = await this.menuVXMLConverter(xml, msg, vxmlAttributes);
168
+ const menu = await this.menuVXMLConverter(xml, msg, vxmlAttributes, sessionInfo);
169
169
  return menu;
170
170
  }
171
171
 
@@ -173,71 +173,62 @@ class TiledeskTwilioTranslator {
173
173
  const isSpeechForm = this.checkIfIsSpeechFormMessage(msg);
174
174
  winston.debug("[TiledeskVXMLTranslator] toVXML: isSpeechForm: "+ isSpeechForm);
175
175
  if(isSpeechForm){
176
- const form = await this.speechFormVXMLConverter(xml, msg, vxmlAttributes);
176
+ const form = await this.speechFormVXMLConverter(xml, msg, vxmlAttributes, sessionInfo);
177
177
  return form;
178
178
  }
179
179
 
180
180
  /** check for FORM (PlayPrompt action) **/
181
181
  winston.debug("[TiledeskVXMLTranslator] toVXML: isPrompt: true");
182
- const prompt = await this.playPromptVXMLConverter(xml, msg, vxmlAttributes);
182
+ const prompt = await this.playPromptVXMLConverter(xml, msg, vxmlAttributes, sessionInfo);
183
183
  return prompt;
184
184
 
185
185
  }
186
186
 
187
187
  }
188
188
 
189
+ /**
190
+ * Generic helper to check if message has a specific VXML action subType
191
+ * @param {Object} msg - The message object
192
+ * @param {string} subType - The subType to check for
193
+ * @returns {boolean}
194
+ */
195
+ _hasSettingsSubType(msg, subType) {
196
+ const commands = msg.attributes?.commands;
197
+ if (!commands) return false;
198
+ const settingsElement = commands.find((command) => command.type === SETTING_MESSAGE);
199
+ return settingsElement?.subType === subType;
200
+ }
201
+
189
202
  checkIfIsDTMFMenuMessage(msg) {
190
- const commands = msg.attributes.commands;
191
- let dtmf_element = commands.filter((command) => command.type === SETTING_MESSAGE)
192
- if(dtmf_element && dtmf_element.length > 0 && dtmf_element[0].subType === TYPE_ACTION_VXML.DTMF_MENU){
193
- return true;
194
- }
195
- return false;
203
+ return this._hasSettingsSubType(msg, TYPE_ACTION_VXML.DTMF_MENU);
196
204
  }
197
205
 
198
206
  checkIfIsDTMFForm(msg){
199
- const commands = msg.attributes.commands;
200
- let dtmf_element = commands.filter((command) => command.type === SETTING_MESSAGE)
201
- if(dtmf_element && dtmf_element.length > 0 && dtmf_element[0].subType === TYPE_ACTION_VXML.DTMF_FORM){
202
- return true;
203
- }
204
- return false;
207
+ return this._hasSettingsSubType(msg, TYPE_ACTION_VXML.DTMF_FORM);
205
208
  }
206
209
 
207
210
  checkIfIsBlindFransfer(msg){
208
- const commands = msg.attributes.commands;
209
- let dtmf_element = commands.filter((command) => command.type === SETTING_MESSAGE)
210
- if(dtmf_element && dtmf_element.length > 0 && dtmf_element[0].subType === TYPE_ACTION_VXML.BLIND_TRANSFER ){
211
- return true;
212
- }
213
- return false;
211
+ return this._hasSettingsSubType(msg, TYPE_ACTION_VXML.BLIND_TRANSFER);
214
212
  }
215
213
 
216
214
  checkIfIsSpeechFormMessage(msg){
217
- const commands = msg.attributes.commands;
218
- let dtmf_element = commands.filter((command) => command.type === SETTING_MESSAGE)
219
- if(dtmf_element && dtmf_element.length > 0 && dtmf_element[0].subType === TYPE_ACTION_VXML.SPEECH_FORM ){
220
- return true;
221
- }
222
- return false;
215
+ return this._hasSettingsSubType(msg, TYPE_ACTION_VXML.SPEECH_FORM);
223
216
  }
224
217
 
225
218
  checkIfIsWait(msg){
226
- const commands = msg.attributes.commands;
227
- let wait_element = commands.filter((command) => command.type === WAIT_MESSAGE)
228
- let message_element = commands.filter((command) => command.type === TEXT_MESSAGE)
229
- if(wait_element && wait_element.length === 1 && message_element && message_element.length === 0){
230
- return true;
231
- }
232
- return false;
219
+ const commands = msg.attributes?.commands;
220
+ if (!commands) return false;
221
+ const hasWait = commands.some((command) => command.type === WAIT_MESSAGE);
222
+ const hasMessage = commands.some((command) => command.type === TEXT_MESSAGE);
223
+ return hasWait && !hasMessage;
233
224
  }
234
225
 
235
226
  setVXMLAttributes(commands, attributes){
236
227
  const settingsCommand = commands.slice(-1)[0];
237
- if(settingsCommand && settingsCommand.settings){
238
- Object.keys(settingsCommand.settings).forEach((key) => attributes[key]= settingsCommand.settings[key])
228
+ if(settingsCommand?.settings){
229
+ Object.assign(attributes, settingsCommand.settings);
239
230
  }
240
- return attributes
231
+ return attributes;
241
232
  }
242
233
 
243
234
  toTiledesk(vxmlMessage) {
@@ -245,20 +236,11 @@ class TiledeskTwilioTranslator {
245
236
  }
246
237
 
247
238
  getMessageFromTdMessage(msg) {
248
- let text = "";
249
- if (msg.attributes && msg.attributes.commands && msg.attributes.commands.length > 0 ) {
250
- /*msg.attributes.commands.forEach((command) => {
251
- if(command.type === 'message'){
252
- text= command.message.text
253
- }
254
- })*/
255
- let command = msg.attributes.commands[1];
256
- if (command.type === "message") {
257
- text = command.message.text;
258
- }
259
- }
260
-
261
- return text;
239
+ const commands = msg.attributes?.commands;
240
+ if (!commands || commands.length === 0) return "";
241
+
242
+ const command = commands[1];
243
+ return command?.type === "message" ? command.message.text : "";
262
244
  }
263
245
 
264
246
  getButtonsFromCommand(command) {
@@ -270,20 +252,16 @@ class TiledeskTwilioTranslator {
270
252
  }
271
253
  return buttons;
272
254
  }
255
+
273
256
  getButtonsFromTdMessage(msg) {
274
- let buttons = [];
275
- if (msg.attributes && msg.attributes.commands && msg.attributes.commands.length > 0 ) {
276
- /*msg.attributes.commands.forEach((command) => {
277
- if(command.type === 'message'){
278
- text= command.message.text
279
- }
280
- })*/
281
- let command = msg.attributes.commands[1];
282
- if (command.type === "message" && command.message.attributes && command.message.attributes.attachment && command.message.attributes.attachment.buttons ) {
283
- buttons = command.message.attributes.attachment.buttons.filter( (button) => (button.type === "action") );
284
- }
257
+ const commands = msg.attributes?.commands;
258
+ if (!commands || commands.length === 0) return [];
259
+
260
+ const command = commands[1];
261
+ if (command?.type === "message" && command.message?.attributes?.attachment?.buttons) {
262
+ return command.message.attributes.attachment.buttons.filter((button) => button.type === "action");
285
263
  }
286
- return buttons;
264
+ return [];
287
265
  }
288
266
 
289
267
  async hangupCall(rootEle){
@@ -293,10 +271,10 @@ class TiledeskTwilioTranslator {
293
271
  }
294
272
 
295
273
 
296
- async delayVXMLConverter(rootEle, message, xmlAttributes){
274
+ async delayVXMLConverter(rootEle, message, xmlAttributes, sessionInfo){
297
275
  const command = message.attributes.commands[0]
298
276
 
299
- const prompt = this.promptVXML(rootEle, message, xmlAttributes);
277
+ const prompt = this.promptVXML(rootEle, message, xmlAttributes, sessionInfo);
300
278
 
301
279
  rootEle.ele("Redirect", {method: "POST"}, this.BASE_URL + '/nextblock/' + xmlAttributes.callSid).up()
302
280
 
@@ -304,12 +282,12 @@ class TiledeskTwilioTranslator {
304
282
  }
305
283
 
306
284
 
307
- async playPromptVXMLConverter(rootEle, message, xmlAttributes){
285
+ async playPromptVXMLConverter(rootEle, message, xmlAttributes, sessionInfo){
308
286
 
309
- const prompt = await this.promptVXML(rootEle, message, xmlAttributes);
287
+ const prompt = await this.promptVXML(rootEle, message, xmlAttributes, sessionInfo);
310
288
 
311
- const queryUrl = '?intentName='+ querystring.encode(xmlAttributes.intentName) + '&previousIntentTimestamp='+Date.now();
312
- rootEle.ele("Redirect", {method: "POST"}, this.BASE_URL + '/nextblock/' + xmlAttributes.callSid + queryUrl).up()
289
+ // const queryUrl = '?intentName='+ querystring.encode(xmlAttributes.intentName) + '&previousIntentTimestamp='+Date.now();
290
+ // rootEle.ele("Redirect", {method: "POST"}, this.BASE_URL + '/nextblock/' + xmlAttributes.callSid + queryUrl).up()
313
291
  //prompt.ele("submit", { fetchhint: "safe", expr: "proxyBaseUrl +'/nextblock/' + session.connection.calltoken", method: "post", namelist: "usertext session intentName previousIntentTimestamp" });
314
292
 
315
293
  return rootEle.end({ pretty: true });
@@ -317,7 +295,7 @@ class TiledeskTwilioTranslator {
317
295
  }
318
296
 
319
297
  /** DONE **/
320
- async speechFormVXMLConverter(rootEle, message, xmlAttributes) {
298
+ async speechFormVXMLConverter(rootEle, message, xmlAttributes, sessionInfo) {
321
299
 
322
300
  if(this.voiceProvider === VOICE_PROVIDER.TWILIO){
323
301
  const gather = rootEle.ele("Gather", { input: "speech"})
@@ -337,7 +315,7 @@ class TiledeskTwilioTranslator {
337
315
  gather.att("speechTimeout", Math.round(xmlAttributes.incompleteSpeechTimeout/1000) ).up();
338
316
  }
339
317
 
340
- const prompt = this.promptVXML(gather, message, xmlAttributes);
318
+ const prompt = this.promptVXML(gather, message, xmlAttributes, sessionInfo);
341
319
 
342
320
  const handleNoInputNoMatchQuery = await this.handleNoInputNoMatch(rootEle, message, xmlAttributes);
343
321
  if(handleNoInputNoMatchQuery && handleNoInputNoMatchQuery.queryNoInput){
@@ -346,7 +324,7 @@ class TiledeskTwilioTranslator {
346
324
 
347
325
  }else{
348
326
 
349
- const prompt = await this.promptVXML(rootEle, message, xmlAttributes);
327
+ const prompt = await this.promptVXML(rootEle, message, xmlAttributes, sessionInfo);
350
328
 
351
329
  const record = rootEle.ele("Record", { playBeep: "false"})
352
330
 
@@ -374,7 +352,7 @@ class TiledeskTwilioTranslator {
374
352
  }
375
353
 
376
354
  /** DONE **/
377
- async menuVXMLConverter(rootEle, message, xmlAttributes) {
355
+ async menuVXMLConverter(rootEle, message, xmlAttributes, sessionInfo) {
378
356
  const lastMessageCommand = message.attributes.commands.slice(-3)[0];
379
357
  const options = this.getButtonsFromCommand(lastMessageCommand);
380
358
 
@@ -397,7 +375,7 @@ class TiledeskTwilioTranslator {
397
375
  .att("method", "POST")
398
376
  .att("language", xmlAttributes.TTS_VOICE_LANGUAGE)
399
377
 
400
- const prompt = await this.promptVXML(gather, message, xmlAttributes);
378
+ const prompt = await this.promptVXML(gather, message, xmlAttributes, sessionInfo);
401
379
 
402
380
 
403
381
  if(handleNoInputNoMatchQuery && handleNoInputNoMatchQuery.queryNoInput){
@@ -408,7 +386,7 @@ class TiledeskTwilioTranslator {
408
386
  }
409
387
 
410
388
  /** DONE **/
411
- async dtmfFormVXMLConverter(rootEle, message, xmlAttributes) {
389
+ async dtmfFormVXMLConverter(rootEle, message, xmlAttributes, sessionInfo) {
412
390
 
413
391
  const gather = rootEle.ele("Gather", { input: "dtmf"})
414
392
 
@@ -419,7 +397,7 @@ class TiledeskTwilioTranslator {
419
397
  .att("language", xmlAttributes.TTS_VOICE_LANGUAGE)
420
398
  const settings = await this.optionsVXML(gather, message, xmlAttributes);
421
399
 
422
- const prompt = this.promptVXML(gather, message, xmlAttributes);
400
+ const prompt = await this.promptVXML(gather, message, xmlAttributes, sessionInfo);
423
401
 
424
402
  const handleNoInputNoMatchQuery = await this.handleNoInputNoMatch(rootEle, message, xmlAttributes);
425
403
  if(handleNoInputNoMatchQuery && handleNoInputNoMatchQuery.queryNoInput){
@@ -431,12 +409,12 @@ class TiledeskTwilioTranslator {
431
409
  }
432
410
 
433
411
 
434
- async blindTransferVXMLConverter(rootEle, message, xmlAttributes) {
412
+ async blindTransferVXMLConverter(rootEle, message, xmlAttributes, sessionInfo) {
435
413
  const lastMessageCommand = message.attributes.commands.slice(-3)[0];
436
414
  const options = this.getButtonsFromCommand(lastMessageCommand);
437
415
 
438
- const prompt = this.promptVXML(rootEle, message, xmlAttributes);
439
- const transfer = this.transferVXML(rootEle, message, xmlAttributes)
416
+ const prompt = await this.promptVXML(rootEle, message, xmlAttributes, sessionInfo);
417
+ const transfer = await this.transferVXML(rootEle, message, xmlAttributes)
440
418
 
441
419
 
442
420
  return rootEle.end({ pretty: true });
@@ -464,124 +442,63 @@ class TiledeskTwilioTranslator {
464
442
  rootEle.ele("catch", { event: "connection.disconnect.hangup" })
465
443
  .ele("submit", { fetchhint: "safe", expr: "proxyBaseUrl+'/event/' + session.connection.calltoken + '/hangup'", method: "post", namelist: "intentName previousIntentTimestamp"}).up();
466
444
  rootEle.ele("catch", { event: "error.noresource.asr" })
467
- //.ele("var", { name: "url", expr: "'https://tiledesk-vxml-connector.glitch.me/event" + "/no-resources?callId=" + attributes.callId + "'" }).up()
468
445
  .ele("submit", { fetchhint: "safe", expr: "proxyBaseUrl+'/event/' + session.connection.calltoken + '/no-resources?event=' + application._event", method: "post", namelist: "intentName previousIntentTimestamp" }).up();
469
446
  return rootEle;
470
447
  }
471
448
 
472
449
 
473
450
  async handleNoInputNoMatch(rootEle, msg, attributes){
474
- let intent_noInput = null, intent_noMatch = null
475
- let button_noIput = {}, button_noMatch = {};
476
- let queryNoInput = '', queryNoMatch = ''
477
-
478
- let dtmf_form_element = msg.attributes.commands.find((command) => command.type === SETTING_MESSAGE)
479
- if(!dtmf_form_element){
480
- return;
481
- }
482
-
483
-
484
- if(dtmf_form_element.settings.noInputIntent){
485
- intent_noInput = dtmf_form_element.settings.noInputIntent
486
- button_noIput = {
487
- action: intent_noInput,
488
- value: 'no_input'
489
- }
490
-
491
-
492
- queryNoInput = 'button_action='+button_noIput.action.substring(1);
493
- //rootEle.ele("Redirect", {}, this.BASE_URL + '/handle/' + attributes.callSid + '/no_input'+ queryNoInput)
494
-
495
-
496
- /*element.ele("noinput")
497
- .ele("assign", { name: "menu_choice", expr: "'" + MENU_CHOICE.NO_INPUT + "'", }).up()
498
- .ele("assign", { name: "button", expr: "'" + JSON.stringify(button_noIput) + "'", }).up()
499
- .ele("goto", { next: "#noinput_form" }).up()
500
- .up()
501
-
502
- rootEle.ele("form", { id: "noinput_form" })
503
- .ele("block")
504
- .ele("submit", { fetchhint: "safe", expr: "proxyBaseUrl +'/handle/' + session.connection.calltoken + '/no_input'", method: "post", namelist: "intentName button previousIntentTimestamp"}).up()
505
- .up()
506
- .up();
507
- */
508
-
451
+ const dtmf_form_element = msg.attributes?.commands?.find((command) => command.type === SETTING_MESSAGE);
452
+ if (!dtmf_form_element?.settings) {
453
+ return null;
509
454
  }
510
455
 
456
+ const { noInputIntent, noMatchIntent } = dtmf_form_element.settings;
511
457
 
512
- if(dtmf_form_element.settings.noMatchIntent){
513
- intent_noMatch = dtmf_form_element.settings.noMatchIntent
514
- button_noMatch = {
515
- action: intent_noMatch,
516
- value: 'no_match'
517
- }
518
-
519
- queryNoMatch = 'button_action='+button_noMatch.action.substring(1); //remove '#' from intentId because is not a valid char for XML lang
520
- //rootEle.ele("Redirect", {}, this.BASE_URL + '/handle/' + attributes.callSid + '/no_match'+ queryNoMatch)
521
-
522
- /*element.ele("nomatch")
523
- .ele("assign", { name: "menu_choice", expr: "'" + MENU_CHOICE.NO_MATCH + "'"}).up()
524
- .ele("assign", { name: "button", expr: "'" + JSON.stringify(button_noMatch) + "'", }).up()
525
- .ele("goto", { next: "#nomatch_form" }).up()
526
- .up();
527
-
528
- rootEle.ele("form", { id: "nomatch_form" })
529
- .ele("block")
530
- .ele("submit", { fetchhint: "safe", expr: "proxyBaseUrl +'/handle/' + session.connection.calltoken + '/no_match'", method: "post", namelist: "intentName button previousIntentTimestamp"}).up()
531
- .up()
532
- .up();
533
- */
534
-
535
- }
536
-
537
-
538
- return { queryNoInput: queryNoInput, queryNoMatch: queryNoMatch}
539
-
458
+ // Build query strings for noInput and noMatch intents
459
+ const queryNoInput = noInputIntent ? `button_action=${noInputIntent.substring(1)}` : '';
460
+ const queryNoMatch = noMatchIntent ? `button_action=${noMatchIntent.substring(1)}` : '';
540
461
 
462
+ return { queryNoInput, queryNoMatch };
541
463
  }
542
464
 
543
465
  /* create PROMPT section */
544
- async promptVXML(rootEle, msg, attributes) {
466
+ async promptVXML(rootEle, msg, attributes, sessionInfo) {
545
467
  //let promt = rootEle.ele("prompt", {bargein: attributes.bargein});
546
468
 
547
469
  if (msg.attributes && msg.attributes.commands && msg.attributes.commands.length > 0 ) {
548
470
  let commands = msg.attributes.commands;
549
- let i = 0;
550
- await new Promise((resolve, reject) => {
551
- const that = this
552
- async function execute(command) {
553
- if (command.type === "message") {
554
- //case type: TEXT
555
- if(command.message.type === 'text'){
556
- let text = utils.markdownToTwilioSpeech(command.message.text);
557
- if(that.voiceProvider !== VOICE_PROVIDER.TWILIO){
558
- let voiceMessageUrl = await that.generateTTS(text, attributes)
559
- rootEle.ele('Play', {}, voiceMessageUrl )
560
- }else{
561
- rootEle.ele("Say", { voice: attributes.TTS_VOICE_NAME, language: attributes.TTS_VOICE_LANGUAGE }, text);
562
- }
563
-
564
- }
565
- //case type: FRAME
566
- if(command.message.type === 'frame' && command.message.metadata.src !== ""){
567
- if(command.message.text != ''){
568
- rootEle.ele("Say", { voice: attributes.TTS_VOICE_NAME, language: attributes.TTS_VOICE_LANGUAGE }, text);
569
- }
570
- rootEle.ele('Play', {}, command.message.metadata.src )
471
+
472
+ for (const command of commands) {
473
+ if (command.type === "message") {
474
+ //case type: TEXT
475
+ if(command.message.type === 'text'){
476
+ let text = utils.markdownToTwilioSpeech(command.message.text);
477
+ if(this.voiceProvider !== VOICE_PROVIDER.TWILIO){
478
+ let voiceMessageUrl = await this.generateTTS(text, attributes, sessionInfo);
479
+ rootEle.ele('Play', { loop: 0}, voiceMessageUrl );
480
+ this.lastCallSidVerb[attributes.callSid] = "play";
481
+ }else{
482
+ rootEle.ele("Say", { voice: attributes.TTS_VOICE_NAME, language: attributes.TTS_VOICE_LANGUAGE }, text);
483
+ this.lastCallSidVerb[attributes.callSid] = "say";
571
484
  }
572
- } else if (command.type === "wait" && command.time !== 0) {
573
- rootEle.ele("Pause", { length: command.time/1000 }).up();
485
+
574
486
  }
575
-
576
- i += 1;
577
- if (i < commands.length) {
578
- execute(commands[i]);
579
- } else {
580
- resolve(true);
487
+ //case type: FRAME
488
+ if(command.message.type === 'frame' && command.message.metadata.src !== ""){
489
+ if(command.message.text != ''){
490
+ let text = utils.markdownToTwilioSpeech(command.message.text);
491
+ rootEle.ele("Say", { voice: attributes.TTS_VOICE_NAME, language: attributes.TTS_VOICE_LANGUAGE }, text);
492
+ }
493
+ rootEle.ele('Play', { loop: 0 }, command.message.metadata.src )
494
+ this.lastCallSidVerb[attributes.callSid] = "play";
495
+
581
496
  }
497
+ } else if (command.type === "wait" && command.time !== 0) {
498
+ rootEle.ele("Pause", { length: command.time/1000 }).up();
499
+ this.lastCallSidVerb[attributes.callSid] = "pause";
582
500
  }
583
- execute(commands[0]); //START render first message
584
- });
501
+ }
585
502
  }
586
503
  return rootEle;
587
504
  }
@@ -640,16 +557,16 @@ class TiledeskTwilioTranslator {
640
557
  return transfer.up()
641
558
  }
642
559
 
643
- async generateTTS(text, attributes){
560
+ async generateTTS(text, attributes, sessionInfo){
644
561
  let audioData = null;
645
562
  try {
646
563
  switch(this.voiceProvider){
647
564
  case VOICE_PROVIDER.OPENAI:
648
- let GPT_KEY = this.integrations.find((el => el.type === VOICE_PROVIDER.OPENAI))?.key
565
+ let GPT_KEY = sessionInfo.integrations.find((el => el.type === VOICE_PROVIDER.OPENAI))?.key
649
566
  audioData = await this.aiService.textToSpeech(text, attributes.TTS_VOICE_NAME, attributes.TTS_MODEL, GPT_KEY)
650
567
  break;
651
568
  case VOICE_PROVIDER.ELEVENLABS:
652
- let ELEVENLABS_APIKEY = this.integrations.find((el => el.type === VOICE_PROVIDER.ELEVENLABS))?.key
569
+ let ELEVENLABS_APIKEY = sessionInfo.integrations.find((el => el.type === VOICE_PROVIDER.ELEVENLABS))?.key
653
570
  audioData = await this.aiService.textToSpeechElevenLabs(text, attributes.TTS_VOICE_NAME, attributes.TTS_MODEL, attributes.TTS_VOICE_LANGUAGE, ELEVENLABS_APIKEY)
654
571
  break;
655
572
  default:
@@ -660,7 +577,7 @@ class TiledeskTwilioTranslator {
660
577
  throw new SttError('TTS_FAILED', 'TTS returned no audio data');
661
578
  }
662
579
 
663
- let fileUrl = await this.uploadService.upload(attributes.callSid, audioData, this.user)
580
+ let fileUrl = await this.uploadService.upload(attributes.callSid, audioData, sessionInfo.user)
664
581
  winston.debug('(voice) Audio Message url captured after TTS -->', fileUrl)
665
582
  return fileUrl
666
583
  } catch (error) {
@@ -0,0 +1,24 @@
1
+ const axios = require("axios").default;
2
+ const logger = require('./logger');
3
+
4
+ class FileUtils {
5
+
6
+
7
+ async downloadFromUrl(url) {
8
+ try {
9
+ const resbody = await axios({
10
+ url: url,
11
+ responseType: 'arraybuffer',
12
+ method: 'GET'
13
+ });
14
+ const buffer = Buffer.from(resbody.data, 'binary');
15
+ return buffer;
16
+ } catch (err) {
17
+ throw err;
18
+ }
19
+ }
20
+ }
21
+
22
+ var fileUtils = new FileUtils();
23
+
24
+ module.exports = fileUtils;
@@ -0,0 +1,32 @@
1
+ var appRoot = require('app-root-path');
2
+ var winston = require('winston');
3
+ var level = process.env.VOICE_TWILIO_LOG || 'info';
4
+
5
+ var options = {
6
+ file: {
7
+ level:level ,
8
+ filename: `${appRoot}/logs/app.log`,
9
+ handleExceptions: true,
10
+ json: false,
11
+ maxsize: 5242880, // 5MB
12
+ maxFiles: 5,
13
+ colorize: false,
14
+ format: winston.format.simple()
15
+ },
16
+ console: {
17
+ level: level,
18
+ handleExceptions: true,
19
+ json: true,
20
+ colorize: true,
21
+ format: winston.format.simple()
22
+ },
23
+ };
24
+
25
+ var logger = winston.createLogger({
26
+ transports: [
27
+ new winston.transports.Console(options.console)
28
+ ],
29
+ exitOnError: false, // do not exit on handled exceptions
30
+ });
31
+
32
+ module.exports = logger;
@@ -73,27 +73,12 @@ function messageType(msgType, message, user) {
73
73
  }
74
74
 
75
75
  function infoMessageType(msg){
76
- if(msg && msg.attributes.messagelabel && msg.attributes.messagelabel.key === INFO_MESSAGE_TYPE.MEMBER_JOINED_GROUP){
77
- return INFO_MESSAGE_TYPE.MEMBER_JOINED_GROUP
78
- }
79
- if(msg && msg.attributes.messagelabel && msg.attributes.messagelabel.key === INFO_MESSAGE_TYPE.CHAT_CLOSED){
80
- return INFO_MESSAGE_TYPE.CHAT_CLOSED
81
- }
82
- if(msg && msg.attributes.messagelabel && msg.attributes.messagelabel.key === INFO_MESSAGE_TYPE.CHAT_REOPENED){
83
- return INFO_MESSAGE_TYPE.CHAT_REOPENED
84
- }
85
- if(msg && msg.attributes.messagelabel && msg.attributes.messagelabel.key === INFO_MESSAGE_TYPE.TOUCHING_OPERATOR){
86
- return INFO_MESSAGE_TYPE.TOUCHING_OPERATOR
87
- }
88
- if(msg && msg.attributes.messagelabel && msg.attributes.messagelabel.key === INFO_MESSAGE_TYPE.LEAD_UPDATED){
89
- return INFO_MESSAGE_TYPE.LEAD_UPDATED
90
- }
91
- if(msg && msg.attributes.messagelabel && msg.attributes.messagelabel.key === INFO_MESSAGE_TYPE.MEMBER_LEFT_GROUP){
92
- return INFO_MESSAGE_TYPE.MEMBER_LEFT_GROUP
93
- }
94
- if(msg && msg.attributes.messagelabel && msg.attributes.messagelabel.key === INFO_MESSAGE_TYPE.LIVE_PAGE){
95
- return INFO_MESSAGE_TYPE.LIVE_PAGE
96
- }
76
+ const key = msg?.attributes?.messagelabel?.key;
77
+ if (!key) return null;
78
+
79
+ // Return the key directly if it's a valid INFO_MESSAGE_TYPE
80
+ const validTypes = Object.values(INFO_MESSAGE_TYPE);
81
+ return validTypes.includes(key) ? key : null;
97
82
  }
98
83
 
99
84
  module.exports = {isCarousel, isImage, isFile, isAudio, isInfo, isInfoSupport, messageType, infoMessageType}