@tiledesk/tiledesk-voice-twilio-connector 0.1.26-rc9 → 0.1.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/logs/app.log CHANGED
@@ -2988,3 +2988,95 @@ info: (voice) Starting Manage Route
2988
2988
  info: (voice)-MANAGE API_URL: https://tiledesk-server-pre.herokuapp.com
2989
2989
  info: (voice)-MANAGE BASE_URL: https://pw99h2hr-3000.euw.devtunnels.ms
2990
2990
  info: (voice)-MANAGE redis_client: [object Object]
2991
+ info: (voice) Starting VOICE TWILIO App
2992
+ info: (voice) Starting Manage Route
2993
+ info: (voice)-MANAGE API_URL: https://stage.eks.tiledesk.com/api
2994
+ info: (voice)-MANAGE BASE_URL: https://pw99h2hr-3000.euw.devtunnels.ms
2995
+ info: (voice)-MANAGE redis_client: [object Object]
2996
+ info: (voice) Starting VOICE TWILIO App
2997
+ info: (voice) Starting Manage Route
2998
+ info: (voice)-MANAGE API_URL: https://stage.eks.tiledesk.com/api
2999
+ info: (voice)-MANAGE BASE_URL: https://pw99h2hr-3000.euw.devtunnels.ms
3000
+ info: (voice)-MANAGE redis_client: [object Object]
3001
+ error: undefined {"name":"MongoServerSelectionError","reason":{"commonWireVersion":null,"compatibilityError":null,"compatible":true,"heartbeatFrequencyMS":10000,"localThresholdMS":15,"logicalSessionTimeoutMinutes":null,"maxElectionId":null,"maxSetVersion":null,"servers":{},"setName":null,"stale":false,"type":"Single"}}
3002
+ info: (voice) Starting VOICE TWILIO App
3003
+ info: (voice) Starting Manage Route
3004
+ info: (voice)-MANAGE API_URL: https://stage.eks.tiledesk.com/api
3005
+ info: (voice)-MANAGE BASE_URL: https://pw99h2hr-3000.euw.devtunnels.ms
3006
+ info: (voice)-MANAGE redis_client: [object Object]
3007
+ error: undefined {"name":"MongoServerSelectionError","reason":{"commonWireVersion":null,"compatibilityError":null,"compatible":true,"heartbeatFrequencyMS":10000,"localThresholdMS":15,"logicalSessionTimeoutMinutes":null,"maxElectionId":null,"maxSetVersion":null,"servers":{},"setName":null,"stale":false,"type":"Single"}}
3008
+ info: (voice) Starting VOICE TWILIO App
3009
+ info: (voice) Starting Manage Route
3010
+ info: (voice)-MANAGE API_URL: https://stage.eks.tiledesk.com/api
3011
+ info: (voice)-MANAGE BASE_URL: https://pw99h2hr-3000.euw.devtunnels.ms
3012
+ info: (voice)-MANAGE redis_client: [object Object]
3013
+ info: (voice) Starting VOICE TWILIO App
3014
+ info: (voice) Starting Manage Route
3015
+ info: (voice)-MANAGE API_URL: https://stage.eks.tiledesk.com/api
3016
+ info: (voice)-MANAGE BASE_URL: https://pw99h2hr-3000.euw.devtunnels.ms
3017
+ info: (voice)-MANAGE redis_client: [object Object]
3018
+ info: (voice) Starting VOICE TWILIO App
3019
+ info: (voice) Starting Manage Route
3020
+ info: (voice)-MANAGE API_URL: https://stage.eks.tiledesk.com/api
3021
+ info: (voice)-MANAGE BASE_URL: https://pw99h2hr-3000.euw.devtunnels.ms
3022
+ info: (voice)-MANAGE redis_client: [object Object]
3023
+ error: undefined {"name":"MongoServerSelectionError","reason":{"commonWireVersion":null,"compatibilityError":null,"compatible":true,"heartbeatFrequencyMS":10000,"localThresholdMS":15,"logicalSessionTimeoutMinutes":null,"maxElectionId":null,"maxSetVersion":null,"servers":{},"setName":null,"stale":false,"type":"Single"}}
3024
+ info: (voice) Starting VOICE TWILIO App
3025
+ info: (voice) Starting Manage Route
3026
+ info: (voice)-MANAGE API_URL: https://stage.eks.tiledesk.com/api
3027
+ info: (voice)-MANAGE BASE_URL: https://pw99h2hr-3000.euw.devtunnels.ms
3028
+ info: (voice)-MANAGE redis_client: [object Object]
3029
+ info: (voice) Starting VOICE TWILIO App
3030
+ info: (voice) Starting Manage Route
3031
+ info: (voice)-MANAGE API_URL: https://stage.eks.tiledesk.com/api
3032
+ info: (voice)-MANAGE BASE_URL: https://pw99h2hr-3000.euw.devtunnels.ms
3033
+ info: (voice)-MANAGE redis_client: [object Object]
3034
+ info: (voice) Starting VOICE TWILIO App
3035
+ info: (voice) Starting Manage Route
3036
+ info: (voice)-MANAGE API_URL: https://stage.eks.tiledesk.com/api
3037
+ info: (voice)-MANAGE BASE_URL: https://pw99h2hr-3000.euw.devtunnels.ms
3038
+ info: (voice)-MANAGE redis_client: [object Object]
3039
+ error: undefined {"name":"MongoServerSelectionError","reason":{"commonWireVersion":null,"compatibilityError":null,"compatible":true,"heartbeatFrequencyMS":10000,"localThresholdMS":15,"logicalSessionTimeoutMinutes":null,"maxElectionId":null,"maxSetVersion":null,"servers":{},"setName":null,"stale":false,"type":"Single"}}
3040
+ info: (voice) Starting VOICE TWILIO App
3041
+ info: (voice) Starting Manage Route
3042
+ info: (voice)-MANAGE API_URL: https://stage.eks.tiledesk.com/api
3043
+ info: (voice)-MANAGE BASE_URL: https://pw99h2hr-3000.euw.devtunnels.ms
3044
+ info: (voice)-MANAGE redis_client: [object Object]
3045
+ error: undefined {"name":"MongoServerSelectionError","reason":{"commonWireVersion":null,"compatibilityError":null,"compatible":true,"heartbeatFrequencyMS":10000,"localThresholdMS":15,"logicalSessionTimeoutMinutes":null,"maxElectionId":null,"maxSetVersion":null,"servers":{},"setName":null,"stale":false,"type":"Single"}}
3046
+ info: (voice) Starting VOICE TWILIO App
3047
+ info: (voice) Starting Manage Route
3048
+ info: (voice)-MANAGE API_URL: https://stage.eks.tiledesk.com/api
3049
+ info: (voice)-MANAGE BASE_URL: https://pw99h2hr-3000.euw.devtunnels.ms
3050
+ info: (voice)-MANAGE redis_client: [object Object]
3051
+ info: (voice) Starting VOICE TWILIO App
3052
+ info: (voice) Starting Manage Route
3053
+ info: (voice)-MANAGE API_URL: https://stage.eks.tiledesk.com/api
3054
+ info: (voice)-MANAGE BASE_URL: https://pw99h2hr-3000.euw.devtunnels.ms
3055
+ info: (voice)-MANAGE redis_client: [object Object]
3056
+ info: (voice) Starting VOICE TWILIO App
3057
+ info: (voice) Starting Manage Route
3058
+ info: (voice)-MANAGE API_URL: https://stage.eks.tiledesk.com/api
3059
+ info: (voice)-MANAGE BASE_URL: https://pw99h2hr-3000.euw.devtunnels.ms
3060
+ info: (voice)-MANAGE redis_client: [object Object]
3061
+ error: undefined {"name":"MongoServerSelectionError","reason":{"commonWireVersion":null,"compatibilityError":null,"compatible":true,"heartbeatFrequencyMS":10000,"localThresholdMS":15,"logicalSessionTimeoutMinutes":null,"maxElectionId":null,"maxSetVersion":null,"servers":{},"setName":null,"stale":false,"type":"Single"}}
3062
+ info: (voice) Starting VOICE TWILIO App
3063
+ info: (voice) Starting Manage Route
3064
+ info: (voice)-MANAGE API_URL: https://stage.eks.tiledesk.com/api
3065
+ info: (voice)-MANAGE BASE_URL: https://pw99h2hr-3000.euw.devtunnels.ms
3066
+ info: (voice)-MANAGE redis_client: [object Object]
3067
+ info: (voice) Starting VOICE TWILIO App
3068
+ info: (voice) Starting Manage Route
3069
+ info: (voice)-MANAGE API_URL: https://stage.eks.tiledesk.com/api
3070
+ info: (voice)-MANAGE BASE_URL: https://pw99h2hr-3000.euw.devtunnels.ms
3071
+ info: (voice)-MANAGE redis_client: [object Object]
3072
+ error: undefined {"name":"MongoServerSelectionError","reason":{"commonWireVersion":null,"compatibilityError":null,"compatible":true,"heartbeatFrequencyMS":10000,"localThresholdMS":15,"logicalSessionTimeoutMinutes":null,"maxElectionId":null,"maxSetVersion":null,"servers":{},"setName":null,"stale":false,"type":"Single"}}
3073
+ info: (voice) Starting VOICE TWILIO App
3074
+ info: (voice) Starting Manage Route
3075
+ info: (voice)-MANAGE API_URL: https://stage.eks.tiledesk.com/api
3076
+ info: (voice)-MANAGE BASE_URL: https://pw99h2hr-3000.euw.devtunnels.ms
3077
+ info: (voice)-MANAGE redis_client: [object Object]
3078
+ info: (voice) Starting VOICE TWILIO App
3079
+ info: (voice) Starting Manage Route
3080
+ info: (voice)-MANAGE API_URL: https://stage.eks.tiledesk.com/api
3081
+ info: (voice)-MANAGE BASE_URL: https://pw99h2hr-3000.euw.devtunnels.ms
3082
+ info: (voice)-MANAGE redis_client: [object Object]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiledesk/tiledesk-voice-twilio-connector",
3
- "version": "0.1.26-rc9",
3
+ "version": "0.1.27",
4
4
  "description": "Tiledesk VOICE Twilio connector",
5
5
  "license": "MIT",
6
6
  "author": "Gabriele Panico",
@@ -36,6 +36,7 @@
36
36
  "redis": "^4.6.13",
37
37
  "redis-lock": "^1.0.0",
38
38
  "redlock": "^4.2.0",
39
+ "remove-markdown": "^0.6.2",
39
40
  "twilio": "^5.2.1",
40
41
  "uuid": "^8.3.2",
41
42
  "winston": "^3.3.3",
@@ -55,7 +55,7 @@ router.get("/", async (req, res) => {
55
55
 
56
56
  router.get('/configure', async (req, res) => {
57
57
 
58
- winston.verbose("(voice) /configure :params", req.query);
58
+ winston.debug("(voice) /configure :params", req.query);
59
59
 
60
60
  let project_id = req.query.project_id;
61
61
  let token = req.query.token;
@@ -89,7 +89,7 @@ router.get('/configure', async (req, res) => {
89
89
  let CONTENT_KEY = CHANNEL_NAME + "-" + project_id;
90
90
 
91
91
  let settings = await db.get(CONTENT_KEY);
92
- winston.verbose("(voice) settings: ", settings);
92
+ winston.debug("(voice) settings: ", settings);
93
93
 
94
94
  // get departments
95
95
  const tdChannel = new TiledeskChannel({
@@ -157,7 +157,7 @@ router.get('/configure', async (req, res) => {
157
157
  })
158
158
 
159
159
  router.post('/update', async (req, res) => {
160
- winston.verbose("(voice) /update", req.body);
160
+ winston.debug("(voice) /update", req.body);
161
161
 
162
162
  let project_id = req.body.project_id;
163
163
  let token = req.body.token;
@@ -168,7 +168,7 @@ router.post('/update', async (req, res) => {
168
168
  let CONTENT_KEY = CHANNEL_NAME + "-" + project_id;
169
169
  let settings = await db.get(CONTENT_KEY);
170
170
 
171
- winston.verbose("(voice) /update settings", settings);
171
+ winston.debug("(voice) /update settings", settings);
172
172
 
173
173
  let proxy_url = BASE_URL + "/webhook/" + project_id;
174
174
  let status_url = BASE_URL + "/twilio/status";
@@ -264,7 +264,7 @@ router.post('/update', async (req, res) => {
264
264
 
265
265
  router.post('/disconnect', async (req, res) => {
266
266
 
267
- winston.verbose("(voice) /disconnect")
267
+ winston.debug("(voice) /disconnect")
268
268
 
269
269
  let project_id = req.body.project_id;
270
270
  let token = req.body.token;
@@ -273,7 +273,7 @@ router.post('/disconnect', async (req, res) => {
273
273
 
274
274
  let CONTENT_KEY = CHANNEL_NAME + "-" + project_id;
275
275
  await db.remove(CONTENT_KEY);
276
- winston.verbose("(voice) Content deleted.");
276
+ winston.debug("(voice) Content deleted.");
277
277
 
278
278
  let proxy_url = BASE_URL + "/webhook/" + project_id;
279
279
  let status_url = BASE_URL + "/twilio/status";
@@ -327,7 +327,7 @@ async function subscribe(token, project_id){
327
327
  return new Promise((resolve, reject)=> {
328
328
  tdClient.subscribe(subscription_info).then(async (data)=> {
329
329
  let subscription = data;
330
- winston.verbose("(voice) Subscription: ", subscription)
330
+ winston.debug("(voice) Subscription: ", subscription)
331
331
 
332
332
  let settings = {
333
333
  project_id: project_id,
@@ -352,7 +352,7 @@ async function unsubscribe(token, project_id, subscriptionId){
352
352
 
353
353
  return new Promise((resolve, reject)=> {
354
354
  tdClient.unsubscribe(subscriptionId).then(async (data)=> {
355
- winston.verbose("(voice) Subscription: ", data)
355
+ winston.debug("(voice) Subscription: ", data)
356
356
  resolve(data)
357
357
  }).catch((error)=> {reject(error) })
358
358
 
@@ -76,7 +76,7 @@ class KVBaseMongo {
76
76
  resolve(doc.value);
77
77
  }
78
78
  else {
79
- winston.verbose("No Doc found!");
79
+ winston.debug("No Doc found!");
80
80
  resolve(null);
81
81
  }
82
82
  }
@@ -246,17 +246,17 @@ class TiledeskChannel {
246
246
 
247
247
  /*SKIP INFO MESSAGES*/
248
248
  if(utils.messageType(TYPE_MESSAGE.INFO, message)){
249
- winston.verbose("> SKIPPING INFO message: " + JSON.stringify(message) );
249
+ winston.debug("> SKIPPING INFO message: " + JSON.stringify(message) );
250
250
  return;
251
251
  }
252
252
 
253
253
  /*SKIP CURRENT USER MESSAGES*/
254
254
  if (message.sender.indexOf("vxml") > -1) {
255
- winston.verbose("> SKIPPING ECHO message: " + JSON.stringify(message) );
255
+ winston.debug("> SKIPPING ECHO message: " + JSON.stringify(message) );
256
256
  return;
257
257
  }
258
258
 
259
- winston.verbose("> SAVE message TO QUEUE: " + JSON.stringify(message) );
259
+ winston.debug("> SAVE message TO QUEUE: " + JSON.stringify(message) );
260
260
 
261
261
  let conversation_id = message.recipient
262
262
  //PUBLISH MESSAGE TO REDIS TOPIC WITH KET tiledesk:queue:+conversation_id
@@ -266,7 +266,7 @@ class TiledeskChannel {
266
266
  /** SUBSCRIBE TO REDIS TOPIC */
267
267
  async subscribeToTopic(conversation_id){
268
268
  const topic = `tiledesk:conversation:${conversation_id}`;
269
- console.log("subscribeToTopic: " + topic);
269
+ // console.log("subscribeToTopic: " + topic);
270
270
 
271
271
  // duplichi il client principale
272
272
  const subscriber = this.redis_client.duplicate();
@@ -3,7 +3,8 @@ const winston = require("../winston");
3
3
  const xmlbuilder = require("xmlbuilder");
4
4
  const querystring = require("querystring");
5
5
 
6
- const utils = require("./utils-message.js");
6
+ const utils = require("./utils.js");
7
+ const utils_message = require("./utils-message.js");
7
8
  const MENU_CHOICE = require("./constants.js").MENU_CHOICE;
8
9
  const WAIT_MESSAGE = require("./constants.js").WAIT_MESSAGE;
9
10
  const TEXT_MESSAGE = require("./constants.js").TEXT_MESSAGE;
@@ -11,11 +12,16 @@ const SETTING_MESSAGE = require('./constants').SETTING_MESSAGE
11
12
  const CHANNEL_NAME = require('./constants').CHANNEL_NAME
12
13
  const VOICE_PROVIDER = require('./constants').VOICE_PROVIDER;
13
14
  const OPENAI_SETTINGS = require('./constants').OPENAI_SETTINGS;
15
+ const ELEVENLABS_SETTINGS = require('./constants').ELEVENLABS_SETTINGS;
14
16
 
15
17
  const TYPE_ACTION_VXML = require('./constants').TYPE_ACTION_VXML
16
18
  const TYPE_MESSAGE = require('./constants').TYPE_MESSAGE
17
19
  const INFO_MESSAGE_TYPE = require('./constants').INFO_MESSAGE_TYPE
18
20
 
21
+ const voiceEventEmitter = require('./services/voiceEventEmitter');
22
+
23
+ const { SttError } = require('./errors');
24
+
19
25
  class TiledeskTwilioTranslator {
20
26
  /**
21
27
  * Constructor for TiledeskVXMLTranslator
@@ -67,8 +73,8 @@ class TiledeskTwilioTranslator {
67
73
  this.voiceProvider = VOICE_PROVIDER.TWILIO
68
74
  if(flowAttributes.VOICE_PROVIDER){
69
75
  this.voiceProvider = flowAttributes.VOICE_PROVIDER
70
-
71
76
  }
77
+ vxmlAttributes.VOICE_PROVIDER = this.voiceProvider;
72
78
 
73
79
  // IF VOICE_PROVIDER is TWILIO --> default values is on user account twilio settings
74
80
  // IF VOICE_PROVIDER is OPENAI --> set default values from constants
@@ -77,13 +83,21 @@ class TiledeskTwilioTranslator {
77
83
  vxmlAttributes.TTS_MODEL = flowAttributes.TTS_MODEL? flowAttributes.TTS_MODEL : OPENAI_SETTINGS.TTS_MODEL;
78
84
  vxmlAttributes.STT_MODEL = flowAttributes.STT_MODEL? flowAttributes.STT_MODEL : OPENAI_SETTINGS.STT_MODEL;
79
85
  }
80
-
86
+
87
+ // IF VOICE_PROVIDER is ELEVENLABS --> default values is on user account twilio settings
88
+ // IF VOICE_PROVIDER is ELEVENLABS --> set default values from constants
89
+ if(this.voiceProvider === VOICE_PROVIDER.ELEVENLABS){
90
+ vxmlAttributes.TTS_VOICE_NAME = flowAttributes.TTS_VOICE_NAME? flowAttributes.TTS_VOICE_NAME : ELEVENLABS_SETTINGS.TTS_VOICE_NAME;
91
+ vxmlAttributes.TTS_MODEL = flowAttributes.TTS_MODEL? flowAttributes.TTS_MODEL : ELEVENLABS_SETTINGS.TTS_MODEL;
92
+ vxmlAttributes.TTS_VOICE_LANGUAGE = flowAttributes.TTS_VOICE_LANGUAGE? flowAttributes.TTS_VOICE_LANGUAGE : ELEVENLABS_SETTINGS.TTS_VOICE_LANGUAGE;
93
+ vxmlAttributes.STT_MODEL = flowAttributes.STT_MODEL? flowAttributes.STT_MODEL : ELEVENLABS_SETTINGS.STT_MODEL;
94
+ }
81
95
 
82
96
  }
83
97
 
84
-
85
-
86
98
  winston.debug("[TiledeskVXMLTranslator] manageVoiceAttributes: vxmlAttributes returned:", vxmlAttributes);
99
+ voiceEventEmitter.emit('saveSettings', vxmlAttributes);
100
+
87
101
  return vxmlAttributes
88
102
  }
89
103
 
@@ -110,9 +124,9 @@ class TiledeskTwilioTranslator {
110
124
 
111
125
 
112
126
  //MANAGE CLOSE info message
113
- const isInfoSupport = utils.messageType(TYPE_MESSAGE.INFO_SUPPORT, msg)
127
+ const isInfoSupport = utils_message.messageType(TYPE_MESSAGE.INFO_SUPPORT, msg)
114
128
  winston.debug("[TiledeskVXMLTranslator] isInfoSupport:"+ isInfoSupport);
115
- if(isInfoSupport && utils.infoMessageType(msg) === INFO_MESSAGE_TYPE.CHAT_CLOSED){
129
+ if(isInfoSupport && utils_message.infoMessageType(msg) === INFO_MESSAGE_TYPE.CHAT_CLOSED){
116
130
  const hangUp = this.hangupCall(xml)
117
131
  return hangUp;
118
132
  }
@@ -281,6 +295,9 @@ class TiledeskTwilioTranslator {
281
295
 
282
296
  async delayVXMLConverter(rootEle, message, xmlAttributes){
283
297
  const command = message.attributes.commands[0]
298
+
299
+ const prompt = this.promptVXML(rootEle, message, xmlAttributes);
300
+
284
301
  rootEle.ele("Redirect", {method: "POST"}, this.BASE_URL + '/nextblock/' + xmlAttributes.callSid).up()
285
302
 
286
303
  return rootEle.end({ pretty: true });
@@ -289,7 +306,7 @@ class TiledeskTwilioTranslator {
289
306
 
290
307
  async playPromptVXMLConverter(rootEle, message, xmlAttributes){
291
308
 
292
- const prompt = this.promptVXML(rootEle, message, xmlAttributes);
309
+ const prompt = await this.promptVXML(rootEle, message, xmlAttributes);
293
310
 
294
311
  const queryUrl = '?intentName='+ querystring.encode(xmlAttributes.intentName) + '&previousIntentTimestamp='+Date.now();
295
312
  rootEle.ele("Redirect", {method: "POST"}, this.BASE_URL + '/nextblock/' + xmlAttributes.callSid + queryUrl).up()
@@ -303,20 +320,21 @@ class TiledeskTwilioTranslator {
303
320
  async speechFormVXMLConverter(rootEle, message, xmlAttributes) {
304
321
 
305
322
  if(this.voiceProvider === VOICE_PROVIDER.TWILIO){
306
-
307
323
  const gather = rootEle.ele("Gather", { input: "speech"})
308
324
 
309
325
  const queryUrl = '?intentName='+ querystring.encode(xmlAttributes.intentName) + "&previousIntentTimestamp="+Date.now();
310
- gather.att("action", this.BASE_URL + '/speechresult/' + xmlAttributes.callSid + queryUrl)
326
+ gather.att("action", this.BASE_URL + '/nextBlock/' + xmlAttributes.callSid + queryUrl)
327
+ // gather.att("action", this.BASE_URL + '/speechresult/' + xmlAttributes.callSid + queryUrl)
311
328
  .att("method", "POST")
312
329
  .att("language", xmlAttributes.TTS_VOICE_LANGUAGE)
313
330
  .att('speechTimeout', "auto")
331
+ .att("enhanced", "true") // enable enhanced recognition
314
332
 
315
333
  //if(xmlAttributes && xmlAttributes.noInputTimeout){
316
- // gather.att("timeout", xmlAttributes.noInputTimeout/1000 ).up();
334
+ // gather.att("timeout", Math.round(xmlAttributes.noInputTimeout/1000) ).up();
317
335
  //}
318
336
  if(xmlAttributes && xmlAttributes.incompleteSpeechTimeout){
319
- gather.att("speechTimeout", xmlAttributes.incompleteSpeechTimeout/1000 ).up();
337
+ gather.att("speechTimeout", Math.round(xmlAttributes.incompleteSpeechTimeout/1000) ).up();
320
338
  }
321
339
 
322
340
  const prompt = this.promptVXML(gather, message, xmlAttributes);
@@ -339,15 +357,16 @@ class TiledeskTwilioTranslator {
339
357
  }
340
358
 
341
359
  record
342
- //.att("action", this.BASE_URL + '/record/' + xmlAttributes.callSid + queryUrl)
360
+ .att("action", this.BASE_URL + '/record/action/' + xmlAttributes.callSid + queryUrl)
343
361
  .att("method", "POST")
344
362
  .att("trim", "trim-silence")
345
- .att("recordingStatusCallback", this.BASE_URL + '/record/' + xmlAttributes.callSid + queryUrl)
363
+ .att("timeout", "2")
364
+ .att("recordingStatusCallback", this.BASE_URL + '/record/callback/' + xmlAttributes.callSid + queryUrl)
346
365
  .att("recordingStatusCallbackMethod", "POST")
347
366
 
348
- if(xmlAttributes && xmlAttributes.noInputTimeout){
349
- record.att("timeout", xmlAttributes.noInputTimeout/1000 ).up();
350
- }
367
+ // if(xmlAttributes && xmlAttributes.noInputTimeout){
368
+ // record.att("timeout", xmlAttributes.noInputTimeout/1000 ).up();
369
+ // }
351
370
 
352
371
  }
353
372
 
@@ -366,13 +385,13 @@ class TiledeskTwilioTranslator {
366
385
  let queryUrl = '?intentName='+ querystring.encode(xmlAttributes.intentName) + '&previousIntentTimestamp='+Date.now() + '&menu_options=' + menu_options;
367
386
  const handleNoInputNoMatchQuery = await this.handleNoInputNoMatch(rootEle, message, xmlAttributes);
368
387
  if(handleNoInputNoMatchQuery && handleNoInputNoMatchQuery.queryNoMatch){
369
- queryUrl += '&button_action='+ handleNoInputNoMatchQuery.queryNoMatch.split('button_action=')[1]
388
+ queryUrl += '&'+ handleNoInputNoMatchQuery.queryNoMatch
370
389
  }
371
390
 
372
391
  const gather = rootEle.ele("Gather", { input: "dtmf"})
373
392
 
374
393
 
375
- gather.att("timeout", xmlAttributes.noInputTimeout/1000 )
394
+ gather.att("timeout", Math.round(xmlAttributes.noInputTimeout/1000) )
376
395
  .att("numDigits", "1" )
377
396
  .att("action", this.BASE_URL + '/menublock/' + xmlAttributes.callSid + queryUrl)
378
397
  .att("method", "POST")
@@ -394,7 +413,7 @@ class TiledeskTwilioTranslator {
394
413
  const gather = rootEle.ele("Gather", { input: "dtmf"})
395
414
 
396
415
  const queryUrl = '?intentName='+ querystring.encode(xmlAttributes.intentName) + "&previousIntentTimestamp="+Date.now();
397
- gather.att("timeout", xmlAttributes.noInputTimeout/1000 )
416
+ gather.att("timeout", Math.round(xmlAttributes.noInputTimeout/1000) )
398
417
  .att("action", this.BASE_URL + '/menublock/' + xmlAttributes.callSid + queryUrl)
399
418
  .att("method", "POST")
400
419
  .att("language", xmlAttributes.TTS_VOICE_LANGUAGE)
@@ -470,7 +489,7 @@ class TiledeskTwilioTranslator {
470
489
  }
471
490
 
472
491
 
473
- queryNoInput = 'intentName='+ querystring.encode(attributes.intentName) + '&previousIntentTimestamp='+Date.now() + '&button_action='+button_noIput.action.substring(1);
492
+ queryNoInput = 'button_action='+button_noIput.action.substring(1);
474
493
  //rootEle.ele("Redirect", {}, this.BASE_URL + '/handle/' + attributes.callSid + '/no_input'+ queryNoInput)
475
494
 
476
495
 
@@ -497,7 +516,7 @@ class TiledeskTwilioTranslator {
497
516
  value: 'no_match'
498
517
  }
499
518
 
500
- queryNoMatch = 'intentName='+ querystring.encode(attributes.intentName) + '&previousIntentTimestamp='+Date.now() + '&button_action='+button_noMatch.action.substring(1); //remove '#' from intentId because is not a valid char for XML lang
519
+ queryNoMatch = 'button_action='+button_noMatch.action.substring(1); //remove '#' from intentId because is not a valid char for XML lang
501
520
  //rootEle.ele("Redirect", {}, this.BASE_URL + '/handle/' + attributes.callSid + '/no_match'+ queryNoMatch)
502
521
 
503
522
  /*element.ele("nomatch")
@@ -534,19 +553,19 @@ class TiledeskTwilioTranslator {
534
553
  if (command.type === "message") {
535
554
  //case type: TEXT
536
555
  if(command.message.type === 'text'){
556
+ let text = utils.markdownToTwilioSpeech(command.message.text);
537
557
  if(that.voiceProvider !== VOICE_PROVIDER.TWILIO){
538
- let voiceMessageUrl = await that.generateTTS(command.message.text, attributes)
558
+ let voiceMessageUrl = await that.generateTTS(text, attributes)
539
559
  rootEle.ele('Play', {}, voiceMessageUrl )
540
560
  }else{
541
- rootEle.ele("Say", { voice: attributes.TTS_VOICE_NAME, language: attributes.TTS_VOICE_LANGUAGE }, command.message.text);
561
+ rootEle.ele("Say", { voice: attributes.TTS_VOICE_NAME, language: attributes.TTS_VOICE_LANGUAGE }, text);
542
562
  }
543
563
 
544
-
545
564
  }
546
565
  //case type: FRAME
547
566
  if(command.message.type === 'frame' && command.message.metadata.src !== ""){
548
567
  if(command.message.text != ''){
549
- rootEle.ele("Say", { voice: attributes.TTS_VOICE_NAME, language: attributes.TTS_VOICE_LANGUAGE }, command.message.text);
568
+ rootEle.ele("Say", { voice: attributes.TTS_VOICE_NAME, language: attributes.TTS_VOICE_LANGUAGE }, text);
550
569
  }
551
570
  rootEle.ele('Play', {}, command.message.metadata.src )
552
571
  }
@@ -622,29 +641,45 @@ class TiledeskTwilioTranslator {
622
641
  }
623
642
 
624
643
  async generateTTS(text, attributes){
625
-
626
644
  let audioData = null;
627
- switch(this.voiceProvider){
628
- case VOICE_PROVIDER.OPENAI:
629
- let GPT_KEY = this.integrations.find((el => el.type === VOICE_PROVIDER.OPENAI))?.key
630
- audioData = await this.aiService.textToSpeech(text, attributes.TTS_VOICE_NAME, attributes.TTS_MODEL, GPT_KEY).catch((err)=>{
631
- console.log('errr while creating audio message', err.response?.data)
632
- })
633
- break;
634
- case VOICE_PROVIDER.ELEVENLABS:
635
- let ELEVENLABS_APIKEY = this.integrations.find((el => el.type === VOICE_PROVIDER.ELEVENLABS))?.key
636
- audioData = await this.aiService.textToSpeechElevenLabs(text, attributes.TTS_VOICE_NAME, attributes.TTS_MODEL, ELEVENLABS_APIKEY).catch((err)=>{
637
- console.log('errr while creating elevenlabs audio message', err.response?.data)
638
- })
639
- break;
645
+ try {
646
+ switch(this.voiceProvider){
647
+ case VOICE_PROVIDER.OPENAI:
648
+ let GPT_KEY = this.integrations.find((el => el.type === VOICE_PROVIDER.OPENAI))?.key
649
+ audioData = await this.aiService.textToSpeech(text, attributes.TTS_VOICE_NAME, attributes.TTS_MODEL, GPT_KEY)
650
+ break;
651
+ case VOICE_PROVIDER.ELEVENLABS:
652
+ let ELEVENLABS_APIKEY = this.integrations.find((el => el.type === VOICE_PROVIDER.ELEVENLABS))?.key
653
+ audioData = await this.aiService.textToSpeechElevenLabs(text, attributes.TTS_VOICE_NAME, attributes.TTS_MODEL, attributes.TTS_VOICE_LANGUAGE, ELEVENLABS_APIKEY)
654
+ break;
655
+ default:
656
+ throw new SttError('TTS_FAILED', 'Unsupported voice provider: ' + this.voiceProvider);
657
+ }
658
+
659
+ if (!audioData) {
660
+ throw new SttError('TTS_FAILED', 'TTS returned no audio data');
661
+ }
640
662
 
663
+ let fileUrl = await this.uploadService.upload(attributes.callSid, audioData, this.user)
664
+ winston.debug('(voice) Audio Message url captured after TTS -->', fileUrl)
665
+ return fileUrl
666
+ } catch (error) {
667
+ winston.error('(voice) TTS generation error:', error);
668
+ switch (error.code) {
669
+ case 'TTS_FAILED':
670
+ winston.error('(voice) TTS_FAILED:', error.message);
671
+ break;
672
+ case 'AI_SERVICE_ERROR':
673
+ winston.error('(voice) AI_SERVICE_ERROR:', error.message);
674
+ break;
675
+ case 'UPLOAD_SERVICE_ERROR':
676
+ winston.error('(voice) UPLOAD_SERVICE_ERROR:', error.message);
677
+ break;
678
+ default:
679
+ throw new SttError('TTS_FAILED', 'TTS generation failed: ' + error.message);
680
+ }
641
681
  }
642
682
 
643
- let fileUrl = await this.uploadService.upload(attributes.callSid, audioData, this.user).catch((err)=>{
644
- console.log('errr while uploading audioData', err.response)
645
- })
646
- console.log('(voice) Audio Message url captured after TTS -->', fileUrl)
647
- return fileUrl
648
683
  }
649
684
 
650
685
 
@@ -4,17 +4,10 @@ const jwt = require("jsonwebtoken");
4
4
  const { v4: uuidv4 } = require("uuid");
5
5
  const { promisify } = require('util');
6
6
 
7
- /*UTILS*/
8
- const utils = require('./utils-message.js')
9
- const TYPE_MESSAGE = require('./constants').TYPE_MESSAGE
10
- const MESSAGE_TYPE_MINE = require('./constants').MESSAGE_TYPE_MINE
11
- const MESSAGE_TYPE_OTHERS = require('./constants').MESSAGE_TYPE_OTHERS
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
-
16
7
  const winston = require("../winston");
17
8
 
9
+ const voiceEventEmitter = require('./services/voiceEventEmitter');
10
+
18
11
  class VoiceChannel {
19
12
 
20
13
 
@@ -48,8 +41,21 @@ class VoiceChannel {
48
41
  }
49
42
 
50
43
  this.redis_client = config.redis_client
44
+
45
+ this.listenToVoiceEvents();
51
46
 
52
47
  }
48
+
49
+
50
+ listenToVoiceEvents(){
51
+
52
+ voiceEventEmitter.on('saveSettings', async (data) => {
53
+ winston.debug('[VoiceChannel] listenToVoiceEvents: saveSettings event received -->', data)
54
+ if(data){
55
+ await this.saveSettingsForCallId(data, data.callSid);
56
+ }
57
+ });
58
+ }
53
59
 
54
60
 
55
61
  async getNextDelayTimeForCallId(callId){
@@ -94,50 +100,68 @@ class VoiceChannel {
94
100
 
95
101
  async saveSettingsForCallId(attributes, callId){
96
102
 
97
- winston.debug('saveSettingsForCallId: attributes -->', attributes)
98
- let flowAttributes = {}
99
- if(attributes && attributes.flowAttributes){
100
-
101
- flowAttributes = attributes.flowAttributes;
102
-
103
- //MANAGE VOICE SETTINGS from globals attributes
104
- let voiceProvider = VOICE_PROVIDER.TWILIO
105
- if(flowAttributes.VOICE_PROVIDER){
106
- voiceProvider = flowAttributes.VOICE_PROVIDER
107
-
108
- }
109
-
110
- // IF VOICE_PROVIDER is TWILIO --> default values is on user account twilio settings
111
- // IF VOICE_PROVIDER is OPENAI --> set default values from constants
112
- if(voiceProvider === VOICE_PROVIDER.OPENAI){
113
- flowAttributes.TTS_VOICE_NAME = flowAttributes.TTS_VOICE_NAME? flowAttributes.TTS_VOICE_NAME : OPENAI_SETTINGS.TTS_VOICE_NAME;
114
- flowAttributes.TTS_MODEL = flowAttributes.TTS_MODEL? flowAttributes.TTS_MODEL : OPENAI_SETTINGS.TTS_MODEL;
115
- flowAttributes.STT_MODEL = flowAttributes.STT_MODEL? flowAttributes.STT_MODEL : OPENAI_SETTINGS.STT_MODEL;
116
- }
117
-
118
-
119
- }
103
+ winston.debug('[VoiceChannel] saveSettingsForCallId: attributes -->', attributes)
120
104
 
121
105
  const index = await this.redis_client.get('tiledesk:voice:'+callId + ':attributes');
122
- winston.debug('saveSettingsForCallId: attributes found -->'+index)
106
+ winston.debug('[VoiceChannel] saveSettingsForCallId: attributes found -->'+index)
123
107
  if(index){
124
108
  //set index to default (0)
125
- await this.redis_client.set('tiledesk:voice:'+callId + ':attributes', JSON.stringify(flowAttributes), {'EX': 86400});
109
+ await this.redis_client.set('tiledesk:voice:'+callId + ':attributes', JSON.stringify(attributes), {'EX': 86400});
126
110
  return;
127
111
  }
128
112
  //if index is not present: set to default (0)
129
- await this.redis_client.set('tiledesk:voice:'+callId + ':attributes', JSON.stringify(flowAttributes), {'EX': 86400});
113
+ await this.redis_client.set('tiledesk:voice:'+callId + ':attributes', JSON.stringify(attributes), {'EX': 86400});
130
114
 
131
115
  }
132
116
 
133
117
 
134
118
  async getSettingsForCallId(callId){
135
119
  const attributes = await this.redis_client.get('tiledesk:voice:'+callId + ':attributes');
120
+ winston.debug('[VoiceChannel] getSettingsForCallId: attributes found -->', attributes, callId)
136
121
  if(attributes){
137
122
  return JSON.parse(attributes)
138
123
  }
139
124
  return {};
140
125
  }
126
+
127
+
128
+ async setSessionForCallId(callSid, session) {
129
+ await this.redis_client.set(`tiledesk:voice:${callSid}:session`, JSON.stringify(session), { 'EX': 86400 });
130
+ //for (const [key, value] of Object.entries(redis_data)){
131
+ // await redis_client.hSet('tiledesk:voice:'+callId, key, JSON.stringify(value))
132
+ //}
133
+ //await redis_client.expire('tiledesk:voice:'+callId, 86400)
134
+ }
135
+
136
+ async getSessionForCallId(callSid) {
137
+ const sessionData = await this.redis_client.get(`tiledesk:voice:${callSid}:session`);
138
+ //let redis_data = await redis_client.hGetAll('tiledesk:voice:'+callId);
139
+ if (sessionData) {
140
+ return JSON.parse(sessionData);
141
+ }
142
+ return null;
143
+
144
+ }
145
+
146
+
147
+ async deleteCallKeys(callSid) {
148
+ const pattern = `tiledesk:voice:${callSid}:*`;
149
+ let cursor = 0;
150
+
151
+ do {
152
+ const reply = await this.redis_client.scan(cursor, {
153
+ MATCH: pattern,
154
+ COUNT: 100
155
+ });
156
+
157
+ cursor = reply.cursor;
158
+ const keys = reply.keys;
159
+
160
+ if (keys.length > 0) {
161
+ await this.redis_client.del(keys);
162
+ }
163
+ } while (cursor !== 0);
164
+ }
141
165
 
142
166
 
143
167
 
@@ -58,5 +58,21 @@ module.exports = {
58
58
  TTS_VOICE_NAME: 'alloy',
59
59
  TTS_MODEL: 'tts-1',
60
60
  STT_MODEL: 'whisper-1'
61
- }
61
+ },
62
+ ELEVENLABS_SETTINGS:{
63
+ TTS_VOICE_NAME: '21m00Tcm4TlvDq8ikWAM',
64
+ TTS_MODEL: 'eleven_multilingual_v2',
65
+ TTS_VOICE_LANGUAGE: 'en',
66
+ STT_MODEL: 'scribe_v1'
67
+ },
68
+ NON_SPEECH_TOKENS: [
69
+ '(music)',
70
+ '(noise)',
71
+ '(silence)',
72
+ '(background noise)',
73
+ '(applause)',
74
+ '(breathing)',
75
+ '(laughs)',
76
+ '(laughter)'
77
+ ]
62
78
  }